2. ViewModel 基本用法:继承 ViewModel、onCleared()、与 Activity/Fragment 绑定
ViewModel 的核心价值在于帮助界面控制器(Activity / Fragment)在配置变更(如屏幕旋转)后仍能保留数据,同时将数据与生命周期解耦。本节从最基础的用法开始,逐步建立对 ViewModel 工作原理的认知。
2.1 继承 ViewModel 创建自定义类
所有 ViewModel 对象都必须直接或间接继承自 androidx.lifecycle.ViewModel。一个最简单的实现如下:
import androidx.lifecycle.ViewModel
class UserProfileViewModel : ViewModel() {
// 存储用户数据,可在配置变更后保持
val userName = "未登录"
// 其他业务数据…
}
在自定义类中,你可以存放任何与界面无关的状态或数据。ViewModel 不持有对 Activity / Fragment 的直接引用,从而避免内存泄漏。默认情况下,ViewModel 类没有构造函数参数;如果需要在构造时注入依赖(如仓库层对象),应使用 ViewModelProvider.Factory,这将在后续章节展开。
2.2 生命周期回调用 onCleared()
当 ViewModel 所关联的 Activity 被 finish 或 Fragment 被 detach 后,系统会调用 onCleared()。这是一个释放资源的理想时机,例如关闭数据库游标、注销观察者、取消协程或停止网络请求。
class DataFetchViewModel : ViewModel() {
// 模拟一个需要手动清理的资源
private var resourceHandle: Any? = Any()
override fun onCleared() {
super.onCleared()
// 关闭资源,防止泄漏
resourceHandle = null
println("DataFetchViewModel onCleared - 资源已释放")
}
}
onCleared() 只会在 ViewModel 不再被任何 LifecycleOwner 引用时调用。在普通 Activity 中,当 finish() 后立即触发;但如果因屏幕旋转重建 Activity,旧的 ViewModel 不会立刻清除,而是等待新的 ViewModel 创建后,旧实例才会被丢弃并回调
onCleared()。
2.3 在 Activity / Fragment 中绑定 ViewModel
通过 ViewModelProvider 获取 ViewModel 实例。该机制保证了同一个 LifecycleOwner 在生命周期内始终返回同一个 ViewModel 实例,即使因配置变更重建也不会重复创建。
标准绑定步骤(以 Activity 为例)
- 在
onCreate()中调用ViewModelProvider(this).get(YourViewModel::class.java)。 - 将 ViewModel 暴露的数据(如
LiveData)与 UI 控件绑定。 - Activity 销毁时,系统自动管理 ViewModel 的生命周期,无需手动干涉。
// Activity 中的标准获取方式
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewModel: UserProfileViewModel =
ViewModelProvider(this).get(UserProfileViewModel::class.java)
// 使用 viewModel 中的数据更新 UI
findViewById(R.id.text_view).text = viewModel.userName
}
}
Fragment 绑定同理,但注意应当使用 ViewModelProvider(requireActivity()).get(...) 共享同一个 Activity 范围的数据,或 ViewModelProvider(this) 限定为当前 Fragment 范围。
2.4 生命周期对照:Activity vs ViewModel
下表直观展示 Activity 生命周期事件与 ViewModel 状态之间的对应关系:
| Activity 事件 | ViewModel 状态 | 说明 |
|---|---|---|
onCreate() 首次 |
创建新实例 | ViewModelProvider.get() 首次调用时实例化 ViewModel |
onCreate() 重建(旋转) |
复用已有实例 | ViewModel 仍然存活,数据保留 |
onDestroy() 重建前 |
仍存活 | Activity 即将重建,但 ViewModel 未被清除 |
onDestroy() 非重建 |
清除并调用 onCleared() |
Activity 真正退出,ViewModel 生命周期结束 |
2.5 常见错误与避坑
- 在 ViewModel 中持有 Activity 或 View 引用:这会导致内存泄漏。正确做法是只持有
LifecycleOwner的弱引用,或使用LiveData将数据发布出去,由 UI 层订阅。 - 重复创建 ViewModel:若在
onCreate()之外(如onResume())反复调用ViewModelProvider.get(),并不会创建新实例,但会增加不必要的检查开销。应只在onCreate()中获取一次。 - 忘记调用
super.onCleared():框架仍会自行清理核心资源,但为了安全,始终在重写onCleared()时调用父类方法。 - 在 ViewModel 中启动协程不主动取消:若使用
viewModelScope(需添加lifecycle-viewmodel-ktx依赖),协程会自动在onCleared()时取消;若自行管理协程,务必在onCleared()中取消 Job。
2.6 绑定流程与生命周期关系图
下方 SVG 图整合了上述所有关键节点:Activity 的创建‑销毁循环、ViewModel 的创建‑复用‑清除过程、以及 onCleared() 的触发时机。
通过上图可以清晰看到:只有在 Activity 真正的 onDestroy()(非旋转重建)时,ViewModel 的 onCleared() 才会被回调,实例随后被回收。旋转屏幕时,Activity 虽然重建,但 ViewModel 持续存活,数据完好无损。
onCleared() 中释放一切不再需要的资源,但不要在此处提交持久化操作(如数据库写入),因为 onCleared() 的调用时机不确定,且可能发生在主线程。持久化应在 ViewModel 的 saveState() 或通过 SavedStateHandle 完成。