第2章 理解ViewModel生命周期:创建、清除、onCleared与测试模拟
2.1 ViewModel生命周期原理
ViewModel的生命周期独立于Activity/Fragment的旋转配置变化,但受ViewModelStoreOwner(如Activity、Fragment)的最终销毁影响。其核心生命周期阶段包括:创建(构造)、活跃使用、清除(onCleared)。
- 创建:首次通过
ViewModelProvider获取时,ViewModel被实例化并绑定到ViewModelStore。 - 活跃使用:在Owner存活期间,ViewModel持有数据,可响应状态变化。
- 清除:当Owner的
onDestroy()被调用且不是因配置变更引起时,ViewModelStore.clear()触发,依次调用每个ViewModel的onCleared()。
关键区别:配置变更(如屏幕旋转)不会导致ViewModel清除;只有Activity.finish()或Fragment被移除等最终销毁才会触发清除。
2.2 实现流程:从创建到清除
- Owner(Activity/Fragment)启动,首次调用
ViewModelProvider.get(MyViewModel::class.java)。 - ViewModelStore.newInstance()创建MyViewModel实例,存入Map。
- Owner存活期间,ViewModel可以持有LiveData、Flow或协程作用域。
- 当Owner因finish()/remove()导致最终销毁时,ViewModelStore.clear()遍历所有ViewModel实例。
- 每个ViewModel的
onCleared()被调用,释放资源(关闭数据库、取消协程等)。 - Owner被回收,ViewModel对象变为不可达。
2.3 API与关键方法
| 方法/接口 | 作用 | 调用时机 |
|---|---|---|
onCleared() | 清理资源、取消订阅 | ViewModelStore.clear() 时,Owner最终销毁后 |
ViewModelStore.clear() | 清除所有ViewModel并触发onCleared | Activity.onDestroy() 非旋转时 / Fragment.onDestroy() |
ViewModelProvider.get() | 创建或返回已存在的ViewModel | 首次访问或配置变更后重新关联 |
closeable(继承自AutoCloseable) | 自动在onCleared时关闭Closeable对象 | onCleared 内部调用 |
2.4 测试中模拟生命周期的关键方法
单元测试中无法依赖Android组件生命周期,需手动模拟ViewModel的创建与清除。以下是三种常见策略:
2.4.1 直接构造并手动调用onCleared
适合纯Kotlin/Java ViewModel,无需依赖Android框架。
class MyViewModelTest {
private lateinit var viewModel: MyViewModel
@Before
fun setUp() {
viewModel = MyViewModel(/* 依赖注入 */)
}
@Test
fun testOnClearedCancelsJob() {
// 模拟开始使用
viewModel.startSomeWork()
// 手动触发清除
viewModel.onCleared()
// 验证协程已被取消
assertTrue(viewModel.scope.isCancelled)
}
}
2.4.2 使用TestCoroutineDispatcher验证协程取消
当ViewModel内部使用 viewModelScope 时,可控制调度器。
@Test
fun testCoroutineCancellationOnCleared() = runTest {
val testDispatcher = StandardTestDispatcher()
Dispatchers.setMain(testDispatcher)
val viewModel = MyViewModel()
viewModel.doWork()
// 模拟owner销毁
viewModel.onCleared()
// 推进时间,确保取消传播
testDispatcher.scheduler.advanceUntilIdle()
assertTrue(viewModel.job.isCancelled)
}
2.4.3 使用ArchTaskExecutor和InstantTaskExecutorRule
适用于LiveData等同步执行场景。
class ViewModelLifecycleTest {
@get:Rule
val instantRule = InstantTaskExecutorRule()
@Test
fun testLiveDataEmitsOnCleared() {
val viewModel = TestViewModel()
viewModel.someLiveData.observeForever { /* 同步接收 */ }
viewModel.onCleared()
// 验证LiveData被置空或发出特定值
assertEquals(null, viewModel.someLiveData.value)
}
}
最佳实践:在测试中调用
onCleared() 后,应检查资源是否完全释放。对于使用 Closeable 的ViewModel,可继承 Closeable 接口,测试仅需验证 close() 被调用。
2.5 常见错误与调试
错误1:测试中未调用onCleared但断言资源释放
结果:测试通过,但生产环境因未释放引发内存泄漏。
解决方法:每个Test中显式调用
结果:测试通过,但生产环境因未释放引发内存泄漏。
解决方法:每个Test中显式调用
viewModel.onCleared(),或使用 @After 统一清理。
错误2:误认为onCleared在配置变更时也会触发
结果:在旋转测试中清除了不该清除的状态。
解决方法:使用
结果:在旋转测试中清除了不该清除的状态。
解决方法:使用
ViewModelProvider.get() 确保复用,不在onCleared中重置配置无关数据。
调试技巧:为ViewModel添加日志,在
onCleared 中输出当前状态。在单元测试中通过捕获日志(如使用JUnit的 TestRule)验证清除路径。
2.6 性能与稳定性优化
- 避免在onCleared中执行耗时操作 – 如需保存数据,使用协程启动并设置超时。
- 使用
viewModelScope进行结构化并发 – 当onCleared触发时,自动取消所有子协程。 - 利用
Closeable接口 – 实现AutoCloseable的ViewModel,多个Closeable会自动依次关闭,简化测试。 - 定期测试生命周期完整性 – 编写回归测试覆盖所有ViewModel的清除流程,防止后续修改破坏清理逻辑。