(译) 使用具有生命周期感知力的组件处理生命周期

原文链接

具有生命周期感知力的组件执行操作以响应另一个组件(例如 Activity 和 Fragment )的生命周期状态的变化。这些组件可帮助你生成更易于组织且通常更轻量级的代码,这些代码更易于维护。

常见模式是在 Actvity 和 Fragment 的生命周期方法中实现依赖组件的操作。但是,这种模式导致代码组织不良和错误扩散。通过使用具有生命周期感知力的组件,你可以将依赖组件的代码移出生命周期方法并移入组件本身。

androidx.lifecycle 包提供了类和接口,使你可以构建具有生命周期感知力的组件 - 这些组件可以根据 Activity 或 Fragment 的当前生命周期状态自动调整其行为。

注意:要将 androidx.lifecycle 导入 Android 项目,请参阅 Lifecycle release 说明 中有关声明依赖项的说明。

Android 框架中定义的大多数应用程序组件都附加了生命周期。生命周期由操作系统或流程中运行的框架代码管理。它们是 Android 运作方式的核心,你的应用程序必须重视它们。不这样做可能会触发内存泄漏甚至应用程序崩溃。

想象一下,我们有一个 Activity ,在屏幕上显示设备位置。常见的实现可能如下所示:

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
internal class MyLocationListener(
private val context: Context,
private val callback: (Location) -> Unit
) {

fun start() {
// 连接系统定位服务
}

fun stop() {
// 与系统定位服务断开
}
}

class MyActivity : AppCompatActivity() {
private lateinit var myLocationListener: MyLocationListener

override fun onCreate(...) {
myLocationListener = MyLocationListener(this) { location ->
// 更新 UI
}
}

public override fun onStart() {
super.onStart()
myLocationListener.start()
// 管理其它需要响应 activity 生命周期的组件
}

public override fun onStop() {
super.onStop()
myLocationListener.stop()
// 管理其它需要响应 activity 生命周期的组件
}
}

即使这个示例看起来很好,但在真实的应用程序中,最终会有太多的调用来管理 UI 和 其他组件以响应生命周期的当前状态。管理多个组件会在诸如onStart()和 onStop()生命周期方法中放置大量代码,这使得它们难以维护。

此外,无法保证组件在 Activity 或 Fragment 停止之前启动。如果我们需要执行长时间运行的操作,例如 onStart() 中的某些配置检查,则尤其如此。这可能会导致 onStop() 方法在 onStart() 之前完成的竞争条件,从而使组件存活的时间超过其所需的时间。

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
class MyActivity : AppCompatActivity() {
private lateinit var myLocationListener: MyLocationListener

override fun onCreate(...) {
myLocationListener = MyLocationListener(this) { location ->
// 更新 UI
}
}

public override fun onStart() {
super.onStart()
Util.checkUserStatus { result ->
// 要是这个回调是在 activty stopped 之后被调用的呢
if (result) {
myLocationListener.start()
}
}
}

public override fun onStop() {
super.onStop()
myLocationListener.stop()
}

}

androidx.lifecycle 包提供了类和接口,可帮助你以弹性和隔离的方式解决这些问题。

Lifecycle

Lifecycle 是这么一个类,它包含有关组件(Activity 或 Fragment)生命周期状态的信息,并允许其它对象观察此状态。

Lifecycle 主要使用两个枚举类型来跟踪其关联组件的生命周期状态:

Event

Lifecycle 事件从框架和 Lifecycle类发出。这些事件映射到 Activity 和 Fragment 中的回调事件。

State

组件当前的状态由 Lifecycle 对象跟踪。

将状态视为图的节点,将事件视为这些节点之间的线。

类可以通过向其方法添加注解来监视组件的生命周期状态。然后,你可以通过调用 Lifecycle 类的 addObserver() 方法并传递观察者的实例来添加观察者,如以下示例所示

1
2
3
4
5
6
7
8
9
10
11
12
class MyObserver : LifecycleObserver {

@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun connectListener() {
...
}

@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun disconnectListener() {
...
}
}

在上面的示例中,myLifecycleOwner 对象实现了 LifecycleOwner 接口,在下一节中进行了说明。

