Android Jetpack 学习笔记之 Lifecycle

本文介绍 LifecycleOwner 的主要概念和基本用法。

ProcessLifecycleOwner

先来看看 ProcessLifecycleOwner

为什么先说这个类呢?相信大家可能都有开发这样的一个常见需求:监听当前应用前后台切换,并对此进行响应。这个功能并不复杂,实现 Application.ActivityLifecycleCallbacks 接口即可。问题在于,在 Android 上并没有标准的方法来判断是应用是处于前台还是后台,即使使用 ActivityLifecycleCallbacks 接口,多多少少有些 trick 在其中,所以大部分代码写得并不优雅易读。

ProcessLifecycleOwner,可以被视为 Android 上一个比较标准的监听应用前后台切换的方式。老习惯,先翻译一遍文档

这个类提供整个应用进程的生命周期。
你可以将这个 LifecycleOwner 作为全体 Activity 的 LifecycleOwner,除了 Lifecycle.Event.ON_CREATE 事件只会分发一次,而 Lifecycle.Event.ON_DESTROY 永远不被分发。其他事件的分发遵守以下规则:
ProcessLifecycleOwner 会在第一个 Activity 经历 Lifecycle.Event.ON_STARTLifecycle.Event.ON_RESUME 事件时分发这些事件。 ProcessLifecycleOwner 会在最后一个 Activity 经历 Lifecycle.Event.ON_PAUSELifecycle.Event.ON_STOP 事件 延迟一段时间 分发这些事件。这个延时足够长以确保 ProcessLifecycleOwner 不会在 Activity 由于配置变更而销毁重建期间发送任何事件。
这个类非常适用于对 app 前后台状态切换时进行响应、且对生命周期不要求毫秒级准确性的场景。

再来看看 ProcessLifecycleOwner 的用法。

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 ForegroundMonitorActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_foreground_monitor)

AppLifecycleObserver.init(application)

buttonStartMonitor.setOnClickListener {
ProcessLifecycleOwner.get().lifecycle.addObserver(AppLifecycleObserver)
}

buttonStopMonitor.setOnClickListener {
ProcessLifecycleOwner.get().lifecycle.removeObserver(AppLifecycleObserver)
}
}
}

@SuppressLint("StaticFieldLeak")
object AppLifecycleObserver : LifecycleObserver {

lateinit var context : Context

fun init(context: Context) {
this.context = context
}

@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun foreground() {
Log.i("AppLifecycleObserver", "goto foreground")
Toast.makeText(context, "goto foreground", Toast.LENGTH_SHORT).show()
}

@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun background() {
Log.i("AppLifecycleObserver", "goto background")
Toast.makeText(context, "goto background", Toast.LENGTH_SHORT).show()
}
}

这段代码使用 ProcessLifecycleOwner 很优雅地实现了如下功能:

  • 应用切换到前台时,打印日志并弹出 toast “goto foreground”
  • 应用切换到后台时,打印日志并弹出 toast “goto background”

Lifecycle 库

Lifecycle 的优势

lifecycle 组件的意义在于更容易组织代码。写过 Android 代码的都知道,常常需要在 Activity/Fragment 的生命周期回调中执行一些操作。比如说:

  • onCreate() 中初始化某些资源
  • onDestory() 中释放某些资源

所以 Activity/Fragment 的代码很容易就越写越多。

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
40
class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;

public void onCreate(...) {
myLocationListener = new MyLocationListener(this, location -> {
// update UI
});
}

@Override
public void onStart() {
super.onStart();
Util.checkUserStatus(result -> {
// what if this callback is invoked AFTER activity is stopped?
if (result) {
myLocationListener.start();
}
});
}

@Override
public void onStop() {
super.onStop();
myLocationListener.stop();
}
}

class MyLocationListener {
public MyLocationListener(Context context, Callback callback) {
// ...
}

void start() {
// connect to system location service
}

void stop() {
// disconnect from system location service
}
}

Activity/Fragment 代码多本身并不是问题,真正的问题在于正确性和可测试性。以上述代码为例,它就不能保证正确性和可测试性。

  • 先说正确性。如果 Util.checkUserStatus() 是一个耗时的异步操作,等到真正调用到 myLocationListener.start() 时可能 Activity 已经处于停止状态,所以无法保证正确性
  • 再说可测试性。写在 Activity/Fragment 中的代码基本是无法做单元测试的。

来看 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
28
29
30
31
32
33
34
35
36
37
38
39
40
class MyActivity extends AppCompatActivity {
private MyLocationListener myLocationListener;

public void onCreate(...) {
myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
// update UI
});
Util.checkUserStatus(result -> {
if (result) {
myLocationListener.enable();
}
});
}
}

