数据共享与持久化——ViewModel 的使用与原理

介绍

ViewModel属于ACC框架组件之一,用以解决数据持久与共享问题,此外,也将数据的相关行为从UI中分离出来。

前言

对于ViewModel的使用以及原理,可能需要对Lifecycle和LiveData有一些理解,不然可能会影响对某些内容的理解。以下为可参考资料。

正文

案例

public class MyData extends LiveData<String> {

    private static final String TAG = "T-MyData"; 

    public  MyData(){
        setValue("hi");
        Log.d(TAG, "create new liveData ");
    }

    @Override
    protected void onActive() {
        super.onActive();
        Log.d(TAG, "onActive ");
    }

    @Override
    protected void onInactive() {
        super.onInactive();
        Log.d(TAG, "onInactive ");
    }

    public void changeValue(String value){
        setValue(value);
    }

}
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "T-MainActivity";

    private TabLayout nav;
    private Fragment nowFragment;

    private Fragment[] fs = new Fragment[]{
            new AFragment(),
            new BFragment()};

    private MViewModel mViewModel;
    MyData data;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d(TAG, "activity onCreate ");

        nav = findViewById(R.id.nav);
        nav.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                if (tab.getText().equals("A")){
                    nowFragment = fs[0];
                }else {
                    nowFragment = fs[1];
                }
                getSupportFragmentManager().beginTransaction().replace(R.id.container, nowFragment).commit();
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {

            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {

            }
        });
        nav.addTab(nav.newTab().setText("A"));
        nav.addTab(nav.newTab().setText("B"));

        mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
        findViewById(R.id.attack).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyData data = mViewModel.getLiveData();
                data.changeValue(data.getValue() + "~");
            }
        });
    }
}
public class AFragment extends Fragment {

    View mainView;
    private TextView text;

    private MViewModel mViewModel;

    public AFragment() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        mainView = inflater.inflate(R.layout.fragment_a, container, false);
        text = mainView.findViewById(R.id.A_text);

        mViewModel = ViewModelProviders.of(getActivity()).get(MViewModel.class);
        mViewModel.getLiveData().observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                text.setText("A--" + s);
            }
        });

        return mainView;
    }

}
public class BFragment extends Fragment {

    private View mainView;
    private TextView text;

    private MViewModel mViewModel;

    public BFragment() {
        // Required empty public constructor
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        mainView = inflater.inflate(R.layout.fragment_b, container, false);
        text = mainView.findViewById(R.id.B_text);

        mViewModel = ViewModelProviders.of(getActivity()).get(MViewModel.class);
        mViewModel.getLiveData().observe(this, new Observer<String>() {
            @Override
            public void onChanged(@Nullable String s) {
                text.setText("B--" + s);
            }
        });
        return mainView;
    }
}
public class MViewModel extends AndroidViewModel {

    private MyData data;

    public MViewModel(Application application) {
        super(application);
        data = new MyData();
    }

    public MyData getLiveData(){
        return data;
    }
}

页面如下图


ViewModel.jpg

描述:LiveData持有String数据初始为hi,A和Bfragment分别从ViewModel中获取LiveData并监听其中数据,在Activity上有一按钮,每次点击更新String数据为其本身加上"~"。
行为:单机几次按钮,来回切换A和B按钮,可以看到数据在Fragment间都是最新的(图不贴,懒),翻转屏幕,再次观察,日志如下图


viewmodel日志.jpg

从日志图中可得到的信息如下:

  • Activity与Fragment被重建
  • LiveData对Fragment的绑定关系被重建
  • ViewModel没有被重建,持有原来的数据对象
    从运行情况可以得知:
  • 数据保持共享

ViewModel是如何做到数据持久化以及数据共享的?以下为讲解

提醒

由于版本问题,ViewModel对较低版本的SDK做了兼容,因此在实现原理上分有两种做法。在此提前点明情况,方便以下的叙述顺序流畅。

原理一(此SKD为27)

入口

mViewModel = ViewModelProviders.of(this).get(MViewModel.class);
    public static ViewModelProvider of(@NonNull FragmentActivity activity) {
        return of(activity, null);
    }
    public static ViewModelProvider of(@NonNull FragmentActivity activity,
            @Nullable Factory factory) {
        // 获取当前程序所依托的Application
        Application application = checkApplication(activity);
        if (factory == null) {
            // 获取AndroidViewModelFactory,单例
            factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
        }
        // ViewModelStores.of()返回了ViewModelStore
        return new ViewModelProvider(ViewModelStores.of(activity), factory);
    }