LifecycleOwner

LifecycleOwner 是一个单一方法接口,表示该类有着一个 Lifecycle。它有一个方法getLifecycle() ,它必须由类实现。如果你正尝试管理整个应用程序进程的生命周期,请参阅ProcessLifecycleOwner

此接口从各个类(如 Fragment 和 AppCompatActivity)中抽象出一个 Lifecycle的所有权,并允许编写与其一起使用的组件。任何自定义应用程序类都可以实现 LifecycleOwner 接口。

实现 LifecycleObserver 的组件与实现 LifecycleOwner 的组件无缝协作,因为所有者可以提供一个 lifeCycle,观察者可以注册观察这个 lifeCycle。

对于位置跟踪示例,我们可以使 MyLocationListener 类实现 LifecycleObserver ,然后在 onCreate() 方法中使用 activity 的 Lifecycle 初始化它。这允许MyLocationListener 类自给自足,这意味着响应生命周期状态变化的逻辑在MyLocationListener 而不是 activity 中声明。让各个组件存储自己的逻辑使得Activity 和 Fragment 逻辑更易于管理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyActivity : AppCompatActivity() {
private lateinit var myLocationListener: MyLocationListener

override fun onCreate(...) {
myLocationListener = MyLocationListener(this, lifecycle) { location ->
// 更新 UI
}
Util.checkUserStatus { result ->
if (result) {
myLocationListener.enable()
}
}
}
}

一个常见的用例是,如果生命周期当前不处于良好状态,则应避免调用某些回调。例如,如果回调在保存 activity 状态后运行 fragment transaction,则会触发崩溃,因此我们永远不会想要调用该回调。

为了简化此用例,Lifecycle 类允许其他对象查询当前状态。

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
internal class MyLocationListener(
private val context: Context,
private val lifecycle: Lifecycle,
private val callback: (Location) -> Unit
) {

private var enabled = false

@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun start() {
if (enabled) {
// 连接
}
}

fun enable() {
enabled = true
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// 如果没有连接择连接
}
}

@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() {
// 如果连接了则断开
}
}

通过此实现,我们的 LocationListener 类已经具有完全的生命周期感知能力了。如果我们需要使用来自另一个 Activity 或 Fragment 的 LocationListener,我们只需要初始化它。所有设置和拆卸操作都由类本身管理。

如果一个库的类需要对 Android 生命周期进行管理,我们建议你使用具有生命周期感知力的组件。你的库可以轻松地集成这些组件,而无需在客户端进行手动生命周期管理。

实现一个自定义的 LifecycleOwner

支持库 26.1.0 及更高版本中的 fragment 和 activity 已实现 LifecycleOwner 接口。

如果你有一个想其成为 LifecycleOwner 的自定义类,则可以使用 LifecycleRegistry类,但需要将事件转发到该类中,如以下代码示例所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MyActivity : Activity(), LifecycleOwner {

private lateinit var lifecycleRegistry: LifecycleRegistry

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

lifecycleRegistry = LifecycleRegistry(this)
lifecycleRegistry.markState(Lifecycle.State.CREATED)
}

public override fun onStart() {
super.onStart()
lifecycleRegistry.markState(Lifecycle.State.STARTED)
}

override fun getLifecycle(): Lifecycle {
return lifecycleRegistry
}
}

具有生命周期感知力的组件的最佳实践

  • 保持 UI 控制器( Activity 和 Fragment )尽可能精简。它们不应该试图获取自己的数据;相反,使用 ViewModel 执行此操作,并观察 LiveData 对象以将变化反映回视图。
  • 尝试编写数据驱动的 UI,其中 UI 控制器负责在数据变化时更新视图,或将用户操作通知给 ViewModel
  • 将你的数据逻辑放在 ViewModel 类中。ViewModel 应该充当 UI 控制器和应用程序其余部分之间的连接器。但要小心,ViewModel不负责获取数据(例如,从网络获取)。相反,ViewModel 应调用适当的组件来获取数据,然后将结果提供回 UI 控制器。
  • 使用 Data Binding 来维护视图和 UI 控制器之间的整洁接口。这使你可以使视图更具声明性,并最大限度地减少在 Activity 和 Fragment 中编写所需的更新代码。如果你更喜欢用 Java 编程语言执行此操作,请使用像 Butter Knife 这样的库来避免样板代码并具有更好的抽象。
  • 如果你的 UI 很复杂,请考虑创建一个 presenter 类来处理 UI 修改。这可能是一项艰巨的任务,但它可以使你的 UI 组件更容易测试。
  • 避免在 ViewModel 中引用 View 或 Activity context 。如果 ViewModel 比 activity 存活的时间长(在配置更改的情况下),你的 activity 将泄漏,并且没有被垃圾回收正确处理。
  • 使用 Kotlin 协程 来管理长时间运行的任务以及可以异步运行的其它操作。

