前言
- 为了更好了解ViewModel,建议看下几篇文章
知识准备
- 重要知识介绍(后面用到)
这个方法是fragment的方法,根据官方注释,我们知道一旦我们设置setRetainInstance(true),意味着当我们旋转屏幕的时候,activity重绘,fragment不会重绘,它将会保留。也就意味着Fragment的onCreate和onDestory方法都不会调用,这样的特性很多被用来用状态保存,数据恢复。具体是怎么样的过程,请看fragment源码分析/** * 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: * <ul> * <li> {@link #onDestroy()} will not be called (but {@link #onDetach()} still * will be, because the fragment is being detached from its current activity). * <li> {@link #onCreate(Bundle)} will not be called since the fragment * is not being re-created. * <li> {@link #onAttach(Activity)} and {@link #onActivityCreated(Bundle)} <b>will</b> * still be called. * </ul> */ public void setRetainInstance(boolean retain) { mRetainInstance = retain; }
ViewModel是什么?
-
定义
ViewModel类旨在以一种有生命周期的方式存储和管理与UI相关的数据。 ViewModel类允许数据在屏幕旋转等配置变化后存活
-
解决的问题
Android框架控制着UI控制器(Activity或者Fragment)的生命周期,它就有可能决定销毁或者重建我们的UI控制,以响应完全不受控制的某些用户操作或设备事件。那么就会出现几个问题。
-
如果被销毁那么存储在其中的所有瞬态UI相关数据都将丢失.
例如:您的应用可能会在其中一个活动中包含用户列表。 当针对配置更改重新创建活动时,新活动必须重新获取用户列表。 对于简单的数据,活动可以使用onSaveInstanceState()方法并从onCreate()的包中恢复其数据,但是这种方法仅适用于可以序列化然后反序列化的少量数据,而不适用于潜在的大量数据像用户或位图的列表。
-
UI控制获取数据的时候经常进行异步调用,可能需要一些时间来返回。 UI控制器需要管理这些调用,并确保系统在销毁后清理它们以避免潜在的内存泄漏。
这种管理需要大量的维护,并且在为配置更改而重新创建对象的情况下,由于对象可能不得不重新发出已经做出的调用(可能会重新请求数据),所以浪费资源.
-
UI控制器(如活动和片段)主要用于显示UI数据,对用户操作做出反应,或处理操作系统通信(如权限请求)。
如果要求UI控制器也负责从数据库或网络加载数据,从而增加了该类的膨胀。 为UI控制器分配过多的责任可能会导致一个类尝试单独处理应用程序的所有工作,而不是将工作委托给其他类。 通过这种方式给UI控制器分配过多的责任也使测试变得更加困难。
-
用法简介
-
依赖
compile "android.arch.lifecycle:runtime:1.0.3" compile "android.arch.lifecycle:extensions:1.0.0-rc1" annotationProcessor "android.arch.lifecycle:compiler:1.0.0-rc1"
-
api用法
- 创建ViewModel
public class MyViewModel extends ViewModel { private MutableLiveData<List<User>> users; public LiveData<List<User>> getUsers() { if (users == null) { users = new MutableLiveData<List<Users>>(); loadUsers(); } return users; } private void loadUsers() { // Do an asyncronous operation to fetch users. } }
ViewModel对象在配置更改期间自动保留,以便它们保存的数据立即可用于下一个activity或fragment
- acitivity或fragment获取数据
public class MyActivity extends AppCompatActivity { public void onCreate(Bundle savedInstanceState) { // Create a ViewModel the first time the system calls an activity's onCreate() method. // Re-created activities receive the same MyViewModel instance created by the first activity. MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class); model.getUsers().observe(this, users -> { // update UI }); } }
如果Activity重新绘制,但是ViewModel实例并不会被销毁,而是重新从ViewModel中获取数据,等到Activity销毁(退出或者被系统杀死),框架会调用onCleared()方法,以便清理资源;
警告:viewModel绝不能引用视图, Lifecycle或任何可能持有对活动上下文的引用的类。
ViewModel可以包含lifeCycleObservers.例如liveData。但是ViewModel绝不能观察生命周期感知的可观察对象(LiveData对象的更改),如果ViewModel需要上下文引用,请用AndroidModelView,它含有Application的对象.
ViewModel的生命周期
从图中可以看出我们发现,当activity因屏幕旋转而销毁,但是ViewModel一直存在,也就是这个对象一直都在(框架核心,怎么实现源码分析会讲到),直到finished才调用clear清除内存。
Fragment之间共享数据
-
一个Activity中两个Fragment进行通信是很常见的,想象一下,主 - 从Fragmetn的一种常见情况,其中有一个片段,用户从列表中选择一个项目,另一个片段显示所选项目的内容。这种情况从来都不是微不足道的,因为这两个片段都需要定义一些接口描述,而所有者活动必须将两者联系在一起。 此外,这两个片段必须处理其他片段尚未创建或可见的场景。
对于这个常见的痛点,可以用ViewModel来解决,这些Fragment可以使用Activity的Scope来共享一个ViewModel,来处理通信。
public class SharedViewModel extends ViewModel { private final MutableLiveData<Item> selected = new MutableLiveData<Item>(); public void select(Item item) { selected.setValue(item); } public LiveData<Item> getSelected() { return selected; } } public class MasterFragment extends Fragment { private SharedViewModel model; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class); itemSelector.setOnClickListener(item -> { model.select(item); }); } } public class DetailFragment extends Fragment { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class); model.getSelected().observe(this, { item -> // Update the UI. }); } }
注意:我们在获取ViewModelProvider时,这两个是使用的是getActivity(),而不是当前的Fragment(后面会分析源码)。所以两个Fragment用的是同一个对象。
好处如下:
- 对于Activity来说,他不知道这个交流,也不用管理
- 对于Framgent,除了共同拥有这个SharedViewModel,他们之间不需要了解彼此。如果一个Framgnet消失不会影响另一个Framgent.
- 每个Framgent都有自己独立的生命周期,不互相影响。
源码分析
- 我们先找到程序的入口
ViewModelProviders.of(this).get(MyViewModel.class)
-
先看of方法
return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory)
返回一个ViewModelProvider,需要两个参数ViewModelStore(存放ViewModel的仓库),Factory(用工厂模式创建我们的ViewModel对象)。假如说我们的Activity重新绘制了,那么这里的ViewModelProvider就是不同的实例。这个类主要的作用就是获取我们ViewModel对象。
-
看一下of()方法
ViewModelStores.of(activity)
ViewModelStores是干什么的呢?提供一些静态方法获取所传入activity的ViewModeStore.
@MainThread public static ViewModelStore of(@NonNull FragmentActivity activity) { return holderFragmentFor(activity).getViewModelStore(); }
-
holderFragmentFor
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) public static HolderFragment holderFragmentFor(FragmentActivity activity) { return sHolderFragmentManager.holderFragmentFor(activity); }
- 我们看到是静态方法,这个方法不是ViewModelStore,而是
HolderFragment的一个静态方法,返回一个HolderFragment对象. - 然后回到3处调用getViewModelStore,返回ViewModelStore。所以说HolderFragment中有一个ViewModelStore。对应关系是一个HolderFrament含有一个ViewModelStore(存放ViewModel的仓库),一个ViewModelStore存放着多个ViewModel。
- HolderFragmentManager是属于HolderFramgent的静态内部类
- 我们看到是静态方法,这个方法不是ViewModelStore,而是
-
在(iii)处调用了sHolderFragmentManager.holderFragmentFor
HolderFragment holderFragmentFor(FragmentActivity activity) { FragmentManager fm = activity.getSupportFragmentManager(); HolderFragment holder = findHolderFragment(fm); if (holder != null) { return holder; } holder = mNotCommittedActivityHolders.get(activity); if (holder != null) { return holder; } holder = createHolderFragment(fm); mNotCommittedActivityHolders.put(activity, holder); return holder; }
-
首先在在Activity的supportFragmentManager中的查找,有的话就会返回
private static HolderFragment findHolderFragment(FragmentManager manager) { if (manager.isDestroyed()) { throw new IllegalStateException("Can't access ViewModels from onDestroy"); } Fragment fragmentByTag = manager.findFragmentByTag(HOLDER_TAG); if (fragmentByTag != null && !(fragmentByTag instanceof HolderFragment)) { throw new IllegalStateException("Unexpected " + "fragment instance was returned by HOLDER_TAG"); } return (HolderFragment) fragmentByTag; }
-
没有的话在我们存放的集合中查找有的话就返回,在HolderFragmentManager集合如下
private Map<Activity, HolderFragment> mNotCommittedActivityHolders = new HashMap<>();
可以看出多个Activity可能对应一个HolderFrament
-
没有的话就创建并放入我们的mNotCommittedActivityHolders。
private static HolderFragment createHolderFragment(FragmentManager fragmentManager) { HolderFragment holder = new HolderFragment(); fragmentManager.beginTransaction().add(holder, HOLDER_TAG).commitAllowingStateLoss(); return holder; }
创建Fragment对象,这是一个无ui的Fragment,==主要作用就是用ViewModelStore储存我们的ViewModel,并在Fragement的OnDestory调用ViewModelStore的clear释放所有内存==。
有人要说,fragment对象还是要重建啊,那么ViewModel也要重建啊,因为它属于Fragment,这就用到了文章开篇讲到了setRetainInstance方法。看下源码
public HolderFragment() { setRetainInstance(true); }
在我们初始化的时候会调用setRetainInstance,这就解决这个问题了。
-
拿到HolderFragment对象,回到(ii)处调用getViewModelStore,拿到ViewModelStore对象。
-
- 我们拿到ViweModelStore回到(1)处,我们就去拿我们的ViewModel。看源码
@NonNull 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); }
- 看get(DEFAULT_KEY + ":" + canonicalName, modelClass)
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) { ViewModel viewModel = mViewModelStore.get(key); if (modelClass.isInstance(viewModel)) { //noinspection unchecked return (T) viewModel; } else { //noinspection StatementWithEmptyBody if (viewModel != null) { // TODO: log a warning. } } viewModel = mFactory.create(modelClass); mViewModelStore.put(key, viewModel); //noinspection unchecked return (T) viewModel; }
- 首先是我们从mViewModelStore取出我们想要的ViewModel.
有的话就返回 - 没有的话就利用工厂模式反射生产我们所要的ViewModel对象,同时把我们的ViewModel对象放入mViewModelStore。同时返回我们的ViewModel.
总结:
核心思想就是HolderFragment调用setsetRetainInstance(true),使得HolderFragment在FragmentMannager调用FindFragmentBytag,找到的是同一个HolderFragment对象(无论Activity是否重绘),这也保证了HolderFragment中的ViewModelStore(存放我们ViewModel的地方)不被销毁,然后我们取出我们所要的ViewModel,进行数据读取。