</>FUNNYGUYZ

知识点 4:Jetpack Lifecycle + ViewModel

#Lifecycle#ViewModel#Flow
← 学习资料优先级:高

优先级:🟡 高 这组知识点适合串联生命周期感知、订阅防漏、配置变更保活和进程重建恢复。


核心原理讲解

💡 按照面试口语化表述,为你梳理核心架构的底层逻辑:

Lifecycle 观察者模式是怎么实现的?

核心实现依托于三个角色:LifecycleOwner(生命周期持有者,如 Activity/Fragment)、LifecycleObserver(观察者)和 LifecycleRegistry(分发注册表)。

待生成图片:图片 04-1:LifecycleOwner 与 LifecycleRegistry 观察者状态对齐状态机

  • 如何同步状态LifecycleRegistry 内部维护了一个状态机(State 状态与 Event 事件)。当宿主生命周期变化时(比如 Activity 走到了 onStart),会通过关联的 ReportFragmentLifecycleRegistry 发送一个 ON_STARTEvent
  • 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 实例。

追问 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 频繁被动刷新。