这里只要注意,of()返回了ViewModelProvider,其持有ViewModelStore信息和AndroidViewModelFactory。 而ViewModelStore其实规划了自身将被如何存储

当前位置
ViewModelProviders.of()
- ViewModelProvider()
-- ViewModelStores.of()

    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        // 当前SDK下,运行到这里就返回了,证据在下一张代码引用图
        if (activity instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) activity).getViewModelStore();
        }
        return holderFragmentFor(activity).getViewModelStore();
    }
public class FragmentActivity extends BaseFragmentActivityApi16 implements
        ViewModelStoreOwner,

既然FragmentActivity实现了ViewModelStoreOwner,那么对应的获取方式如下

当前位置
FragmentActivity. getViewModelStore()

    public ViewModelStore getViewModelStore() {
        if (getApplication() == null) {
            throw new IllegalStateException("Your activity is not yet attached to the "
                    + "Application instance. You can't request ViewModel before onCreate call.");
        }
        // 为空新建
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
        return mViewModelStore;
    }

可见,FragmentActivity自身是持有ViewModelStore

以上构造出了ViewModelProvider,紧接着去获取具体的ViewModel

当前位置ViewModelProvider
    public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
        // 类名
        String canonicalName = modelClass.getCanonicalName();
        if (canonicalName == null) {
            throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
        }
        return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
    }

    public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        // 从ViewModelStore中获取viewModel
        ViewModel viewModel = mViewModelStore.get(key);
        
        // 获取到,返回
        if (modelClass.isInstance(viewModel)) {
            return (T) viewModel;
        } else {
            if (viewModel != null) {
            }
        }
        // 创建viewModel,factory为AndroidViewModelFactory
        viewModel = mFactory.create(modelClass);
        // 将ViewModel与类名绑定,并保存
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

以上代码就获取到了具体的ViewModel,其中AndroidViewModelFactory.create()代码仅仅是通过反射创建了ViewModel实例并捕捉了异常。先做个小结:

  • ViewModelProvider持有ViewModelStore信息
  • VIewModelStore规划自身的提取方式并持有ViewModel信息
  • 通过类作为key获取具体的ViewModel实现共享

我们知道,在屏幕旋转时,如果没有对Activity做相应的配置更变设置,Activity是会被重建的,而Activity被销毁时,相应持有的数据理应被释放。那ViewModel是如何逃过一劫的?

思考

在源码看到这的时候,实际上正面线索已无法跟踪,因为在庞大的Activity架构之中,很难快速地找到事件发源地。那现在,如何找到ViewModel持久的线索呢?在当前条件下,FragmentActivty是直接持有了ViewModelStore的信息,那么在配置更变需要重建时,必定要对ViewModelStore做一些处理,因此选择了跟踪FragmentActity.mViewModelStore。

果然,定位到了以下位置

FragmentActivity

    public final Object onRetainNonConfigurationInstance() {
       .......
        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = mViewModelStore;
        nci.fragments = fragments;
        return nci;
    }

在配置更改需要重建页面的时候,系统会去保存现场以便恢复,在这个函数中,ViewModelStore被作为状态之一被保存在NonConfigurationInstances之中。依次类推,有保存就有取出,继续跟踪,定位到了以下位置

FragmentActivity

    protected void onCreate(@Nullable Bundle savedInstanceState) {
        .....
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            mViewModelStore = nc.viewModelStore;
        }
      .....
  }

可见,在生命周期onCreate,备份的ViewModelStore被取出。因此,当重建后,再次通过ViewModelProvider.get()去获取ViewModel时候,会直接获取到此ViewModelStore并取出ViewModel,不会再通过AndroidVIewModelFactory重建ViewModel。

这里小结一下:

  • 在配置更变需要重建页面时,ViewModelStore会在重建前交由NonConfigurationInstances保管,并在重建后取出恢复。

以上,就是ViewModel在高SDK下的数据共享与持久化的原理。

接下来,是适配低版本的。

原理二 (实例SDK为25)

之前说过,ViewModelStore规划了自身将被如何存储,而且差异也在于低版本的SDK的Activity的各父类并不是ViewModelStoreOwner,回看代码ViewModelStore.of()

    public static ViewModelStore of(@NonNull FragmentActivity activity) {
        if (activity instanceof ViewModelStoreOwner) {
            return ((ViewModelStoreOwner) activity).getViewModelStore();
        }
        // 当前SDK下,运行到此返回
        return holderFragmentFor(activity).getViewModelStore();
    }

以上代码从HolderFragment取得ViewModelStore

