Android Architecture Components 系列(四)ViewModel
带着下面的这个问题开始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 or Fragment 不适于保存大量数据
但是当这些控件因为一些原因被系统随时销毁或是重新创建时候,任何存放在这里的数据都有可能会丢失。举个栗子,Activity中有一个查询得到的用户列表,这时候Activity被重建,新的Activity需要再次去获取用户数据。如果简单的数据可以使用控件自带的方法,将数据保存到onSaveInstances()方法中,在下次OnCreate()中重新将数据取出来,比如UI状态这类少量数据是可以的,但是对于上述提到的大量的数据,比如列表数据,这样做就很不合时宜了。
-
在Activity中进行大量的耗时操作和数据的回调管理会造成大量的资源浪费
另一个问题,经常需要在Activty中加载数据,这些数据一般是异步耗时操作,因为获取数据需要联网或是花费很长时间。当前的Activity就需要管理这些数据调用,否则可能产生内存泄露的问题。这些回调事件可能会非常耗时,这时候Ui组件管理这些调用的同时,在UI组件销毁时候还需要清除这些调用。这就造成需要花费更多成本进行维护管理,而且在UI重建时候如configuration change,又需要再次重新调用,造成了很多资源的浪费。
-
Activity的代码臃肿造成了维护和测试的不友好
同时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方法了,这样实现了对其生命周期的控制。
系列文章列表: