先把结论放在前面:Lifecycle 的本质是"一组生命周期状态 + 一组观察者 + 一个状态机"。 LifecycleOwner 持有当前状态,Lifecycle 维护观察者集合,状态变化时按主线程同步分发事件。整个组件只有三个真概念——Lifecycle、LifecycleOwner、LifecycleObserver——剩下的都是围绕它们的注册、注销与时序处理。
这题属于"出场率最高的那一批"。丢分的典型方式不是答不出概念,而是只会用 lifecycleScope.launch,说不出状态分发规则、说不出"事件在状态变化之后才发"的时序、也说不清为什么在 onDestroy 里读状态会拿到 DESTROYED。 这三条恰恰是区分"用过"和"懂"的分界线。
状态机与分发规则
Lifecycle.State 有五个状态,构成一个由小到大的单向阶梯:
INITIALIZED → CREATED → STARTED → RESUMED
↑ ↓
DESTROYED ←──────── ON_STOP ← ON_PAUSE
关键规则有三条,每一条都能答出具体场景:
- 状态是"至少达到"的语义。
currentState返回的是"当前处于或不低于此状态"的那一档。当 View 处于STARTED时,lifecycle.currentState.isAtLeast(STARTED)为 true,而isAtLeast(RESUMED)为 false。 事件分发时框架会把观察者向上"降级"到目标状态并补齐所有该发的事件——例如一个只关心ON_START的观察者在页面恢复时会依次收到ON_START(补发)。 - 事件在状态迁移完成之后才发出。
ON_PAUSE之后才发ON_PAUSE事件,此时currentState已经是STARTED。这解释了为什么在onPause回调里读currentState拿到的不是RESUMED。 ON_DESTROY是唯一"不可逆"的事件。一旦进入DESTROYED,观察者会被自动移除,之后任何addObserver都会立刻收到ON_DESTROY而不会收到其他事件。这个设计避免了"往已销毁的生命周期里加观察者"的悬挂。
DefaultLifecycleObserver:为什么它优于注解
旧方案是 LifecycleObserver 注解 + 反射(ReflectiveGenericLifecycleObserver)。注解方案的三个问题是:① 依赖反射,有性能与混淆风险;② 方法名写错(如 onStartA)编译期无感知、运行期静默不回调;③ 没有默认实现,接口新增方法时所有实现类都要改。
DefaultLifecycleObserver 解决了上述三点:接口为每个方法提供默认实现(源码兼容)、无反射(编译期生成桥接)、方法名受编译器检查。这三点就是"编译期更安全"的具体含义——能把这三点都讲出来,才算答到了"为什么官方要换方案"这一层。
最常见的坑是
第一层坑:在 ON_DESTROY 回调里再读状态,状态已推进导致异常。
在 onDestroy(owner) 里调 lifecycle.currentState、lifecycleScope.launch 都会遇到"DESTROYED 之后不能再启动协程"的问题——lifecycleScope 在 ON_DESTROY 时被 cancel,之后再 launch 的协程不会执行(表现为"这段清理代码没跑")。这本身不是崩溃,而是静默失效,比崩溃更难查。 规范是清理工作放在 onDestroy 里可以,但凡"需要保证执行完"的逻辑不要依赖 lifecycleScope,而是用自己的 executor 或在 onStop 里完成。
第二层坑:把非生命周期对象硬注册成观察者,销毁不移除造成泄漏。
Fragment 里有 companion object 或单例注册了 lifecycle.addObserver(obj),这个对象被生命周期强引用着,页面销毁时如果观察者没有被移除(且对象不是 DefaultLifecycleObserver 的自动移除场景),就会连着页面一起泄漏。 更隐蔽的写法是在非生命周期类里缓存了 Lifecycle 引用并在其之后注册观察者。规范是三条:① 单例不注册观察者,改用回调或 Flow;② 必须注册时,在宿主销毁时显式 removeObserver;③ 优先用 repeatOnLifecycle 建立"进入才订阅、离开就取消"的成对关系。
第三层坑:lifecycleScope.launch 忘记指定 dispatcher,阻塞主线程。
lifecycleScope 默认是 Dispatchers.Main.immediate,在里面写阻塞 IO 会直接卡主线程。这是"用了协程但还是 ANR"的高频原因。规范是lifecycleScope.launch(Dispatchers.IO) { ... } 做 IO,主线程那段用 withContext(Dispatchers.Main)。
还有一个更隐蔽的坑:多个观察者的分发顺序没有保证。 框架按添加顺序分发,但如果观察者 A 在分发过程中又添加了观察者 C,C 会在本轮之后才收到事件;而 A 的 onStart 里改了 B 依赖的状态,B 读到的就是"改后的"状态。规范是观察者之间不互相依赖,共享状态用 ViewModel 而不是靠分发顺序。
代码里见真章
先看一个规范的观察者写法,重点是"哪些方法该做什么":
class VideoPlayerController(private val context: Context) : DefaultLifecycleObserver {
private var player: ExoPlayer? = null
// ON_CREATE:做一次性初始化(依赖 Context 的资源)
override fun onCreate(owner: LifecycleOwner) {
player = ExoPlayer.Builder(context).build()
}
// ON_START:注册"页面可见时该做"的行为
override fun onStart(owner: LifecycleOwner) {
player?.playWhenReady = true
owner.lifecycle.addObserver(NetworkWatcher(owner.lifecycle, this))
}
// ON_STOP:释放前台资源(相机、传感器、播放)
override fun onStop(owner: LifecycleOwner) {
player?.pause()
}
// ON_DESTROY:释放不可恢复的资源,并把引用置空
override fun onDestroy(owner: LifecycleOwner) {
player?.release()
player = null
}
}
这段代码的分工是这题的骨架:onCreate 建对象、onStart 注册与激活、onStop 释放可恢复资源、onDestroy 彻底释放并置空。"相机/传感器在 onStop 释放、播放器在 onDestroy 释放"这种分工判断,是实际做过才答得出的。
repeatOnLifecycle 的正确用法是这题的高分点,因为它是"订阅与取消成对"的唯一正解:
class FeedFragment : Fragment() {
private val viewModel: FeedViewModel by viewModels()
override fun onViewCreated(v: View, s: Bundle?) {
super.onViewCreated(v, s)
// 规范:repeatOnLifecycle 内部保证 STARTED 才订阅、STOPPED 就取消
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
// 三个子协程并行:事件收集、状态提交、副作用
launch {
viewModel.events.collect {
handleEvent(it) } }
launch {
viewModel.state.collect {
render(it) }
}
launch {
// 副作用:进入 STARTED 才执行,离开自动取消
val visible = viewModel.state
.map {
it.list.isNotEmpty() }
.distinctUntilChanged()
visible.collect {
showEmptyView(!it) }
}
}
}
}
}
这段代码的价值在于三点:① 用 viewLifecycleOwner 而非 Fragment,避免 View 已销毁但 Fragment 还在的场景;② repeatOnLifecycle 自动处理"订阅与取消",不需要手写 start/stop;③ 内部三个 launch 并行,收集与副作用分离。 能讲出"为什么是 viewLifecycleOwner 而不是 LifecycleOwner",说明真的踩过这个坑。
自定义观察者时,DefaultLifecycleObserver 与手写 LifecycleEventObserver 的选择:
// 方式一:DefaultLifecycleObserver —— 语义清晰,方法名自解释
class ScreenStateObserver : DefaultLifecycleObserver {
override fun onStart(owner: LifecycleOwner) {
/* 可见 */ }
override fun onStop(owner: LifecycleOwner) {
/* 不可见 */ }
}
// 方式二:LifecycleEventObserver —— 需要按事件类型分支处理时用
class RawEventObserver : LifecycleEventObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
when (event) {
Lifecycle.Event.ON_PAUSE -> {
/* 事件在状态已变为 STARTED 之后才来 */ }
Lifecycle.Event.ON_STOP -> {
}
Lifecycle.Event.ON_DESTROY -> {
/* 之后观察者会被自动移除 */ }
else -> {
}
}
// 读状态时用 isAtLeast 判断,而不是 == STARTED
val resumed = source.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)
}
}
// 方式三:LifecycleEventObserver + 单一状态流(组合式,可测试性最好)
fun Lifecycle.stateFlow(): Flow<Lifecycle.State> = callbackFlow {
val obs = LifecycleEventObserver {
_, _ ->
trySend(this@stateFlow.currentState)
}
this@stateFlow.addObserver(obs)
trySend(this@stateFlow.currentState) // 关键:先发一次当前状态
awaitClose {
this@stateFlow.removeObserver(obs) } // 关键:取消时移除,防泄漏
}.distinctUntilChanged()
这段代码里的 awaitClose 是防泄漏的关键——callbackFlow 若不在取消时移除观察者,观察者会一直持有协程的 channel,整个链路泄漏。"先发一次当前状态"也是必答点,否则订阅瞬间拿不到初始状态。
Lifecycle 与进程重建的关系是这题容易被追问的第二层:
class MainActivity : AppCompatActivity() {
private var savedCount = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 规范:区分"进程被杀后重建"与"配置变更重建"
val fromProcessDeath = savedInstanceState != null
// ViewModelStore 通过 NonConfigurationInstance 桥接,配置变更不丢数据
if (savedCount == 0) {
// 只在首次进入时做一次性初始化
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// 规范:只存"恢复界面必需"的最小状态,不存可重建的数据
outState.putInt(KEY_COUNT, savedCount)
}
}
这段代码说明的是 Lifecycle 与进程生死的关系:ViewModelStore 靠 NonConfigurationInstance 在配置变更时保留,但进程被杀就丢失;savedInstanceState 两者都能恢复,但容量有限。"哪些数据该放 ViewModel、哪些该放 savedInstanceState"是这题的延伸追问。
面试中的经典考点
问:Lifecycle 的事件分发是同步还是异步?分发顺序是什么?
答:同步,在主线程分发,且是"降级补齐"的策略——框架会把观察者的状态调整到目标状态,按 ON_CREATE → ON_START → ON_RESUME(下行)或 ON_PAUSE → ON_STOP → ON_DESTROY(上行)的顺序补发它尚未收到的事件。分发过程中新加的观察者会在下一轮收到事件。 观察者之间不保证"状态隔离",所以不应用于跨组件传状态。
问:为什么用 viewLifecycleOwner 而不是 this(Fragment)?
答:Fragment 的生命周期长于它的视图——onDestroyView 之后 Fragment 还存在(onDestroy 才结束)。如果用 Fragment 的 lifecycleScope + view 引用,视图销毁后协程仍在跑,会操作已销毁的 View,直接崩溃。 viewLifecycleOwner 在 onCreateView 到 onDestroyView 之间有效,它绑定的正是"视图生命周期"。这是这题最高频的追问。
问:repeatOnLifecycle 与手动 addObserver 的差别?
答:repeatOnLifecycle 在生命周期降到目标状态以下时自动取消内部协程,回到目标状态时重新启动——"重启"意味着块内的 collect 会重新执行一次,所以块内必须是"订阅 + 收集"的组合,不能放"只该执行一次的初始化"。手动 addObserver 没有取消语义,订阅会在页面销毁后仍然存在。能指出"块内代码会重复执行"这一点,是很多人不知道的细节。
问:进程被杀重建,Lifecycle 怎么表现?
答:整个进程重启,所有生命周期对象都是全新的——savedInstanceState 会带回来(系统保存的),ViewModelStore 是空的。观察者、协程、绑定都需要重建。 规范是把"必须跨进程存活的数据"放 savedInstanceState 或本地持久化,不要指望 ViewModel 兜住进程死亡。
问:自定义 View 要感知生命周期,怎么做?
答:三种。① ViewTreeObserver.OnAttachStateChangeListener——只感知 attach/detach,与生命周期无关但足够解决"别在未 attach 时更新";② 让宿主在 onStart/onStop 时调用 View 的 resumeAnimations()/pauseAnimations()——这是官方推荐做法(见 androidx.core 的 LifecycleEventObserver 手动注册示例);③ Compose 里用 LifecycleStartEffect/LifecycleResumeEffect——框架已封装。能说出"官方推荐由宿主显式调用 View 方法"这一点,比答"用 LiveData"更贴近实践。
落到项目里怎么做
一条能写进规范的红线:协程作用域必须绑对 owner——Fragment 内一律用 viewLifecycleOwner.lifecycleScope,View 内用 findViewTreeLifecycleOwner()?.lifecycleScope。 选错 owner 是这类崩溃与泄漏的第一来源。
配套实践三条:① 把"进入可见期才做的事"统一收口到 repeatOnLifecycle(STARTED) 一处,禁止散落在 onStart/onResume 里各写一半;② 自定义观察者一律实现 DefaultLifecycleObserver,用编译期检查换掉注解与反射;③ 生命周期回调里只做"状态响应",不做长耗时阻塞,耗时任务进 ViewModel 或独立调度器。
给正在准备面试的你
这题的答法要落到"状态机规则 + 观察者选型 + owner 绑定"这条主线。推荐这条线:先画五状态与事件补发规则("事件在状态变化之后才发"要主动说)→ 讲 DefaultLifecycleObserver 优于注解的三点(默认方法、无反射、编译期检查)→ 讲三个坑:onDestroy 里用 lifecycleScope 静默失效、单例注册观察者泄漏、忘指定 dispatcher → 落到"viewLifecycleOwner + repeatOnLifecycle"这套标准写法。能被追问到"repeatOnLifecycle 的块会重复执行"并答出"因为它会重新启动协程",说明真的在项目里用过;能讲清 onDestroy 里 launch 为什么不执行,说明踩过静默失效的坑。
如果这篇文章对你有帮助,欢迎点赞、在看、转发三连。你的支持就是这个系列持续更新的动力。
「Android软件开发面试·从入门到精通」连载系列
上一篇:图片加载原理:三级缓存与解码采样
下一篇预告:LiveData-原理:粘性分发与生命周期绑定
有任何问题欢迎在评论区留言交流。