2. ViewModel 核心原理回顾
ViewModel 是 Android Jetpack 架构组件中最关键的部分之一,专门用于以生命周期感知的方式存储和管理界面相关的数据。它能够在 Activity 或 Fragment 因配置变更(如屏幕旋转)而重建时保持数据存活,避免重复加载网络、数据库或计算任务。本章从生命周期、作用域以及与 Activity / Fragment 的绑定关系三个维度展开,并为后续与 DataStore 的协同打下理论基础。
核心概念提炼: ViewModel 的生命周期 长于 宿主(Activity / Fragment),作用域随宿主自然消亡,其内部数据不会因配置变更而丢失。
2.1 ViewModel 的生命周期
- 创建时机: 当 Activity / Fragment 首次调用
ViewModelProvider(model).get(XXViewModel::class.java)时创建。 - 存活边界: ViewModel 一直存活,直到其关联的 Activity 真正 finish 或 Fragment 脱离 FragmentManager(即宿主生命周期最终销毁)。
- 配置变更: 旋转屏幕、切换语言、改变字体大小等暂时销毁并重建 Activity,但 ViewModel 不会销毁,新 Activity 会拿到同一个 ViewModel 实例。
- 主动清除: 调用
ViewModelStore.clear()或宿主 finish 时,ViewModel 的onCleared()回调会被触发,释放资源。
| 宿主状态 | ViewModel 状态 | 数据是否保留 | 典型场景 |
|---|---|---|---|
| Activity onCreate(首次) | 创建 | — | 首次初始化数据 |
| Activity 旋转重建 | 保持存活 | 是 | 列表滚动位置、表单输入 |
| Activity 被系统回收 | 可能被销毁(依赖 SavedState) | 部分保留(SavedStateHandle) | 低内存 kill 后重建 |
| Activity finish() | 销毁(onCleared) | 否 | 用户退出页面 |
2.2 ViewModel 的作用域
作用域决定了 ViewModel 的共享范围和生命周期边界。Android 提供了三种常用作用域,且可通过自定义 ViewModelStoreOwner 扩展。
| 作用域 | 生命周期绑定点 | 典型使用 | 备注 |
|---|---|---|---|
Activity |
Activity 生命周期 | 该 Activity 下所有 Fragment 共享 | 最常用 |
Fragment |
Fragment 生命周期 | 仅在单个 Fragment 内有效 | 默认作用域 |
NavBackStackEntry |
Navigation 图的回退栈 | 导航图中特定目的地的状态 | 需搭配 Navigation Compose / Fragment |
作用域选择建议: 如果需要在 Activity 内的多个 Fragment 之间共享数据(如登录状态、购物车),使用 Activity 作用域;数据仅属于当前 Fragment 时,使用 Fragment 作用域,避免不必要的耦合。
2.3 ViewModel 与 Activity / Fragment 的关系
ViewModel 通过 ViewModelStore 与宿主绑定。每个 ViewModelStoreOwner(Activity/Fragment)持有一个 ViewModelStore,当宿主被销毁时, ViewModelStore 被清空,所有关联的 ViewModel 执行 onCleared()。关键 API 与实现流程如下:
- 获取实例:
ViewModelProvider(this).get(MyViewModel::class.java),参数this即为 ViewModelStoreOwner。 - 构造注入: 使用
ViewModelProvider.Factory传入自定义参数或依赖(如 DataStore)。 - 生命周期回调: ViewModel 内部不应持有直接引用 Activity / Fragment 的
Context或View,以避免内存泄漏。需要 Context 时通过Application作用域或SavedStateHandle获取。 - 数据暴露: 通常使用
LiveData或StateFlow对外暴露数据,确保 UI 层观察者随生命周期自动取消注册。
2.4 核心流程图:ViewModel 生命周期与宿主关系
2.5 关键 API 与代码实现
以下示例展示了如何在 Activity 中获取 ViewModel 并观察其数据:
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 通过 ViewModelProvider 获取 ViewModel 实例
viewModel = ViewModelProvider(this).get(MyViewModel::class.java)
// 观察 LiveData(生命周期感知)
viewModel.uiState.observe(this) { state ->
// 更新 UI
}
}
}
自定义 Factory 注入 DataStore:
class MyViewModelFactory(
private val dataStore: DataStore<Preferences>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MyViewModel(dataStore) as T
}
}
// 使用时
val factory = MyViewModelFactory(dataStore)
val vm = ViewModelProvider(this, factory).get(MyViewModel::class.java)
常见错误: 在 ViewModel 中保存 Activity / Fragment 的 Context 或 View 引用会导致内存泄漏。如需 Context,请使用
ViewModelProvider.Factory 传入 Application 或使用 SavedStateHandle。
2.6 调试与性能优化要点
- 日志跟踪: 在
init{}和onCleared()中添加Log.d,确认是否异常重建或多次实例化。 - 避免大对象: ViewModel 内部不要持有大量 bitmap 或大集合,否则配置变更时仍占用内存,增加 OOM 风险。超过 50KB 的数据建议使用 Room 或 DataStore 持久化。
- 协程作用域: 推荐使用
viewModelScope(Ktx 扩展)管理协程,当 ViewModel 清除时自动取消,避免泄露。 - SavedStateHandle: 即使系统进程杀死 Activity,也能恢复少量关键数据,配合 ViewModel 使用可提升稳定性。
// 使用 viewModelScope 发起协程
viewModelScope.launch {
dataStore.data.collect { preferences ->
// 更新状态
}
}
2.7 稳定性优化
| 问题 | 原因 | 解决方案 |
|---|---|---|
| ViewModel 重复创建 | 多次调用 get() 但未使用同一个 ViewModelStoreOwner |
确保宿主对象一致(如都传入 this) |
| 配置变更后数据不更新 | LiveData 未正确设置观察者或 Activity 重建后未重新订阅 | 使用 LifecycleOwner 参数 this 自动管理观察 |
| 内存泄漏 | ViewModel 持有 Activity 引用 | 仅使用 Application Context 或 SavedStateHandle |
| 协程任务泄漏 | 未使用 viewModelScope |
统一用 viewModelScope 启动协程 |
本章小结: ViewModel 的生命周期长于宿主,通过 ViewModelStore 绑定,配置变更时不销毁;其作用域可选择 Activity、Fragment 或 Navigation 回退栈;与宿主通信时使用 LiveData / StateFlow,避免直接引用 UI 组件。掌握这些原理是后续实现 ViewModel + DataStore 协同的基石。