第2章 ViewModel核心原理与生命周期感知:ViewModelStore、onCleared、与Activity/Fragment存活周期对齐
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 | 从创建到弹出返回栈 | 入栈保留,出栈清除 |
关键实现流程:
- 当调用
ViewModelProvider(this).get(XxxViewModel::class.java)时,内部从当前ViewModelStoreOwner获取ViewModelStore。 ViewModelStore内部维护HashMap<String, ViewModel>,以类名(或自定义 key)作为唯一键缓存 ViewModel 实例。- 若 cache 命中,直接返回已存在的 ViewModel;否则通过反射或
ViewModelProvider.Factory创建并存入。 - 当 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 在配置变更与真正销毁两种场景下的存活边界:
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 组件,例如后台服务或自定义对话框。