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 为例)

  1. 在 onCreate() 中调用 ViewModelProvider(this).get(YourViewModel::class.java)。
  2. 将 ViewModel 暴露的数据(如 LiveData)与 UI 控件绑定。
  3. 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 生命周期结束
💡 关键理解:ViewModel 的生命周期跨度大于对应的 Fragment 或 Activity 实例,但小于整个进程。它既不跟随 Activity 的重建而销毁,也不在 Fragment 的 onDetach() 时立即清除。

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() 的触发时机。

ViewModel 生命周期与 Activity 绑定关系 Activity.onCreate() onStart/onResume onPause/onStop Activity.onDestroy() 屏幕旋转重建 真正退出 ViewModel 首次创建 ViewModel 复用期 继续存活/数据保留 onCleared() 调用 实例被 GC 回收 ViewModelProvider.get(this) 触发清除 关键规则 屏幕旋转时不调用 onCleared() 数据自动保留

通过上图可以清晰看到:只有在 Activity 真正的 onDestroy()(非旋转重建)时,ViewModel 的 onCleared() 才会被回调,实例随后被回收。旋转屏幕时,Activity 虽然重建,但 ViewModel 持续存活,数据完好无损。

✅ 最佳实践:在 onCleared() 中释放一切不再需要的资源,但不要在此处提交持久化操作(如数据库写入),因为 onCleared() 的调用时机不确定,且可能发生在主线程。持久化应在 ViewModel 的 saveState() 或通过 SavedStateHandle 完成。