知识点 4:Jetpack Lifecycle + ViewModel
优先级:🟡 高 这组知识点适合串联生命周期感知、订阅防漏、配置变更保活和进程重建恢复。
核心原理讲解
💡 按照面试口语化表述,为你梳理核心架构的底层逻辑:
Lifecycle 观察者模式是怎么实现的?
核心实现依托于三个角色:LifecycleOwner(生命周期持有者,如 Activity/Fragment)、LifecycleObserver(观察者)和 LifecycleRegistry(分发注册表)。
待生成图片:图片 04-1:LifecycleOwner 与 LifecycleRegistry 观察者状态对齐状态机
- 如何同步状态:
LifecycleRegistry内部维护了一个状态机(State 状态与 Event 事件)。当宿主生命周期变化时(比如 Activity 走到了onStart),会通过关联的ReportFragment向LifecycleRegistry发送一个ON_START的Event。 LifecycleRegistry接收到 Event 后,会推进自身的State(例如从CREATED变为STARTED),并遍历持有的观察者列表,逐个触发回调。它能保证不管观察者是在什么时候注册进来的,都能自动向后对齐到宿主当前的最新状态。
ViewModel 重建保活的机制
ViewModel 为什么能在屏幕旋转、语言切换等配置重建(Configuration Changes)后依然存活?
待生成图片:图片 04-2:Activity 配置变更重建与 ViewModelStore 暂存恢复机制图
- 核心秘密:系统在销毁因配置变更重建的 Activity 时,会先调用
retainNonConfigurationInstances()将内部的ViewModelStore存起来。 - 重建恢复:当新 Activity 实例重建并创建
ViewModelProvider时,会通过getLastNonConfigurationInstance()拿回刚才暂存的ViewModelStore,从中取出原本就存在的 ViewModel 实例。 - 真正销毁:只有当 Activity 真正调用了
finish()退出生命周期(或者 Fragment 真正被 Pop 出栈)时,宿主才会告诉ViewModelStore调用所有 ViewModel 的onCleared()来彻底释放资源。
StateFlow 在 MVI 中的订阅防漏与生命周期感知
在 MVI 中,View 订阅 ViewModel 中的单一不可变状态 StateFlow。
如果直接在 lifecycleScope.launch 里收集(collect)数据,当 App 被切到后台(处于 STOPPED 状态)时,底层的 Flow 收集协程依然在活跃,生产者如果继续推送数据,协程依然会处理,这会造成不必要的 CPU 开销和潜在的 UI 异常。
待生成图片:图片 04-3:repeatOnLifecycle(STARTED) 自动收集与后台自动取消协程生命周期图
- 解决办法:使用
repeatOnLifecycle(Lifecycle.State.STARTED)。它是一个挂起函数,当生命周期进入STARTED时自动启动协程进行 Flow 收集;一旦生命周期低于STARTED(比如切到后台变成STOPPED),它会直接取消(cancel)收集协程;回到前台后又会重新创建协程开始收集。这比旧的launchWhenStarted更加安全和省电。
应用场景
复杂页面可以让 ViewModel 统一暴露不可变 StateFlow<UiState>,UI 层通过 repeatOnLifecycle(Lifecycle.State.STARTED) 订阅。这样页面不可见时会取消收集协程,返回前台后重新收集,避免后台继续处理无效 UI 数据。
高频追问 + 答题要点
追问 1:既然 repeatOnLifecycle 这么好,那它和之前常用的 launchWhenStarted 到底有什么区别?
- 要点框架:
launchWhenStarted在生命周期低于 STARTED 时,仅仅是将协程挂起(suspend),底层的 Flow 依然在保持收集和写入缓冲(背压),没有真正断开。repeatOnLifecycle在离开 STARTED 状态时,会直接**取消(cancel)**协程,彻底释放订阅资源;返回 STARTED 时再重新 launch。性能和资源回收更彻底。
追问 2:ViewModel 内部能不能传入 Context?如何拿全局的 Context?
- 要点框架:
- 绝对不能直接或间接传入 Activity / Fragment 的
Context或 View 的引用,否则会导致 Activity 重建时,旧的 Activity 无法被回收,直接造成严重的内存泄漏。 - 正确做法:如果需要 Context,必须使用
AndroidViewModel,它在构造方法中会传入全局唯一的Application实例。
- 绝对不能直接或间接传入 Activity / Fragment 的
追问 3:如果 Activity 被系统低内存强杀并重建(Process Recreation),ViewModel 能保活吗?怎么解决?
- 要点框架:
- 不能。系统强杀是进程层面的,
getLastNonConfigurationInstance()也会被清空,ViewModel 中的数据会全部丢失。 - 解决办法:在 ViewModel 的构造方法中注入
SavedStateHandle,它能够借助系统onSaveInstanceState(Bundle)的持久化机制,自动在进程重建时恢复此前保存的数据。
- 不能。系统强杀是进程层面的,
当前知识缺口提示
-
[!IMPORTANT] Fragment 生命周期订阅的坑(致命高频):在 Fragment 中观察 LiveData 或收集 StateFlow 时,
lifecycleOwner参数传入this还是viewLifecycleOwner?必须用viewLifecycleOwner!因为 Fragment 的视图销毁时(onDestroyView),Fragment 实例可能还活着(没有onDestroy),如果用了this,此时 LiveData 观察者没有被销毁,当数据变化时会尝试去操作已经销毁的 View 导致直接崩溃。viewLifecycleOwner的生命周期与 Fragment 的 View 绑定,在onDestroyView时会自动断开观察。 -
[!NOTE] MVI 状态 copy 的开销:MVI 强调 State 不可变,状态变化必须
copy整个对象。如果 UI 很复杂,高频变化时会有大量的对象创建和 GC 压力。需要在 ViewModel 中合理分层,利用 Flow 的distinctUntilChanged()对状态字段进行去重,防止不相关的子 View 频繁被动刷新。