带着下面的这个问题开始ViewModel的学习:
ViewModel的生命周期是如何控制的,而且如何保证在必定范围内的惟一性?
官方文档里这样写到:
The ViewModel class is designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.
ViewModel 简单来讲 这个类是设计用来存储UI层的数据,以及管理对应的数据,而且这些数据不受配置变化的影响。可以作到当数据修改的时候,能够立刻刷新Ui效果,好比屏幕的旋转操做。
引言
Android系统自己提供控件,好比Activity 和Fragment ,这些控件都是具备生命周期方法,这些生命周期方法被系统调用。
可是当这些控件由于一些缘由被系统随时销毁或是从新建立时候,任何存放在这里的数据都有可能会丢失。举个栗子,Activity中有一个查询获得的用户列表,这时候Activity被重建,新的Activity须要再次去获取用户数据。若是简单的数据可使用控件自带的方法,将数据保存到onSaveInstances()方法中,在下次OnCreate()中从新将数据取出来,好比UI状态这类少许数据是能够的,可是对于上述提到的大量的数据,好比列表数据,这样作就很不合时宜了。
另外一个问题,常常须要在Activty中加载数据,这些数据通常是异步耗时操做,由于获取数据须要联网或是花费很长时间。当前的Activity就须要管理这些数据调用,不然可能产生内存泄露的问题。这些回调事件可能会很是耗时,这时候Ui组件管理这些调用的同时,在UI组件销毁时候还须要清除这些调用。这就形成须要花费更多成本进行维护管理,并且在UI重建时候如configuration change,又须要再次从新调用,形成了不少资源的浪费。
同时Ui组件不只仅只是用来加载数据,更要对用户的操做做出响应和处理,还要加载其余资源,致使Ui类变的愈来愈大,愈来愈臃肿,这就是常说的上帝类。这种状况对代码的维护和 测试 都是很是不友好的。
前人在这些问题的基础上开发出了MVP框架 ,建立相同相似于生命周期函数作代理,一方面减小Activity的代码量,一方面优化了各个功能模块的逻辑。
ViewModel
Google官方提出的AAC 的ViewModel 就是用于解决上述问题。
ViewModel 用于为Ui组件提供管理数据,而且可以在须要的时候扔能保持里面的数据。其提供的自动绑定的形式,当数据源有更新的时候能够自动当即的更新Ui效果。
下面看一个官方的小代码实例:
publicclass MyViewModel extends ViewModel {
privateMutableLiveData<List<User>> users;
publicLiveData<List<User>>getUsers() {
if(users ==null) {
users =newMutableLiveData<List<Users>>();
loadUsers();
}
returnusers;
}
privatevoidloadUsers() {
// do async operation to fetch users
}
}
You can then access the list from an activity as follows:
Activity 访问User List 数据
publicclass MyActivity extends AppCompatActivity {
publicvoidonCreate(Bundle savedInstanceState) {
MyViewModel model =
ViewModelProviders.of(
this).get(MyViewModel.class);
model.getUsers().observe(this, users -> {
// update UI
});
}
@Override
protected void onDestroy() {
super.onDestroy();
mViewModelStore.clear()
}
}
当咱们获取ViewModel实例的时候,ViewModel 对象是经过ViewModelProvider保存在LifeCycle中,ViewModel会一直保存在LifeCycle中,直到Activity或是Fragment被销毁掉,Framework会调用ViewModelStore的clear方法,也就是调用ViewModel的onCleared()方法来进行资源的清理,那么ViewModel 也会被销毁的。
Caution: A ViewModel must never reference a view, Lifecycle, or any class that may hold a reference to the activity context.
ps:由于ViewModel的生命周期是和Activity分开的,因此在ViewModel中禁止引用任何View对象或者任何引用了Activity的Context的实例对象。若是ViewModel中须要Application的context能够继承AndroidViewModel类。
那么思考 用户主动按了返回Home键,主动销毁了这个Activity呢?
这时候系统会调用ViewModel的onClear()方法 清除ViewModel中的数据。
先上一张
ViewModel的生命周期示意图:
如图 ,VIewModel相对于Activity 或是Fragment 的生命周期来讲很是简单,就一个生命周期函数:onCleared(),会在Activity的onDestroy()以后执行,那么是否是能够说在Fragment的生命周期函数内也是在onDestroy以后执行呢?
ViewModel的实现过程
//获取当前类的ViewModel提供者,以后在传入须要得到的ViewModel的类型
MyViewModel model = ViewModelProviders.of(this) .get(MyViewModel.class);
解析:若是传入的是this 是Fragment 就先判断是否已经关联到Activity上,没有就抛出非法参数异常。以后在初始化一个sDefaultFactory对象,用于建立ViewModelProvider,并在viewModelProvider的构造函数中初始化一个ViewModelStores对象
俩个工厂方法用于建立ViewModelStore ,并区分传入的是Activity 仍是 Fragment
以传入的是Activity为例:
建立FragmentManager对象,并获取,查找当前的Activity有没有添加过HoldFragment, 若是没有呢则去尚未添加的Activity/Fragment 的 HoldFragment列表中查询,看看有没有已经建立的HoldFragment。若是没有就建立一个新的HoldFragment ,同时给Application注册一个Activity的生命监听器,再把建立饿的HoldFragment添加到缓存列表中。
HoldFragment()又是如何操做的呢?
在onCreate方法中执行了将在未添加到Activity或是Fragment的HolderFragment列表中删除当前的Activity 或是Fragment。
在onDestroy方法中执行了ViewModel的clear方法,当Ui组件被销毁的时候自动通知Lifecycle进行解除绑定清除ViewModel资源的操做。
简单总结以上内容:
- 查找当前的Activity/Fragment中是否有已经添加的HoldFragment,有则返回。
- 查找当前的Activity/Fragment是否有已经建立可是并未添加的HoldFragment,有则返回。
- 注册Activity/Fragment的生命周期监听。
- 建立新的HoldeFragment,并添加的缓存列表。
- HoldFragment在关联到Activity/Fragment以后会在缓存中去掉当前的Activity/Fragment对应的HoldFragment
- HoldFragment在onDestory的时候会调用其成员变量mViewStore的clear方法。
回到以前建立ViewModelProvider的地方:
/**
* Creates {@code ViewModelProvider}, which will create {@code ViewModels} via the given
* {@code Factory} and retain them in the given {@code store}.
*
* @param store {@code ViewModelStore} where ViewModels will be stored.
* @param factory factory a {@code Factory} which will be used to instantiate
* new {@code ViewModels}
*/
public ViewModelProvider(@NonNull ViewModelStorestore, @NonNull Factory factory) {
mFactory = factory;
this.mViewModelStore= store;
}
构造方法中先给两个成员变量赋值,而后经过ViewModelStore的get方法获取ViewModel对象
viewModel = mFactory .create(modelClass);
mViewModelStore.put(key,viewModel);
若是获取不到传入类的ViewModel 就经过工厂类Factory建立一个新的viewModel 经过put方法添加到ViewModelStore中。
简而言之就是利用Fragment的方式去获取生命周期,而后再利用工厂类建立ViewModel。
关于在必定范围内的惟一性,由于ViewModelStore是HoldFragment的成员变量,HoldFragment是经过FragmentManager添加到指定的Activity/Fragment,那么对于当前的宿主,只有一个HoldFragment,也就只有一个ViewModelStore,同时也就只有一个ViewModel。
ViewModel的在Fragment间的数据分享
有时候一个Activity中的两个或多个Fragment须要分享数据或者相互通讯,这样就会带来不少问题,好比数据获取,相互肯定生命周期。
ViewModel能够很好的解决该类问题。有两个Fragment,一个Fragment提供点击每一个item显示的详情,另外一个Fragment提供一个列表。那两个的交互代码应该是如何表现的呢?
实例代码以下:
//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;
}
}
//第一个Fragment
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);
});
}
}
//第二个Fragment
public class DetailFragment extends LifecycleFragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity())
.get(SharedViewModel.class);
model.getSelected().observe(this, {
item -> // update UI
});
}
}
两个Fragment都是经过getActivity()来获取
ViewModelProvider。这意味着两个Activity都是获取的属于同一个Activity的同一个ShareViewModel实例。
这样作优势以下:
- Activity不须要写任何额外的代码,也不须要关心Fragment之间的通讯。
- Fragment不须要处理除SharedViewModel之外其余的代码。这两个Fragment不须要知道对方是否存在。
- Fragment的生命周期不会相互影响,即便用其余Fragment替换其中的一个Fragment,另外一个依然能也不受影响。
ViewModel和SavedInstanceState对比
最后前文提到保存简单的数据可使用Activity自带的SavedInstanceState方法,那这个和viewMOdel的区别是?
ViewModel使得在屏幕旋转等操做时候保存数据变得很便捷,可是这不能用于应用被系统kill时的持久化数据。举个简单的例子,有一个界面展现国家信息。不该该把整个国家信息放到SavedInstanceState里,而是把国家对应的id放到SavedInstanceState,等到界面恢复时,再经过id去获取详细的信息。这些详细的信息应该被存放在数据库中。说到数据库,下篇文章将会介绍Android Architecture Components提供的Room来操做数据库。
小结
ViewModel其实就是经过给宿主添加Fragment的方式来获取宿主的生命周期。在HoldFragment中持有一个集合用于保存当前宿主的ViewModel,只须要在onDestroy方法中调用集合的clear方法,就能间接调用到ViewModel的onCleared方法了,这样实现了对其生命周期的控制。