3. ViewModel的生命周期与作用域:onCleared()触发时机、与Activity/Fragment生命周期对比
原理概述
ViewModel 的生命周期与它的宿主(ViewModelStoreOwner)密切相关。默认情况下,ViewModel 的作用域是 Activity 或 Fragment,但它不会因为配置变更(如屏幕旋转)而被销毁。只有当宿主真正结束其生命周期(如 Activity finish(),且非重建场景)时,系统才会调用 ViewModel 的 onCleared() 方法。理解这一机制是避免内存泄漏和正确管理资源的关键。
核心观点: ViewModel 的生命周期独立于 Activity 的配置变更,这使得 UI 数据能够在屏幕旋转等操作后无缝保留。
3.1 核心API与生命周期集成
在 Activity 或 Fragment 中,通过 ViewModelProvider 获取 ViewModel 实例。系统内部通过 ViewModelStore 维护 ViewModel 的生命周期。当 ViewModelStoreOwner 发生配置变更时,旧的 Activity 会将其 ViewModelStore 传递给新的 Activity,从而实现 ViewModel 的对象保留。
class MyViewModel : ViewModel() {
override fun onCleared() {
super.onCleared()
// 清理协程作业、关闭数据库连接、释放资源
Log.d("MyViewModel", "onCleared invoked")
}
}
// 在 Activity 中使用
class MyActivity : AppCompatActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this)[MyViewModel::class.java]
// 此时 ViewModel 被创建,并与 Activity 的生命周期绑定
}
}
3.2 onCleared() 触发时机详解
理解 onCleared() 的触发时机,是区分 ViewModel 生命周期与 Activity/Fragment 生命周期的关键。以下是几种典型场景:
- 场景 A:Activity 正常完成(非配置变更)
当 Activity 调用 finish() 或被系统正常销毁且不会重建时,系统会清除 ViewModelStore,并触发 ViewModel 的 onCleared() 回调。这是最常见的资源清理时机。 - 场景 B:Fragment 被分离或完成
当 Fragment 被移除或所在的 Activity 最终销毁时,其对应的 ViewModelStore 被清除,onCleared() 被调用。 - 场景 C:进程被系统杀死
当进程被系统回收时,onCleared() 可能不会被调用。此时应用应依赖 SavedStateHandle 来持久化关键数据,以便后续恢复。
最佳实践: 使用 Kotlin 协程时,优先利用 viewModelScope 进行协程管理。viewModelScope 会在 onCleared() 被调用时自动取消其内部的协程作业,从而有效避免协程泄漏。
3.3 生命周期对比:原理与差异
为了更清晰地展示 ViewModel 与 Activity/Fragment 生命周期的差异,下表从多个维度进行了对比:
| 对比维度 | Activity / Fragment | ViewModel |
|---|---|---|
| 作用域 | 依赖于用户导航和系统配置 | 依赖于 ViewModelStoreOwner 的生命周期 |
| 创建时机 | onCreate() / onAttach() | 当 ViewModelProvider 被首次调用时 |
| 配置变更 | 销毁并重建 | 自动保留在内存中 |
| 销毁时机 | onDestroy() | onCleared() (当 Owner 最终销毁时) |
| 主要职责 | 管理 UI 生命周期、显示数据 | 管理 UI 数据、业务逻辑 |
3.4 常见错误与稳定性优化
常见陷阱: 不要在 ViewModel 中持有 Activity、Fragment 或 View 的强引用。由于 ViewModel 的生命周期比单个 Activity 实例长,强引用 View 对象会导致 Activity 无法被垃圾回收,从而造成严重的内存泄漏。如果需要 Context,请使用 AndroidViewModel(application) 或 SavedStateHandle。
为了提升应用的稳定性和性能,在实际开发中应遵循以下优化策略:
- 及时清理外部资源
在 onCleared() 回调中,主动取消网络请求、关闭数据库连接、解绑系统服务或释放 FileDescriptor。不要依赖系统垃圾回收。 - 避免存储大对象
不要在 ViewModel 中存储大型 Bitmap、视图层级结构等对象。如果必须持有,请在 onCleared() 中将其置为 null,以便 GC 及时回收。 - 使用 Lifecycle-aware 组件
结合 LiveData、StateFlow 等响应式组件,确保数据流能够自动感知生命周期状态,避免在后台更新 UI 导致崩溃。