(译) ViewModel 概览

原文链接

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

注意:要将 ViewModel 导入Android项目,请参阅 Lifecycle发行说明 中有关声明依赖项的说明。

Android 框架管理 UI 控制器 (例如 Activity 和 Fragment ) 的生命周期。框架可以决定销毁或重新创建 UI 控制器以响应某些完全不受你控制的用户操作或设备事件。

如果系统销毁或重新创建 UI 控制器,则存储在其中的任何瞬态 UI 相关数据都将丢失。例如,你的应用可能会在其中一项 Activity 中包含用户列表。为配置更改重新创建活动时,新活动必须重新获取用户列表。对于简单数据,活动可以使用 onSaveInstanceState() 方法并从 onCreate()中的 bundle 中恢复其数据,但此方法仅适用于可以序列化然后反序列化的少量数据,而不适用于潜在的大量数据像用户列表或位图。

另一个问题是 UI 控制器经常需要进行可能需要一些时间才能返回的异步调用。 UI 控制器需要管理这些调用并确保系统在销毁后清理它们以避免潜在的内存泄漏。此管理需要大量维护,并且在为配置更改重新创建对象的情况下,这会浪费资源,因为对象可能必须重新发出已经进行的调用。

诸如 Activity 和 Fragment 之类的 UI 控制器主要用于显示 UI 数据,对用户操作作出反应或处理操作系统通信,例如许可请求。要求 UI 控制器也负责从数据库或网络加载数据,这会使类膨胀。为 UI 控制器分配过多的责任可能导致单个类尝试自己处理应用程序的所有工作,而不是将工作委托给其他类。以这种方式为UI控制器分配过多的责任也会使测试变得更加困难。

将视图数据所有权与UI控制器逻辑分离开来,会使编程变得更容易,更高效。

实现一个ViewModel

架构组件为 UI 控制器提供 ViewModel 帮助程序类,该控制器负责为 UI 准备数据。 ViewModel 对象在配置更改期间自动保留,以便它们保存的数据可立即用于下一个 Activity或 Fragment 实例。例如,如果你需要在应用程序中显示用户列表,请确保将获取和保存用户列表数据的职责分配到 ViewModel,而不是分配给 Activity 或 Fragment,如以下示例代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyViewModel : ViewModel() {
private val users: MutableLiveData<List<User>> by lazy {
MutableLiveData().also {
loadUsers()
}
}

fun getUsers(): LiveData<List<User>> {
return users
}

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

然后,你可以从 Activity 中访问列表,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
class MyActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
// 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.

val model = ViewModelProviders.of(this).get(MyViewModel::class.java)
model.getUsers().observe(this, Observer<List<User>>{ users ->
// update UI
})
}
}

如果重新创建 Activity,它将接收由第一个 Activity 创建的相同 MyViewModel 实例。 当所有者活动完成时,框架调用 ViewModel 对象的 onCleared() 方法,以便它可以清理资源。

警告:ViewModel绝不能引用视图,生命周期或任何可能包含对 Activity context 的引用的类。

ViewModel 对象被设计成比视图或 LifecycleOwners 的特定实例活得长。 此设计还意味着你可以更轻松地编写测试以覆盖 ViewModel,因为不需要了解视图和 Lifecycle 对象。 ViewModel 对象可以包含 LifecycleObservers,例如 LiveData 对象。 但是,ViewModel 对象绝不能观察可感知生命周期的可观察对象(例如LiveData对象)的更改。 如果 ViewModel 需要Application context,例如查找系统服务,它可以扩展 AndroidViewModel 类并具有在构造函数中接收 Application 的构造函数,因为Application 类扩展了Context。

ViewModel 的生命周期

在获取 ViewModel 时,ViewModel 对象被限定到传递给 ViewModelProviderLifecycle 的范围。 ViewModel 保持在内存中,直到 Lifecycle 它的范围(Activity 或 Fragment)永久消失:在 Activity 中,是当它结束的时候,在 Fragment 中,是当它被分离的时候。

图 1 阐释了 Activity 历经转向然后结束时的各种生命周期状态。 该图还阐释了关联Activity 生命周期旁边的 ViewModel 的一生。 这张图阐释了 Activity 的状态。相同的基础状态适用于 Fragment 的生命周期。

你通常在系统第一次调用 Activity 对象的 onCreate() 方法时请求 ViewModel。 系统可以在 Activity 的整个生命周期中多次调用 onCreate() ,例如在旋转设备屏幕时。 ViewModel 从你第一次请求 ViewModel 到 Activity 结束并销毁之时都存在着。

在多个 fragment 之间共享数据

Activity 中的两个或多个 Fragment 需要相互通信是很常见的。 想象一下主从 Fragment 的常见情况,其中一个 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
34
35
36
37
38
39
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()

fun select(item: Item) {
selected.value = item
}
}

class MasterFragment : Fragment() {

private lateinit var itemSelector: Selector

private lateinit var model: SharedViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}

class DetailFragment : Fragment() {

private lateinit var model: SharedViewModel

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
model = activity?.run {
ViewModelProviders.of(this).get(SharedViewModel::class.java)
} ?: throw Exception("Invalid Activity")
model.selected.observe(this, Observer<Item> { item ->
// Update the UI
})
}
}

请注意,两个 Fragment 都会检索包含它们的活动。 这样,当每个 Fragment 都获得ViewModelProvider 时,它们会收到相同的 SharedViewModel 实例,该实例的范围限定为此 activity 。

这种方法具有以下优点:

  • Activity 不需要做任何事情,也不需要了解这种通讯。
  • 除了 SharedViewModel 契约之外,Fragment 不需要彼此了解。 如果其中一个Fragment 消失,另一个 Fragment 继续照常工作。
  • 每个 Fragment 都有自己的生命周期,不受另一个 Fragment 的生命周期的影响。 如果一个 Fragment 被另一个 Fragment 替换,则 UI 继续工作而没有任何问题。

用 ViewModel 取代 Loaders

CursorLoader 这样的 Loader 类经常用于保持应用程序 UI中 的数据与数据库同步。 你可以使用 ViewMode l和其他一些类来替换加载器 。 使用 ViewModel 将 UI 控制器与数据加载操作分开,这意味着类之间的强引用较少。

常见的加载器使用方法是,应用程序可能使用 CursorLoader 来观察数据库的内容。当数据库中的值发生更改时,加载程序会自动触发重新加载数据并更新 UI:

图2. 使用加载器加载数据

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

图3.使用ViewModel加载数据

将协程和 ViewModel 一起使用

ViewModel 包括对 Kotlin 协程的支持。 有关更多信息,请参阅将 Kotlin 协程与 Android 体系结构组件一起使用

更多信息

随着数据变得越来越复杂,你可能会选择单独的类来加载数据。 ViewModel 的目的是封装UI控制器的数据,以使数据不受配置更改的影响。有关如何跨配置更改加载,保留和管理数据的信息,请参阅保存 UI 状态

Android 应用架构指南 建议构建一个存储库类来处理这些功能。

非典型前端coder wechat
想要随时Follow我的最新博客,可扫码关注我的公众号