3. ViewModel 创建与生命周期绑定:ViewModelProvider、ViewModelStore、onCleared 回调的正确使用
3.1 原理:ViewModel 的生命周期边界
ViewModel 的核心设计目标是在配置变更(如屏幕旋转)后保持数据存活。其生命周期范围始终大于关联的 Activity 或 Fragment 实例,直至宿主被系统真正销毁时才结束。
- 作用域:ViewModel 绑定于 ViewModelStoreOwner(AppCompatActivity、Fragment 等)。
- 存储:ViewModelStore 是存储/恢复 ViewModel 的容器,一个 ViewModelStoreOwner 对应一个 ViewModelStore。
- 回收:onCleared() 在 ViewModelStore.clear() 被调用时触发,代表 ViewModel 即将被永久丢弃。
关键认知:千万不要在 ViewModel 中持有 Activity 或 Fragment 的强引用,否则会导致内存泄漏。ViewModel 的生命周期比任何 UI 实例都长。
3.2 实现流程:ViewModelProvider 与 ViewModelStore
3.2.1 标准创建(默认构造)
Activity/Fragment 通过 ViewModelProvider 获取 ViewModel 实例,生命周期自动绑定。
// Activity 中获取 ViewModel
MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
// Fragment 中获取 ViewModel(作用域为宿主 Activity)
MyViewModel viewModel = new ViewModelProvider(requireActivity()).get(MyViewModel.class);
3.2.2 带参数的 Factory
若 ViewModel 构造需要参数,须自定义 ViewModelProvider.Factory。
public class MyViewModel extends ViewModel {
private final int param;
public MyViewModel(int param) {
this.param = param;
}
}
// Activity 中创建带参数的 ViewModel
ViewModelProvider.Factory factory = new ViewModelProvider.Factory() {
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new MyViewModel(42);
}
};
MyViewModel viewModel = new ViewModelProvider(this, factory).get(MyViewModel.class);
3.2.3 ViewModelStore 的内部机制
ViewModelProvider 内部通过 ViewModelStore 完成缓存和重建。流程如下表所示。
| 步骤 | 操作 | 关键方法 |
|---|---|---|
| 1 | Activity 重建时,系统自动保留 ViewModelStore(通过非配置保留片段) | Activity.onRetainNonConfigurationInstance() |
| 2 | ViewModelProvider 构造时关联此 ViewModelStore | ViewModelStoreOwner.getViewModelStore() |
| 3 | get() 调用时,先尝试从 HashMap 取缓存,未命中则调用 Factory.create() | ViewModelStore.get() / put() |
| 4 | 宿主真正销毁时调用 ViewModelStore.clear() | ViewModelStore.clear() → onCleared() |
// ViewModelStore 核心源码思想(简化)
public class ViewModelStore {
private final HashMap<String, ViewModel> mMap = new HashMap<>();
final void put(String key, ViewModel viewModel) {
ViewModel oldViewModel = mMap.put(key, viewModel);
if (oldViewModel != null) {
oldViewModel.onCleared();
}
}
final ViewModel get(String key) {
return mMap.get(key);
}
public final void clear() {
for (ViewModel vm : mMap.values()) {
vm.onCleared();
}
mMap.clear();
}
}
调试要点:在 ViewModel 构造方法和 onCleared() 中添加日志,通过观察 logcat 判断 ViewModel 是否因配置变更被复用,以及是否在正确时机被销毁。
3.3 内联 SVG:ViewModel 生命周期绑定流程
3.4 onCleared 回调的正确使用
3.4.1 何时触发
onCleared() 在以下情况被调用:
- 宿主 Activity 调用 finish() 或系统因资源回收杀掉进程。
- 宿主 Fragment 被移除或事务替换(非 addToBackStack)。
⚠️ 注意:配置变更(如旋转屏幕)不会触发 onCleared()。ViewModel 实例会被复用,onCleared() 仅当宿主被真正销毁时才执行。
3.4.2 典型错误用法
在 onCleared() 中执行异步操作或访问已关闭的资源可能导致异常。
// ❌ 错误:在 onCleared 中启动耗时协程
@Override
protected void onCleared() {
super.onCleared();
// 此时 ViewModel 即将丢弃,异步回调可能访问到已清理的上下文
repository.fetchData().observeForever(data -> { ... });
}
3.4.3 正确用法示例
public class SafeViewModel extends ViewModel {
private CompositeDisposable disposables = new CompositeDisposable();
private final MutableLiveData<List<String>> items = new MutableLiveData<>();
public void loadData() {
// 将 Disposable 添加到 CompositeDisposable 管理
Disposable d = repository.getItems()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(items::setValue, Throwable::printStackTrace);
disposables.add(d);
}
@Override
protected void onCleared() {
// ✅ 正确:取消所有异步订阅,释放资源
disposables.clear();
super.onCleared();
}
}
3.5 调试技巧
- 日志标记:在 onCleared() 和 ViewModel 构造方法中加入 Tag 日志,区分实例是否被复用。
- LeakCanary:集成 LeakCanary 检测 ViewModel 是否意外持有 Activity/Fragment 引用。
- 单元测试:使用
ViewModelProvider.AndroidViewModelFactory结合 Mock 对象测试 onCleared 行为。
// 调试日志示例
public class DebugViewModel extends ViewModel {
private static final String TAG = "DebugVM";
private final int id;
public DebugViewModel() {
this.id = (int) (Math.random() * 1000);
Log.d(TAG, "ViewModel created: " + id);
}
@Override
protected void onCleared() {
Log.d(TAG, "ViewModel onCleared: " + id);
super.onCleared();
}
}
3.6 常见错误与性能优化
| 问题 | 原因 | 解决方案 |
|---|---|---|
| ViewModel 泄漏 | ViewModel 内部持有了 Activity 的 Context 或 Fragment 引用 | 改用 Application Context(AndroidViewModel)或使用弱引用 |
| 异步泄漏 | 协程/RxJava 未在 onCleared 中取消 | 使用 viewModelScope(KTX)或 CompositeDisposable |
| Factory 作用域错误 | 使用 ViewModelProvider(this) 时 Factory 被全局缓存 | 每个 Activity/Fragment 创建独立的 Factory 实例 |
| SavedStateHandle 误用 | 错误地在 onCleared 中保存状态 | SavedStateHandle 会在配置变更时自动保留,无需手动干预 |
性能优化要点:
• 避免在 ViewModel 中执行大量计算,考虑将其放入后台线程。
• 使用 ViewModelProvider.Factory 时,尽量复用已有实例,减少对象创建。
• 对于仅与 UI 相关的临时数据,优先使用 LiveData 而非 ViewModel 存储,降低内存占用。
• 避免在 ViewModel 中执行大量计算,考虑将其放入后台线程。
• 使用 ViewModelProvider.Factory 时,尽量复用已有实例,减少对象创建。
• 对于仅与 UI 相关的临时数据,优先使用 LiveData 而非 ViewModel 存储,降低内存占用。