Lifecycle-aware 组件执行操作以响应另一个组件(例如 activity 和 fragment)生命周期状态的更改。 这些组件可帮助你生成组织更好、通常更轻、更易于维护的代码。
一种常见的模式是在 activity 和 fragment 的生命周期方法中实现依赖组件的操作。 然而,这种模式会导致代码组织不良并导致错误激增。
通过使用 lifecycle-aware 组件,可以将依赖组件的代码从生命周期方法移至组件本身。
androidx.lifecycle
包提供了类和接口,可让你构建 lifecycle-aware 组件,这些组件可以根据 Activity 或 Fragment 的当前生命周期状态自动调整其行为。
Android 框架中定义的大多数 app 组件都附加了生命周期。 生命周期由操作系统或进程中运行的 framework 代码管理。
它们是 Android 工作方式的核心,你的应用程序必须尊重它们。 不这样做可能会引发内存泄漏甚至应用程序崩溃。
Lifecycle
Lifecycle
是一个类,它保存有关组件(如 activity 或 fragment)生命周期状态的信息,并允许其他对象观察此状态。
Lifecycle
使用两个主要枚举来跟踪其关联组件的生命周期状态:
Event
从 framework 和 Lifecycle
类调度的生命周期事件。 这些事件映射到 activity 和 fragment 中的回调事件。
State
Lifecycle
对象跟踪的组件的当前状态。
构成 Android Activity 生命周期的状态和事件
类可以通过实现 DefaultLifecycleObserver
并重写相应的方法(如 onCreate
、onStart
等)来监视组件的生命周期状态。
然后,可以通过调用 Lifecycle
类的 addObserver()
方法并传递 observer 的实例来添加 observer:
1 | class MyObserver : DefaultLifecycleObserver { |
LifecycleOwner
LifecycleOwner
是一个单一方法接口,表示该类具有 Lifecycle
。 它有一个方法 getLifecycle()
,该方法必须由类实现。
如果你尝试管理整个应用程序进程的生命周期,请参阅 ProcessLifecycleOwner
。
该接口从各个类(例如 Fragment
和 AppCompatActivity
)中抽象出生命周期的所有权,并允许编写与它们一起使用的组件。 任何自定义应用程序类都可以实现 LifecycleOwner
接口。
实现 DefaultLifecycleObserver
的组件与实现 LifecycleOwner
的组件无缝协作,因为 owner 可以提供生命周期,observer 可以注册来监视该生命周期。
对于位置跟踪示例,我们可以使 MyLocationListener
类实现 DefaultLifecycleObserver
,然后在 Activity.onCreate()
方法中使用 activity 的 Lifecycle
对其进行初始化。
这使得 MyLocationListener
类能够自给自足,这意味着对生命周期状态变化做出反应的逻辑是在 MyLocationListener
而不是 Activity 中声明的。
让各个组件存储自己的逻辑使得 activity 和 fragment 逻辑更易于管理。
一个常见的用例是,如果 Lifecycle
当前状态不佳,则避免调用某些回调。 例如,如果回调在保存 activity 状态后运行 fragment 事务,它将触发崩溃,因此我们永远不想调用该回调。
为了使这个用例变得简单,Lifecycle
类允许其他对象查询当前状态。
1 | internal class MyLocationListener( |
通过此实现,我们的 LocationListener
类完全具有生命周期感知能力。 如果我们需要从另一个 activity 或 fragment 使用我们的 LocationListener
,我们只需要初始化它。
所有的设置和拆卸操作都由类本身管理。
如果 library 提供需要与 Android 生命周期配合使用的类,我们建议使用生命周期感知组件。 你的 library 客户可以轻松集成这些组件,而无需在客户端进行手动生命周期管理。
Implementing a custom LifecycleOwner
支持库 26.1.0 及更高版本中的 activity 和 fragment 已实现 LifecycleOwner
接口。
如果有一个想要创建 LifecycleOwner
的自定义类,则可以使用 LifecycleRegistry
类,但需要将事件转发到该类中,如以下代码示例所示:
1 | class MyActivity : Activity(), LifecycleOwner { |
最佳实践 for lifecycle-aware components
- 使 UI 控制器(activity 和 fragment)尽可能精简。 他们不应该试图获取自己的数据; 相反,使用
ViewModel
来执行此操作,并观察LiveData
对象以将更改反映回 view。 - 尝试编写数据驱动的 UI,其中 UI 控制器的职责是在数据更改时更新 view,或将用户操作通知回
ViewModel
。 - 将数据逻辑放入
ViewModel
类中。ViewModel
应该充当 UI 控制器和应用程序其余部分之间的连接器。 但要小心,ViewModel
不负责获取数据(例如,从网络)。 相反,ViewModel
应该调用适当的组件来获取数据,然后将结果提供回 UI 控制器。 - 使用 Data Binding 来维护 view 和 UI 控制器之间的干净界面。 这使您可以使 view 更具声明性,并最大限度地减少需要在 activity 和 fragment 中编写的更新代码。
- 如果你的 UI 很复杂,请考虑创建一个 Presenter 类来处理 UI 修改。 这可能是一项艰巨的任务,但它可以使你的 UI 组件更易于测试。
- 避免在
ViewModel
中引用View
或Activity
context。 如果ViewModel
比Activity
的寿命更长(在配置更改的情况下),你的 Activity 就会泄漏并且不会被垃圾收集器正确处理。 - 使用 Kotlin 协程来管理长时间运行的任务和其他可以异步运行的操作。
用例 for lifecycle-aware components
生命周期感知组件可以让你在各种情况下更轻松地管理生命周期。 一些例子是:
- 在粗粒度和细粒度位置更新之间切换。 使用生命周期感知组件在位置应用程序可见时启用细粒度位置更新,并在应用程序处于后台时切换到粗粒度更新。
LiveData
是一个生命周期感知组件,允许你的应用程序在用户更改位置时自动更新 UI。 - 停止和启动视频缓冲。 使用生命周期感知组件尽快启动视频缓冲,但推迟播放,直到应用程序完全启动。 还可以使用生命周期感知组件在应用程序被销毁时终止缓冲。
- 启动和停止网络连接。 使用生命周期感知组件可以在应用程序位于前台时实时更新(流式传输)网络数据,并在应用程序进入后台时自动暂停。
- 暂停和恢复动画绘图。 使用生命周期感知组件来处理应用程序位于后台时暂停的动画可绘制对象,并在应用程序位于前台后恢复可绘制对象。
Handling on stop events
当 Lifecycle
属于 AppCompatActivity
或 Fragment
时,在调用 AppCompatActivity
或 Fragment
的 onSaveInstanceState()
时,Lifecycle
的状态将更改为 CREATED
,并且调度 ON_STOP
事件。
当 Fragment
或 AppCompatActivity
的状态通过 onSaveInstanceState()
保存时,它的 UI 被认为是不可变的,直到调用 ON_START
为止。
保存状态后尝试修改 UI 可能会导致应用程序的导航状态不一致,这就是为什么如果应用程序在保存状态后运行 FragmentTransaction
,FragmentManager
会引发异常。
如果 observer 的关联 Lifecycle
至少为 STARTED
,LiveData
会避免调用其 observer,从而防止出现这种边缘情况。 在幕后,它在决定调用其 observer 之前调用 isAtLeast()
。
不幸的是,AppCompatActivity
的 onStop()
方法是在 onSaveInstanceState()
之后调用的,这留下了一个间隙,即不允许 UI 状态更改,但 Lifecycle
尚未移至 CREATED
状态。
为了防止此问题,beta2
及更低版本中的 Lifecycle
类将状态标记为 CREATED
,而不分派事件,以便检查当前状态的任何代码都会获取实际值即使事件没被分发,直到系统调用 onStop()
。
不幸的是,这个解决方案有两个主要问题:
- 在 API 级别 23 及更低级别上,Android 系统实际上会保存某个 Activity 的状态,即使该 Activity 部分被另一个 Activity 覆盖。 换句话说,Android系统会调用onSaveInstanceState(),但不一定会调用onStop()。 这会产生一个潜在的较长间隔,观察者仍然认为生命周期处于活动状态,即使其 UI 状态无法修改。
- 任何想要向
LiveData
类公开类似行为的类都必须实现Lifecycle
版本 beta 2 及更低版本提供的解决方法。
注意:为了使此流程更简单并提供与旧版本更好的兼容性,从版本 1.0.0-rc1 开始,Lifecycle
对象被标记为 CREATED
,并且在调用 onSaveInstanceState()
时调度 ON_STOP
,而无需等待调用 onStop()
方法。 这不太可能影响您的代码,但您需要注意这一点,因为它与 API 级别 26 及更低级别的 Activity 类中的调用顺序不匹配。
LifecycleRegistry
LifecycleRegistry
是 可以处理多个 observer 的 Lifecycle
实现。
它由 fragment 和支持库 activity 使用。 如果有自定义的 LifecycleOwner
,也可以直接使用它。
ViewModel
ViewModel
类是业务逻辑或屏幕级状态持有者。 它将状态暴露给 UI 并封装相关的业务逻辑。 它的主要优点是它可以缓存状态并通过配置更改保留它。 这意味着你的 UI 在 Activity 之间导航或配置更改(例如旋转屏幕时)时不必再次获取数据。
ViewModel 的优点
ViewModel 的替代方案是一个普通类,它保存你在 UI 中显示的数据。 在 activity 或 Navigation destinations 之间导航时,这可能会成为问题。
如果不使用保存实例状态机制来存储数据,那么这样做会破坏该数据。 ViewModel 提供了一个方便的 API 来实现数据持久化,解决了这个问题。
ViewModel 类的主要优点主要有两个:
- 它允许你保留 UI 状态。
- 它提供对业务逻辑的访问。
Persistence
ViewModel 允许通过 ViewModel
持有的状态和 ViewModel
触发的操作进行持久化。 这种缓存意味着你不必通过常见的配置更改(例如屏幕旋转)再次获取数据。
Scope
当你实例化 ViewModel 时,你向其传递一个实现 ViewModelStoreOwner
接口的对象。 这可以是 Navigation destination, Navigation graph, activity, fragment 或实现该接口的任何其他类型。
然后,你的 ViewModel
的范围限定为 ViewModelStoreOwner
的生命周期。 它保留在内存中,直到其 ViewModelStoreOwner
永久消失。
一系列类是 ViewModelStoreOwner
接口的直接或间接子类。 直接子类是 ComponentActivity
、Fragment
和 NavBackStackEntry
。 有关间接子类的完整列表,请参阅 ViewModelStoreOwner
reference。
当 ViewModel 作用域内的 fragment or activity 被销毁时,异步工作将在其作用域内的 ViewModel 中继续。 这是 persistence 的关键。
SavedStateHandle
SaveStateHandle 不仅允许你通过配置更改来保存数据,还可以跨进程重新创建来保存数据。 也就是说,即使用户关闭应用程序并稍后打开它,它也使您能够保持 UI 状态不变。
Access to business logic
尽管绝大多数业务逻辑存在于数据层中,但 UI 层也可以包含业务逻辑。 当组合来自多个存储库的数据来创建屏幕 UI 状态时,或者当特定类型的数据不需要数据层时,可能会出现这种情况。
ViewModel 是 UI 层中处理业务逻辑的正确位置。 当需要应用业务逻辑来修改应用程序数据时,ViewModel 还负责处理事件并将它们委托给层次结构的其他层。
Implement a ViewModel
1 | data class DiceUiState( |
1 | class DiceRollActivity : AppCompatActivity() { |
The lifecycle of a ViewModel
ViewModel
的生命周期与其范围直接相关。 ViewModel
保留在内存中,直到其作用域的 ViewModelStoreOwner
消失。 这可能发生在以下情况:
- 对于 activity 来说,当它 finish 时。
- 如果是 fragment,当它 detach 时。
- 对于 Navigation entry,当它从返回堆栈中删除时。
通常会在系统第一次调用 Activity 对象的 onCreate()
方法时请求 ViewModel
。 在 Activity 存在期间,系统可能会多次调用 onCreate()
,例如当设备屏幕旋转时。ViewModel
从第一次请求 ViewModel
时一直存在,直到 Activity finish 并销毁。
Clearing ViewModel dependencies
当 ViewModelStoreOwner
在其生命周期过程中销毁 ViewModel 时,ViewModel 会调用 onCleared
方法。 这允许你清理 ViewModel 生命周期之后的任何工作或依赖项。
以下示例显示了 viewModelScope
的替代方案。 viewModelScope
是一个内置的 CoroutineScope
,它自动遵循 ViewModel 的生命周期。 ViewModel 使用它来触发业务相关的操作。
如果你想使用自定义作用域而不是 viewModelScope
来简化测试,ViewModel 可以在其构造函数中接收 CoroutineScope
作为依赖项。 当 ViewModelStoreOwner
在其生命周期结束时清除 ViewModel 时,ViewModel 也会取消 CoroutineScope
。
从 lifecycle 版本 2.5 及更高版本开始,可以将一个或多个 Closeable 对象传递给 ViewModel 的构造函数,该构造函数会在清除 ViewModel 实例时自动关闭。
Best practices
- 由于其范围,请使用 ViewModel 作为屏幕级状态持有者的实现细节。 不要将它们用作可重用 UI 组件(例如芯片组或表单)的状态持有者。 否则,你将在同一 ViewModelStoreOwner 下以不同方式使用同一 UI 组件时获得相同的 ViewModel 实例。
- ViewModel 不应该了解 UI 实现细节。 使 ViewModel API 公开的方法名称和 UI 状态字段的名称尽可能通用。 通过这种方式,您的 ViewModel 可以适应任何类型的 UI:手机、可折叠手机、平板电脑,甚至是 Chromebook!
- 由于它们的生命周期可能比 ViewModelStoreOwner 更长,因此 ViewModel 不应保留任何与生命周期相关的 API(例如 Context 或 Resources)的引用,以防止内存泄漏。
- 不要将 ViewModel 传递给其他类、函数或其他 UI 组件。 因为 platform 管理它们,所以应该让它们尽可能靠近 platform。 接近你的 Activity、fragment 或屏幕级可组合函数。 这可以防止较低级别的组件访问超出其需要的数据和逻辑。