Android Jetpack - ViewModel

ViewModel Overview

ViewModel 类旨在以生命周期意识的方式存储和管理 UI 相关数据。 ViewModel 类允许数据在配置更改之后(例如屏幕旋转)后继续存在。

Android 框架管理着 UI controller 的生命周期,例如 Activity 和 Fragment ,框架可以决定创建或是重新创建 UI controller 以响应不受您控制的某些用户操作或设备事件。

如果系统销毁或重新创建 UI controller ,你在其中存储的任何的顺势的数据都将会丢失。例如,你在 Activity 中存储了一个 User 列表。当这个 Activity 因为配置变更而重建时,新的 Activity 必须得重新请求用户列表。简单来说, Activity 可以使用 onSaveInstanceState() 方法、并在 onCreate() 中的 bundle 中获取数据,但是这也仅限于小数据量的可以序列化和反序列化的数据,大量的 User 数据或是 Bitmap 就不行了。

另一个问题就是 UI controller 要频繁的进行异步请求,需要时间来等待返回结果。 UI controller 需要管理这些调用,并在系统清理现场的时候避免潜在的内存泄漏。这种管理工作需要维护大量代码,并且在这种情况下数据对象会被重新创建,对于已经调用并创建的对象来说重建他们是一种资源浪费。

诸如 Activity 和 Fragment 这样的 UI controller 主要用于显示 UI 数据,对应互操作进行响应或是处理跟操作系统的交互,例如申请权限。用 UI controller 来负责请求数据库,会让类越来越臃肿。为 UI controller 分配过多的任务可能会导致最后一个类处理了所有的工作,而不是委托给其它类。这样也会更加难以测试代码。

将视图数据与 UI controller 分离开会更简单、更有效。

Implement a ViewModel

应用架构组件提供 ViewModel 辅助类来帮助 UI controller 准备数据。 ViewModel 对象在配置更改期间自动保留,以便它们保存的数据可以立即被下一个 Activity 或 Fragment 使用。例如,你想要在应用中显示用户列表,请确保分配好指责,将用户列表分配给 ViewModel 对象,而不是 Activity 或 Fragment ,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> users;
public LiveData<List<User>> getUsers() {
if (users == null) {
users = new MutableLiveData<List<User>>();
loadUsers();
}
return users;
}

private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
}

在 Actitivty 中可以像这样访问数据:

1
2
3
4
5
6
7
8
9
10
11
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 的时候,他将接受与第一个 Activity 相同的 MyViewModel 实例。当 Activity 结束的时候,框架将会调用 ViewModel 对象的 onCleared() 方法,以便清理资源。

ViewModel 绝对不能引用 ViewLifecycle 或是任何可能包含 Context 引用的类。

ViewModel 对象旨在超越 ViewLifecyclerOwners 的存在。此设计还意味着你可以更容易的编写 ViewModel 的测试代码,因为他无关 ViewLifecycle 对象。 ViewModel 可以包含 LifecyclerObservers 对象,例如 LivaData 对象。然而 ViewModel 对象绝对不能观察生命周期感知的 observables ,例如 LiveData 对象。如果 ViewModel 需要使用 Application context ,例如需要找到一个系统服务,它可以继承 AndroidViewModel 类,构造函数接受一个 Application 示例, Applicetion 集成了 Context

The lifecycle of a ViewModel

获取 ViewModel 时, ViewModel 对象的范围就限定为传递给 ViewModelProvider 的生命周期。 ViewModel 保留在内存中,知道生命周期永久消失。例如 Activity 在 finish 的时候, Fragment detached 的时候。

下图展示了 Activity 经历了屏幕旋转然后 finish 的生命周期状态。还显示了关联了 Activity 生命周期的 ViewModel 的生命周期。此图展示的是 Activity 的情况, Fragment 与此类似。

通常情况下,应该在系统调用 Activity 的 onCreate() 方法时请求 ViewModel。系统可以在 Activity 生命周期多次调用 onCreate() ,例如在设备旋转时。 ViewModel 从第一次请求 ViewModel 到 Activity finish 的时一直存在。

Share data between fragments

Activity 里面两个或者多个 Fragment 系要相互通信是很常见的。想象以下 master-detail 结构的页面,一个 Fragment 显示列表,另一个 Fragment 显示所选项目的内容。这种情况不容小视,因为两个 Fragment 都需要定义一下接口描述,并且 Activity 必须将两者绑定在一起。此外,两个 Fragment 都需要处理没有另一个 Fragment 的时候的情况。

可以使用 ViewModel 对象来解决这个常见的痛点。这些 Fragment 可以使用 Activity 的 ViewModel 来处理通信,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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.
});
}
}

注意到两个 Fragment 在获取 ViewModelProvider 的时候都是用的是 getActivity() 。相应的,两个 Fragment 都拿到了同一个 SHaredViewModel 实例,这个示例是属于 Activity 的。

这个实现具有以下几种优点:

  • Activity 不需要多人喝的事情,也不需要了解任何通信的事情。
  • 除了 SharedViewModel 之外,两个 Fragment 不会相互依赖。没有其中一个 Fragment 另一个也会照常工作。
  • 每个 Fragment 都有自己的生命周期,不受另一个 Fragment 的生命周期影响。如果一个 Fragment 换成了另一个, UI 也会照常工作。

Replacing Loaders with ViewModel

像是 CursorLoader 这样的加载类通常用来将 UI 中的数据与数据库保持同步。你可以使用 ViewModel 和其他类来替换 Loader 。使用 ViewModel 将 UI controller 与数据加载操作粉看,这可以是类之间的强引用减少。

在使用 Loader 时的一种常见做法中,应用程序可能使用 CursorLoader 来观察数据库中的内容。当数据库中的内容发生变更的时候,家在城区会自动重新加载并更新 UI 。

ViewModel 使用 RoomLiveData 一起可以来替换加载器。 ViewModel 可以确保哦数据在配置变更的时候仍然存在。当数据库发生变化时,会通知您的 LiveData ,然后 LiveData 会使用更改后的数据来更新你的 UI 。

Additional resources

这篇文章 描述了怎么使用 ViewModelLiveData 来替换 AsyncTaskLoader

随着你的数据慢慢变得更加复杂,你也许会使用单独的类来来加载数据。使用 ViewModel 的目的是封装 UI controller 的数据,以便可以是数据不受配置更改的影响。有关如何跨配置更改加载,保留和管理数据的信息,请参考 Saving UI States

Guide to Android App Architecture 建议构建一个 Repository 类来管理这些功能。