ViewModel类被设计为通过lifecycle感知的方式存储和管理UI相关数据。ViewModel类允许数据在配置更改(如屏幕旋转)中保活。
注意:要将ViewModel导入到Android项目中,请参见将组件添加到项目中。
Android框架管理UI控制器的生命周期,如activities和fragments。该框架可以决定销毁或重新创建UI控制器,以响应某些完全超出您控制的用户操作或设备事件。
如果系统销毁或重新创建UI控制器,则在其中存储的任何与UI相关的临时数据都会丢失。例如,您的应用程序可能在一个Activity中包含用户的列表。当配置更改时候activity会重新创建,新Activity必须重新获取用户列表。对于简单数据,该Activity可以使用onSaveInstanceState()方法并从onCreate()中的bundle恢复其数据,但是这种方法仅适用于可以序列化/反序列化的少量数据,而不适用于潜在的大量数据,如用户列表或位图。
另一个问题是UI控制器经常需要进行异步调用,这可能需要一些时间才能返回。UI控制器需要管理这些调用,并确保在控制器销毁的时候,系统能清理它们,以避免潜在的内存泄漏。这种管理需要大量的维护,在配置更改时需要重新创建对象的情况下,这是资源的浪费,因为对象可能必须重新发出它已经发出的调用。
UI控制器(如activities和fragments)主要用于显示UI数据、对用户动作作出反应或处理与操作系统间的通信(如权限请求)。要求UI控制器还负责从数据库或网络加载数据,这些都造成了控制器代码急剧膨胀。将过多的职责分配给UI控制器会导致一个类试图自己处理应用程序的所有工作,而不是将工作委托给其他类。以这种方式向UI控制器分配过度的职责也会使测试变得更加困难。
将视图数据所有权与UI控制器逻辑分离是更容易和更有效的。
架构组件为UI控制器提供ViewModel帮助类,用于负责为UI准备数据。ViewModel对象在配置更改期间自动保留,以便它们保存的数据可立即用于下一个activity或fragment实例。例如,如果需要在app中显示用户列表,请确保将获取和保持用户列表的责任分配给ViewModel,而不是activity或fragment,如下面的示例代码所示:
public class MyViewModel extends ViewModel {
private MutableLiveData> users;
public LiveData> getUsers() {
if (users == null) {
users = new MutableLiveData>();
loadUsers();
}
return users;
}
private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}
然后,可以从以下活动访问列表:
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被重新创建,它将接收由第一个活动创建的相同的MyViewModel实例。当所有者activity finished时,框架调用ViewModel对象的onCleared()方法,以便它可以清理资源。
注意:ViewModel绝不能引用view、Lifecycle或任何可能引用了activity上下文的类
ViewModel对象被设计为Lifecycle超出views或LifecycleOwners的特殊的实例。这个设计还意味着你可以编写测试来更容易地覆盖ViewModel,因为它不知道view和Lifecycle对象。ViewModel对象可以包含LifecycleObservers,如LiveData对象。然而,ViewModel对象绝不可能观察到具有生命周期感知变量(如LiveData对象)的变化。如果ViewModel需要应用程序上下文,例如查找系统服务,那么它可以继承AndroidViewModel类,并具有接收Application的构造函数,因为Application类继承了Context。
在获ViewModel时,ViewModel对象被视为Lifecycle传递给ViewModelProvider。ViewModel一直保留在内存中,直到它的作用域永久消失:在activity的情况下,当它finishes时,而在fragment的情况下,当它被detached时。
图1说明了一个activity的各种生命周期状态,因为它经历了一个旋转,然后finished。该插图还显示了与activity关联的ViewModel的生命周期。这个特殊的图表说明了activity的状态。相同的基本状态适用于fragment的生命周期。
通常,当系统首次调用Activity对象的onCreate()方法时,通常会请求ViewModel。系统可能在活动的整个生命周期中多次调用onCreate(),例如当设备屏幕被旋转时。从你第一次请求ViewModel时,ViewModel一直存在,直到activity完成并销毁。
一个activity中的两个或多个fragments需要相互通信是很常见的。设想一个主细节fragments的常见情况,其中有一个fragment,其中用户从列表中选择项,另一个fragment显示所选择的内容。这种情况从来都是很重要的,因为两个fragments都需要定义一些接口描述,并且所有者activity必须将两者绑定在一起。此外,两个fragment都必须处理其他fragment尚未创建或可见的场景。
可以通过使用ViewModel对象来解决这个共同的疼痛点。这些fragments可以使用其activity范围内的共享ViewModel来处理此通信,如下面的示例代码所示:
public class SharedViewModel extends ViewModel {
private final MutableLiveData- selected = new MutableLiveData
- ();
public void select(Item item) {
selected.setValue(item);
}
public LiveData
- 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时,两个fragments都使用getActivity()。结果,两个fragments都接收到与activity相关的SharedViewModel实例。
这种方法提供了以下好处:
activity不需要做任何事情,也不需要知道关于通信的任何事。
除了SharedViewModel契约之外,Fragments不需要互相了解。如果其中一个fragment消失,另一个像往常一样继续工作。
每个fragment都有自己的生命周期,不受另一个生命周期的影响。如果一个fragment替换了另一个fragment,则UI继续工作而不会出任何问题。
像CursorLoader这样的Loader类经常被用来保持app的UI中的数据与数据库同步。您可以使用ViewModel和其他几个类来替换loader。使用ViewModel将UI控制器与数据加载操作分离,这意味着类之间的强引用更少。
在使用loaders的一种常见方法中,app可能使用CursorLoader来观察数据库的内容。当数据库中的值发生变化时,loader将自动触发数据的重新加载并更新UI:
图2. loaders加载数据
ViewModel使用Room和LiveData来替换loader。ViewModel能确保数据幸存于设备配置更改。当数据库改变时,Room通知您的LiveData,LiveData又用修改后的数据更新UI。
图3. ViewModel加载数据
这篇博客文章描述了如何使用一个LiveData的ViewModel来代替AsyncTaskLoader。
随着数据变得越来越复杂,您可能会选择单独的类来加载数据。ViewModel的目的是封装UI控制器的数据,让数据在配置更改时继续存活。有关如何跨配置更改加载、保存和管理数据的信息,请参阅保存UI状态。
Android应用程序体系结构指南建议建立一个仓库类来处理这些功能。
ViewModel是一个Android Jetpack架构组件。在Sunflower演示应用程序中使用了它。