当前位置
HolderFragment

    private static final HolderFragmentManager sHolderFragmentManager = new HolderFragmentManager();

    public static HolderFragment holderFragmentFor(FragmentActivity activity) {
        return sHolderFragmentManager.holderFragmentFor(activity);
    }

当前位置
HolderFragement.HolderFragmentManager

        HolderFragment holderFragmentFor(FragmentActivity activity) {
            FragmentManager fm = activity.getSupportFragmentManager();
            // 查找合适的HolderFragment
            HolderFragment holder = findHolderFragment(fm);
            // 查找到返回
            if (holder != null) {
                return holder;
            }
            // 通过key(activity)取出HolderFragment
            holder = mNotCommittedActivityHolders.get(activity);
           // 取到返回
            if (holder != null) {
                return holder;
            }
            
            if (!mActivityCallbacksIsAdded) {
                mActivityCallbacksIsAdded = true;
                activity.getApplication().registerActivityLifecycleCallbacks(mActivityCallbacks);
            }
            // 注入HolderFragment
            holder = createHolderFragment(fm);
            // 绑定HolderFragment和key
            mNotCommittedActivityHolders.put(activity, holder);
            return holder;
        }

以上代码代码可知,HolderFragment静态对象HolderFragmentManager,持有HolderFragment对象与key的对应管理。这里因为HolderFragment被注入,需要看一下初始工作。

当前位置HolderFragment
    private ViewModelStore mViewModelStore = new ViewModelStore();

    public HolderFragment() {
        setRetainInstance(true);
    }

新的HolderFragment新建时自身持有了ViewMolderStore,之前通过ViewModelStores.of()获取的,就是这个ViewMolderStore。

能区别出,在原理一种,具有生命周期的对象,本身会持有ViewModelStore,而在原理二中,会通过注入HolderFrament,去间接持有ViewModelStore。其他流程是一致的。

现在,就还剩一个问题,简介持有的ViewModelStore,如何保持持久化?

注意到,在初始化HolderFragment是,设置了mRetainInstance,如下

    /**
     * Control whether a fragment instance is retained across Activity
     * re-creation (such as from a configuration change).  This can only
     * be used with fragments not in the back stack.  If set, the fragment
     * lifecycle will be slightly different when an activity is recreated
     */
    public void setRetainInstance(boolean retain) {
        mRetainInstance = retain;
    }

注释大意为:在Activity 销毁-重建时控制是否是有fragment实例。仅在fragment不在back stack时生效。当mRetainInstance设置为trues时,生命周期表现行为与重建时有轻微不同。

简单来说,HolderFragment并没有被销毁,而当再次通过key去取出对应的HolderFragment时,就能取出。

至于HolderFragment为什么没有被销毁,那就需要了解FragmentManager如何去管理Fragment了,这就扯远了。

总结

通过以上的梳理分析,算是讲明了ViewModel的数据如何共享以及持久化,一下为要点:

  • 通过ViewModelProvider持有ViewModelStore和Factory,并主要用来获取对应的ViewModelStore
  • ViewModelStore存储了key-value形势的类与ViewModel的对应,实现了数据的共享,Factory负责在需要时创建出ViewModel
  • ViewModelStore被Activity或Fragment持有, 或通过注入HolderFragment间接持有
  • Activity因配置原因销毁-重建时,ViewModelStore被NonConfigurationInstances保存或被HolderFragment保存,再此需求时从从保存处恢复。

简单原理图


ViewModel原理-2.png

细节

当前位置
HolderFragment

    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        sHolderFragmentManager.holderFragmentCreated(this);
    }


当前位置
HolderFragment.HolderFragmentManager

        void holderFragmentCreated(Fragment holderFragment) {
            // 获取作为依托的父fragment
            Fragment parentFragment = holderFragment.getParentFragment();
            if (parentFragment != null) {
                // 释放父fragment
                mNotCommittedFragmentHolders.remove(parentFragment);
                parentFragment.getFragmentManager().unregisterFragmentLifecycleCallbacks(
                        mParentDestroyedCallback);
            } else {
                //释放Activity
                mNotCommittedActivityHolders.remove(holderFragment.getActivity());
            }
        }

在Activity销毁-重建状态下,虽然ViewModelStore跟随HolderFragment被保存了,但是此时的与HolderFragment绑定的Activity或Fragment已不再是当时候的对象,因此,会存在内存泄漏问题。因此,在HolderFragment生命周期onCreate()里解决这一问题。

注意到,HolderFragment与Activity或Fragment间的对应关系链已不存在,那么再去获取对应的HolderFragment是,会通过holderFragmentFor() ->findHolderFragment() 找到,如下图


viewModel获取Holder.jpg
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容