具有生命周期感知能力的组件的用例

具有生命周期感知能力的组件可以使你在各种情况下更轻松地管理生命周期。以下是一些例子:

  • 在粗粒度和细粒度位置更新之间切换。使用具有生命周期感知能力的组件可在你的位置应用程序可见时启用细粒度位置更新,并在应用程序位于后台时切换到粗粒度更新。 LiveData 是一个具有生命周期感知能力的组件,允许你的应用在用户更改位置时自动更新 UI。
  • 停止并开始视频缓冲。具有生命周期感知能力的组件尽快启动视频缓冲,但推迟播放直到应用程序完全启动。你还可以使用具有生命周期感知能力的组件在销毁应用程序时终止缓冲。
  • 启动和停止网络连接。使用具有生命周期感知能力的组件在应用程序处于前台时启用网络数据的实时更新(流式传输),并在应用程序进入后台时自动暂停。
  • 暂停和恢复动画。当 app 在后台时使用具有生命周期感知能力的组件处理暂停动画,并在 app 在前台后恢复。

处理 stop 事件

当一个 Lifecycle 属于 AppCompatActivityFragment 时,Lifecycle 的状态将变跟为 CREATED,并且 ON_STOP 事件在调用 AppCompatActivity 或 Fragment 的onSaveInstanceState() 时被发出来。

FragmentAppCompatActivity 的状态通过 onSaveInstanceState() 被保存时,直到 ON_START 被调用之前它们的UI都是不可以修改的。保存状态后尝试修改 UI 可能会导致应用程序的导航状态不一致,这就是如果应用程序在保存状态后运行 FragmentTransaction 时,FragmentManager 会抛出异常的原因。有关详细信息,请参阅 commit()

在观察者相关的 LifeCycle 连至少的 STARTED 都没有达到时,LiveData 会通过禁止调用其观察者来防止这种极端情况发生。背后的原理是,它在决定调用其观察者之前先调用 isAtLeast()

不幸的是,AppCompatActivity 的 onStop() 是在 onSaveInstanceState() 之后调用方法,这就留下了一个间隙,其中不允许 UI 状态更改但 Lifecycle 尚未移至 CREATED 状态。

为防止出现此问题,版本 beta2 及更低版本的 LifeCycle 类将状态标记为 CREATED,而不发送事件,以便检查当前状态的任何代码都能获得实际值,即使事件在 onStop() 被系统调用之前都没有被发送。

不幸的是,这个解决方案主要有两个问题

  • 在API级别 23 和更低级别,Android系统实际上保存了 Activity 的状态,即使它被另一个 Activity 部分覆盖。换句话说,Android系统调用 onSaveInstanceState() 但它不一定调用 onStop()。这会创建一个可能很长的时间间隔,即使无法修改其 UI 状态,观察者仍然认为生命周期处于活动状态。

任何想要向 LiveData 类暴露类似行为的类都必须实现 LifeCycle 版本 beta 2 及更低版本提供的解决方法。

注意:为了使此流更简单并提供与旧版本的更好兼容性,从版本 1.0.0-rc1 开始,Lifecycle 对象被标记为 CREATED,并且在调用 onSaveInstanceState() 时发送ON_STOP,而不等待对onStop()方法的调用。这不太可能影响你的代码,但你需要注意这一点,因为它与 API 级别 26 及更低级别的 Activity 类中的调用顺序不匹配。

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