class MyLocationListener implements LifecycleObserver {
private boolean enabled = false;
public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
...
}

@OnLifecycleEvent(Lifecycle.Event.ON_START)
void start() {
if (enabled) {
// connect
}
}

public void enable() {
enabled = true;
if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
// connect if not connected
}
}

@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
void stop() {
// disconnect if connected
}
}

修改后的代码有这几个好处:

  • MyLocationListener 可感知生命周期,将生命周期的响应与 Activity 解耦
  • MyLocationListener 易于测试
  • MyLocationListener 可以避免在不正确的生命周期时进行回调

Lifecycle 简介

lifecycle 组件包括 Lifecycle, LifecycleOwner, LifecycleObserver 三个主要类:

  • Lifecycle 类持有 Fragment 和 Activity (LifecycleOwner) 的生命周期状态信息,并且允许其他对象(LifecycleObserver)观察这些信息。
    • 此外 Lifecycle 还使用 Event 和 Status 来记录组件的生命周期状态

  • LifecycleOwner 仅有一个 getLifecycle() 方法,用于返回当前对象的 Lifecycle。Fragment 和 Activity 实现了 LifecycleOwner 接口
  • LifecycleObserver,这个接口通常由 app 来实现,用于封装那需要感知生命周期的对象或业务逻辑

官方文档是这样描述三者的关系的:

LifecycleOwner 接口将 Lifecycle 的所有权从单独的类 (Fragment 和 Activity) 中抽象出来。其他的类也可以实现 LifecycleOwner 接口
实现了 LifecycleObserver 接口的组件可以跟实现了 LifecycleOwner 接口的类很好地协同工作,owner 可以提供 lifecycle,而 observer 则注册到 owner 提供的 lifecycle 进行观察

官方给出的关于 lifecycle 的最佳实践:

  • UI controller 尽可能”瘦”
  • 编写数据驱动的 UI,UI controller 的作用仅仅是在数据更新时渲染界面
  • 数据逻辑放到 ViewModel 中
  • 使用数据绑定技术保持 view 和 UI controller 之间有干净的接口
  • 如果 UI 过于复杂,考虑使用 presenter 模式
  • 避免在 ViewModel 中持有 View 或 UI controller

官方给出的一些应用场景:

  • 在精确定位和粗略定位之间切换。使用 lifecycle-aware 组件进行切换,当应用处于前台时使用精确空位,当应用处于后台时切换到粗略定位
  • 停止或开始视频流缓冲。使用 lifecycle-aware 组件尽早开始缓冲,但直到 app 完全启动后才开始播放
  • 开始或停止网络连接。使用 lifecycle-aware 组件,当应用在前台时开启网络数据,在后台时则停止
  • 停止或开始 animated drawable。使用 lifecycle-aware 组件,当应用在前台时播放 animated drawable,在后台时则停止

If a library provides classes that need to work with the Android lifecycle, we recommend that you use lifecycle-aware components. Your library clients can easily integrate those components without manual lifecycle management on the client side.

早期版本的 Glide (v3.7.0之前) 就已经意识到 lifecycle-aware 问题,所以它抽象出以下两个接口来作为解决方案。

  • com.bumptech.glide.manager.Lifecycle
  • com.bumptech.glide.manager.LifecycleListener

不过 Android 官方推出 lifecycle 组件后,Glide 其实没必要使用自定义接口了。不过截至目前(2019.5.6)它仍然是使用 Lifecycle,也许是有历史包袱,也许是不想信赖 lifecycle 库。

Lifecycle 与可测试

将代码从 Activity/Fragment 抽出来封装成 lifecycle-aware 组件后带来的一个额外好处是可以单元测试。(Activity/Fragment 中的代码几乎没办法单元测试!)

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
class MyLifeCycleObserverTest {

lateinit var lifeCycleObserver: MyLifeCycleObserver
lateinit var lifeCycle: LifecycleRegistry
val logger : MyLogger = mock(MyLogger::class.java)

@Before
fun setUp() {
val lifeCycleOwner: LifecycleOwner = mock(LifecycleOwner::class.java)
lifeCycle = LifecycleRegistry(lifeCycleOwner)
lifeCycleObserver = MyLifeCycleObserver(lifeCycle, logger)
lifeCycle.addObserver(lifeCycleObserver)

lifeCycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}

@Test
fun shouldLogStart() {
lifeCycle.markState(Lifecycle.State.STARTED)
verify(logger).logStart()
}

@Test
fun shouldLogStop() {
lifeCycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
lifeCycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
verify(logger).logStop()
}
}

Kotlin 代码中使用 mockito 时会遇到无法 mock final 类的问题,解决方法见这里

完整的代码见 Github

参考