Android 生命周期监听演进与对应实现
用一条时间线说明「生命周期监听」如何从 Activity 里手写对称回调,演进为
Lifecycle+DefaultLifecycleObserver、Fragment 的viewLifecycleOwner,再到与协程配合的repeatOnLifecycle+ Flow,并对应各阶段在解决什么问题、今天该怎么选。
一、演进主线与时间线
Android 生命周期监听的发展,可以概括成一条主线:把生命周期管理从 UI 控制器里抽出去,交给 Lifecycle 与可组合的 API,让组件能声明式地「跟着生命周期走」。
flowchart LR
p1["手写对称回调"]
p2["AAC 与 @OnLifecycleEvent"]
p3["DefaultLifecycleObserver"]
p4["viewLifecycleOwner"]
p5["repeatOnLifecycle + Flow"]
p1 --> p2 --> p3 --> p4 --> p5
| 阶段 | 大致年代 |
|---|---|
| 手写对称回调 | AAC 出现前长期存在;约 2017 年前仍是主流做法 |
AAC 与 @OnLifecycleEvent | 约 2017 起随 Architecture Components 发布并推广 |
DefaultLifecycleObserver | 约 2018 起接口写法随 AndroidX 普及;Lifecycle 2.4(约 2021–2022 稳定) 废弃 @OnLifecycleEvent,迁移到接口为主 |
viewLifecycleOwner | 约 2019 前后起,随 Fragment 区分「实例 / View」生命周期成为常规选项 |
repeatOnLifecycle + Flow | 与 Lifecycle 2.4 一代协程与生命周期文档一并普及,用于按可见性收集 Flow |
二、第一阶段:手写回调的「刀耕火种」时代
1. 典型写法
在 Activity / Fragment 的生命周期方法里,手动调用业务组件的注册与注销。
class MyActivity : AppCompatActivity() {
private val locationListener = MyLocationListener()
override fun onStart() {
super.onStart()
locationListener.start()
}
override fun onStop() {
locationListener.stop()
super.onStop()
}
}
2. 为什么会被取代?(痛点)
- Activity/Fragment 臃肿:UI 控制器里堆满定位、网络、播放等无关业务的启停逻辑,难以维护。
- 极易出错:
onStart与onStop(或成对的其他回调)必须对称;漏写注销容易导致内存泄漏或后台仍持有资源引发崩溃。 - 组件无法独立:业务组件不能自给自足,必须依赖外部谁在哪个生命周期里「喊它一声」。
三、第二阶段:AAC Lifecycle 与注解时代(约 2017)
为解决上述问题,Google 在 Android Architecture Components(AAC)中引入 Lifecycle 与 LifecycleOwner:组件可以注册为观察者,自己响应生命周期事件,UI 层只负责 addObserver。
1. 典型写法:@OnLifecycleEvent
class MyLocationListener : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun start() { /* 开始定位 */ }
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() { /* 停止定位 */ }
}
class MyActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycle.addObserver(MyLocationListener())
}
}
2. 注解路径:为何出现,又因何废弃?
- 当年为何用注解:AAC 落地时工程侧仍以 Java 7 为主——没有接口默认方法时,若用「一个大接口列全
onCreate~onDestroy」,实现类往往要补大量空方法;若用抽象 Adapter 又占用单继承。@OnLifecycleEvent用按需标注绕开这一约束,在当时是务实折中。 - 实现成本:注解路径往往依赖反射(运行时)或 APT/KAPT(编译期生成代码),要么有运行时开销,要么拖慢构建。
- 环境与替代:Java 8 脱糖与 Kotlin 普及后,接口默认实现让显式
DefaultLifecycleObserver既能「只 override 关心的回调」,又避免反射;当初靠注解规避的问题有了更干净的解法。 - 官方收口:
@OnLifecycleEvent在 Lifecycle 2.4.0 起标记为废弃,推荐迁移到DefaultLifecycleObserver等显式 API(见 OnLifecycleEvent 文档说明)。与下一节「接口化」写法直接衔接。
四、第三阶段:接口化——DefaultLifecycleObserver
废弃 @OnLifecycleEvent 并不是放弃 Lifecycle 模型本身,而是把「按需订阅生命周期」交给接口默认实现这一更合适的语言特性。注解退场后,推荐写法是显式实现接口:DefaultLifecycleObserver 或 LifecycleEventObserver。其中日常最常用的是 DefaultLifecycleObserver:各生命周期方法在接口中有默认空实现,你只 override 关心的即可,无反射、契约清晰。
1. 典型写法
class MyLocationListener : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) { /* 开始定位 */ }
override fun onStop(owner: LifecycleOwner) { /* 停止定位 */ }
}
lifecycle.addObserver(MyLocationListener())
2. 为何这是当前推荐的「生命周期感知组件」基底?
- 零反射、构建友好:普通方法重写,行为一目了然。
- 类型安全:方法名与参数由接口固定,避免注解时代方法名随意、难以静态检查的问题。
更多说明见官方指南:Handling Lifecycles。
五、支线:Fragment 与 viewLifecycleOwner
这条支线为什么重要:Fragment 实例的生命周期和它内部 View 的生命周期不同步;只绑
fragment.lifecycle操作 UI,会在返回栈等场景下踩坑。
1. 痛点场景
当 Fragment 进入返回栈(BackStack)时,View 会被销毁(走到 onDestroyView),但 Fragment 实例仍存在。若某个监听仍挂在 Fragment 的 lifecycle 上,异步回调回来时去更新已销毁的 TextView 等控件,轻则 NPE,重则 泄漏已销毁的 View 引用。
2. 正确做法:viewLifecycleOwner
viewLifecycleOwner 表示 Fragment 中 View 的生命周期。凡是与界面绑定的观察、协程作用域,应使用 viewLifecycleOwner.lifecycle / viewLifecycleOwner.lifecycleScope,这样在 onDestroyView 时会自动与 View 对齐、解绑。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// 当 View 销毁时,Observer 会随 view 生命周期结束而移除
viewLifecycleOwner.lifecycle.addObserver(MyUIObserver())
// 错误示例:Fragment 在返回栈、View 已无时,仍可能收到回调并更新 UI
// lifecycle.addObserver(MyUIObserver())
}
详见:Fragment 生命周期 中对 viewLifecycleOwner 的说明。
六、第四阶段:协程、Flow 与 repeatOnLifecycle
LiveData 曾常与生命周期配合使用;在 Kotlin 中转向 Flow / StateFlow 后,collect 默认不感知生命周期:若在 Activity/Fragment 里长时间 collect,应用进入后台时仍可能持续收集,浪费 CPU 与电量。
1. 过渡期的别扭写法(应淘汰)
在 onStart 里启动协程收集、onStop 里取消,本质上又回到手写对称生命周期的老路,易错且难读。
2. 推荐写法:repeatOnLifecycle
在 lifecycleScope(Fragment 中与 UI 相关时用 viewLifecycleOwner.lifecycleScope)里使用 repeatOnLifecycle:在生命周期至少到达某一状态(如 STARTED)时执行块内逻辑;低于该状态时取消块内协程;再次进入时重新执行。这样与「前后台可见性」对齐,避免后台空转收集。
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
// 更新 UI
}
}
}
}
说明与注意事项见:将 Kotlin 协程与生命周期感知组件一起使用(含 lifecycleScope、repeatOnLifecycle 等用法)。
七、落地速查
演进的主线是:生命周期管理从 UI 层外移,由 Lifecycle、viewLifecycleOwner、repeatOnLifecycle 等标准 API 表达「何时启动、何时停止、与谁绑定」。日常写代码可直接按下表选型。
| 场景 | 当前推荐做法 |
|---|---|
| 编写生命周期感知的组件 | 实现 DefaultLifecycleObserver,使用 lifecycle.addObserver() 注册 |
| 在 Fragment 中操作 UI | 绑定到 viewLifecycleOwner.lifecycle(及对应的 lifecycleScope) |
| 在 Kotlin 中收集 Flow / StateFlow | viewLifecycleOwner.lifecycleScope + repeatOnLifecycle(Lifecycle.State.STARTED) 等 |
| 进程级前后台判断 | ProcessLifecycleOwner.get().lifecycle |