2. ViewModel核心原理与生命周期感知

2.1 生命周期感知的本质

ViewModel 并不感知 Activity/Fragment 的 onDestroy(非配置变更型),而是感知 配置变更(如屏幕旋转)和 真正销毁(如用户退出)。其核心机制围绕 ViewModelStore 与 onCleared() 展开,通过 Lifecycle 和 SavedStateRegistry 协同实现数据存活边界的对齐。

2.2 ViewModelStore:存储与释放控制点

每个 ViewModelStoreOwner(如 ComponentActivity、Fragment)持有一个 ViewModelStore 实例。该实例在配置变更期间被保留(通过 NonConfigurationInstances 机制),从而保证数据不丢失。

组件ViewModelStore 生命周期数据保留时机
Activity(正常场景)从 onCreate 到 onDestroy(非配置变更)屏幕旋转:保留;用户按返回:清除
Fragment(正常场景)从 onAttach 到 onDetach(非配置变更)旋转/夜间模式:保留;replace/remove:清除
Navigation NavBackStackEntry从创建到弹出返回栈入栈保留,出栈清除

关键实现流程:

  1. 当调用 ViewModelProvider(this).get(XxxViewModel::class.java) 时,内部从当前 ViewModelStoreOwner 获取 ViewModelStore。
  2. ViewModelStore 内部维护 HashMap<String, ViewModel>,以类名(或自定义 key)作为唯一键缓存 ViewModel 实例。
  3. 若 cache 命中,直接返回已存在的 ViewModel;否则通过反射或 ViewModelProvider.Factory 创建并存入。
  4. 当 Owner 被“真正销毁”时(非配置变更),ViewModelStore 调用 clear() 方法,逐一遍历所有 ViewModel 并执行 onCleared(),然后清空 HashMap。

2.3 onCleared():资源释放的守门员

官方 onCleared() 默认实现为空。子类应在此方法中释放长期持有的资源(如协程 Job、Rx Disposable、LiveData 观察者、流等)。

class MyViewModel : ViewModel() {
    private val job = SupervisorJob()
    val scope = CoroutineScope(job + Dispatchers.Main)

    override fun onCleared() {
        super.onCleared()
        job.cancel()
        Log.d("ViewModel", "所有协程已取消")
    }
}
常见陷阱: 在 onCleared() 中访问 viewModelScope 或已失效的引用会导致内存泄漏或崩溃。viewModelScope 内部关联了 onCleared() 自动取消,但若自己创建 CoroutineScope 必须手动取消。

2.4 与 Activity/Fragment 生命周期对齐的细节

下图展示了 ViewModel 在配置变更与真正销毁两种场景下的存活边界:

ViewModel 生命周期对齐图 ViewModel 生命周期对齐模型 Activity onCreate 配置变更 onDestroy 真正销毁 ViewModelStore 保留在 NonConfigInstances HashMap<String, ViewModel> 返回给新Activity clear() ViewModel 实例存活 onCleared() 回收资源 遍历 HashMap 逐个调用 onCleared() 配置变更时 ViewModelStore 保留, onCleared() 不被调用

2.5 调试与验证技巧

  • 在 onCleared() 中加入日志 Log.d("ViewModel", "$this cleared"),配合 adb logcat -s ViewModel 观察调用时机。
  • 使用 mDebugCount 计数器验证配置变更后 ViewModel 实例是否同一对象。
  • 通过 ViewModelStore#clear() 源码(androidx.lifecycle.ViewModelStore)确认迭代行为。
// 调试示例:观察实例 id
class DebugViewModel : ViewModel() {
    val id = System.identityHashCode(this)
    init { Log.d("DEBUG_VM", "创建实例: $id") }
    override fun onCleared() { Log.d("DEBUG_VM", "清除实例: $id") }
}

2.6 常见错误与性能/稳定性优化

问题类别表现解决方案
内存泄漏ViewModel 内持有 Activity 引用或未取消长时间 Task使用 viewModelScope 或手动清理;避免在 ViewModel 中传 Context(应用级 Context 除外)
onCleared 调用时机误判在 Fragment 被替换时期望 onCleared 立即执行,但实际可能延迟明确区分 FragmentManager 的 remove/replace 与 popBackStack 行为
ViewModel 创建开销每次旋转重建 Activity 时 ViewModel 实例不变,但若未正确 owner 将创建新实例确认使用的 ViewModelProvider 是否传入正确的 ViewModelStoreOwner
多 Fragment 共享 ViewModel 冲突两个 Fragment 使用相同 ViewModel 但 scope 不同导致状态不同步使用父 Fragment 或 Activity 作为统一 owner,避免各自创建 ViewModelStore
稳定性要点: ViewModel 的生命周期对齐是数据恢复的基石。务必在 onCleared() 中释放所有非 Android 内部管理的资源,避免使用 softReference 或 weakReference 作为延迟释放的替代手段——显式清理永远更可靠。

2.7 实战配置:自定义 ViewModelStoreOwner

在非标准场景(如自定义 View、Service 或 Jetpack Compose)中,可以手动实现 ViewModelStoreOwner 接口:

class CustomViewModelStoreOwner : ViewModelStoreOwner {
    private val store = ViewModelStore()
    override fun getViewModelStore(): ViewModelStore = store
    fun clearOnDestroy() { store.clear() }
}

// 使用
val owner = CustomViewModelStoreOwner()
val vm = ViewModelProvider(owner).get(MyViewModel::class.java)
// 在真正退出时显示清理
owner.clearOnDestroy()

这种方式适用于需要精确控制 ViewModel 存活的非 Activity/Fragment 组件,例如后台服务或自定义对话框。