Android Architecture Components介绍之ViewModel的使用详解
ViewModel是一个用生命周期的方式来储存和管理UI数据的类。它被允许在生命周期发生改变时存活,比如屏幕旋转时Activity生命周期的变化。
Android 框架管理UI controller的生命周期。例如:Activity和Fragment。这个框架会根据用户的某些行为和设备的事件来决定销毁或创建UI controller,这些不需要我们实现都是次框架来实现。
如果系统销毁或创建UI controller,所有储存的与UI相关的数据会暂时丢失。比如:在一个Activity中包含一个用户的表单数据。当Activity为改变配置重新创建时(屏幕选装为例),新建的Activity会重新获取用户的数据。对于简单的数据可以保存在onSaveInstanceState()方法并在onCreate()中通过bundle获取数据,但是这种方式只适合少量数据的序列化和反序列化,对于大量的表单数据或者图片不适用。
另一个问题是UI controller频繁的通过异步回调访问数据不能及时的返回。UI controller需要管理这些数据确保系统在destroyed,避免内存溢出。这就需要大量的管理与维护,在对象被重新创建以进行配置更改的情况下,对象会被重复调用,这就造成了资源浪费。
在Activity和Fragment中UI controller主要显示UI,响应用户的动作,或者和系统进行通信,例如权限的动态请求。此外UI controller还要从数据库或者网络加载数据,这就造成了类很臃肿。将过多的责任分配给UI控制器可能会导致单个类试图单独处理所有应用程序的工作,而不是将工作委托给其他类。以这种方式给UI控制器分配过多的责任也会使测试变得更加困难。
ViewModel会将View数据所有权从UI控制器中分离出来使APP更容易维护,也更高效。
实现一个ViewModel
ViewModel负责为UI准备数据。它在配置改变时为下一个Activity或者Fragment自动的保存数据。比如:如果要显示一个用户的表单,确保为你的表单使用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 asynchronous operation to fetch users.
}
}
Activity中的使用:
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只能接受第一个Activity创建的实例。当我们的Activity销毁掉,框架将会调用ViewModel对象的onCleared()方法清除资源。
PS:ViewModel绝对不能引用View、Lifecycle或任何可能包含对Activity上下文的引用的类
ViewModel对象的生命周期比View和LifecycleOwner更长。这种设计便于测试,因为他和View,Lifecycle对象没有关联。ViewModel可以包含LifecycleObserver对象,比如LiveData。ViewModel不能观察observable生命周期感知的变化。如果ViewModel需要Application的Context(比如:使用系统的服务),需要继承AndroidViewModel类,并在构造中接受Application(因为Application继承自Context)
。
ViewModel的生命周期
当我们获取到ViewModel时,ViewModel对象通过ViewModelProvider作用给Lifecycle。View在Lifecycle作用域消失之前会一直保存在内存中:(比如Activity销毁之前,Fragment被detache之前)。
图1:Activity经历一次旋转被销毁之前的各种生命周期状态。下图在展示了Activity生命周期旁边关联了ViewModel整个的寿命
下图是Activity与ViewModel的关联,Fragment与ViewModel同样
你通常在系统回调Activity的onCreat()方法时请求ViewModel,系统可以在活动的整个生命周期中多次调用onCreate(),比如当设备屏幕被旋转时。ViewModel存在于您第一次请求ViewModel时,直到Activity完成并销毁为止。
Fragment之间共享数据
通常在一个Activity中会有两个或者两个以上的Fragment进行通讯。举例:其中有一个Fragment,用户从一个列表中选择一个item,另一个Fragment显示所选item的内容。两个Fragment需要定义同一个接口,物主Activity必须将他们绑定在一起。这两个Fragment都必须处理另一个Fragment是否创建或者是否可见。
这个问题可以通过ViewModel去解决。这些Fragment可以使用它们的Activity作用域共享一个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.
});
}
}
PS:获取ViewModelProvider实例时,需要使用两个Fragment的的getActivity()因此,这样两个Fragment都在同一个Activity的作用域上,所以他们可以接受到相同的SharedViewModel实例。
这种方式的有点:
1.>Activity不需要做任何事情,也不需要知道任何相关的通讯
2.>Fragment 除了SharedViewModel契约,其他的不需要彼此了解。一个Fragment被销毁了另一个还可以正常工作。
3.>每个Fragment都有自己的生命周期,并且不受另一个生命周期的影响。如果一个Fragment替换了另一个Fragment,UI可以不受影响并继续工作。
ViewModel替代Loader
CursorLoader这样的类Loader,经常被用于保持UI数据与数据库的同步。你可以使用ViewModel,以及一些其他的类来替换装载机。ViewModel分离Controller和数据加载的操作极大的减少了类之间的强引用。
Loader的一个常用用法,使用CursorLoader,观察数据库的内容。当数据库的值发生改变Loader自动触发重新加载数据和跟新UI.
图2:loader加载数据
VIewModel的工作就是为了替代Room和LivaData替换加载器。ViewModel确保设备配置发生改变数据得以保存。当数据库发生改变Room会通知LiveData,LiveData会反过来给修改后的数据更新UI。
图3:loader加载数据
本文介绍了怎么使用ViewModel和LiveData去替换AsyncTaskLoader。
随着你的数据变得越来越复杂,我们会选择抽取单独管理数据。ViewModel的目的是封装UI控制器的数据,以便在配置更改时保存数据不丢失。有关如何加载,保存,和管理数据,请参阅Saving UI States
Guide to Android App Architecture 建议构建一个存储库类来处理这些函数。
官网地址:https://developer.android.google.cn/topic/libraries/architecture/viewmodel
推荐阅读