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 生命周期绑定流程

ViewModel 创建、缓存、回收核心流程 ViewModel 生命周期绑定与存储流程 说明:灰色连接线表示实例共享;绿色节点表示存活期;红色节点表示销毁期。 Activity 重建(旋转) ViewModelStoreOwner ViewModelProvider ViewModelStore ViewModel 实例(存活) 宿主真正销毁 onCleared() 触发

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 存储,降低内存占用。