4. ViewModelProvider 的正确创建方式:不传 Activity/Fragment 实例、避免直接 new

很多开发新手在接触 ViewModel 时最容易犯的错误就是直接 new ViewModel(),或者在构造 ViewModelProvider 时传入 Activity/Fragment 的实例引用。这两种做法都会破坏 ViewModel 的生命周期管理机制,导致内存泄漏、状态丢失甚至应用崩溃。本章将深入剖析 ViewModelProvider 的正确构造原理,并给出标准实现流程。

4.1 为什么不能直接 new ViewModel?

直接调用 new MyViewModel() 创建的 ViewModel 对象不会被 ViewModelStore 管理,也无法感知 Activity/Fragment 的生命周期。当配置变更(如屏幕旋转)时,该对象会被当作普通 Java 对象回收,而系统期望的是同一个 ViewModel 实例保留在内存中直到所属的 ViewModelStoreOwner 真正销毁。

反之,通过 ViewModelProvider 获取的 ViewModel 会被存储在 ViewModelStore 中,当 Owner 被销毁且不是因为配置变更(如 isFinishing() 返回 true)时,ViewModel 的 onCleared() 才会被触发,从而规范释放资源。

4.2 为什么不能传 Activity/Fragment 实例?

在早期版本的 AAC 中,ViewModelProvider 的构造确实会接收 Fragment 或 Activity 对象。但这样会持有对 UI 组件的强引用,导致在配置变更期间 UI 组件被重建后,旧的 Activity/Fragment 实例无法被垃圾回收,形成内存泄漏。

当前推荐的方式是只传递 ViewModelStoreOwner 接口(Activity、Fragment、NavBackStackEntry 等都实现了该接口),ViewModelProvider 内部只通过该接口获取 ViewModelStore,而不持有实际的 UI 组件引用。

核心原则:始终使用 ViewModelProvider(owner) 或 Kotlin 委托属性 by viewModels(),永远不要直接调用构造方法。

4.3 标准实现流程

  1. 获取 ViewModelStoreOwner:在 Activity 中 this 即自身;在 Fragment 中推荐使用 this(Fragment 本身的 ViewModel)或 requireActivity()(共享 Activity 级 ViewModel)。
  2. 构造 ViewModelProvider:ViewModelProvider(owner),如果 ViewModel 需要构造参数则传入 ViewModelProvider.Factory。
  3. 获取 ViewModel 实例:调用 .get(MyViewModel::class.java) 或 Kotlin 属性委托 by viewModels()。
  4. 使用 ViewModel:在 Activity/Fragment 的整个生命周期内保持同一个实例。
// Kotlin 推荐方式(无参数)
class MyFragment : Fragment() {
    val viewModel: MyViewModel by viewModels()
}

// Kotlin 推荐方式(需要 Factory)
class MyFragment : Fragment() {
    val viewModel: MyViewModel by viewModels {
        MyViewModelFactory(param1, param2)
    }
}

// Java 标准方式
public class MyActivity extends AppCompatActivity {
    private MyViewModel mViewModel;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 只传 this(ViewModelStoreOwner),不传 Activity 实例
        mViewModel = new ViewModelProvider(this).get(MyViewModel.class);
    }
}

// 错误示范(直接 new)
MyViewModel vm = new MyViewModel();  // ❌ 生命周期不受控

// 错误示范(传 Fragment 实例)
// 不推荐:new ViewModelProvider(fragment) 如果 fragment 是实例引用(已弃用或内部持有强引用)
常见误区:千万不要在 Fragment 的构造方法或 onAttach() 之前调用 viewModels(),此时 ViewModelStore 尚未初始化,会抛出 IllegalStateException。所有 ViewModel 的获取应放在 onCreate() 或之后。

4.4 不同创建方式对比

创建方式生命周期管理内存安全推荐场景
new ViewModel()无❌ 易泄漏(无 onCleared)绝不使用
ViewModelProvider(activity)(传 Activity 实例)有,但持有 Activity 引用⚠️ 配置变更时可能泄漏已废弃,勿用
ViewModelProvider(owner)(传 ViewModelStoreOwner)完整周期感知✅ 自动释放所有场景
Kotlin by viewModels()完整周期感知✅ 自动释放所有 Kotlin 项目

4.5 ViewModelProvider 内部工作流程

下图为 ViewModelProvider 从 Owner 到 ViewModel 实例的完整创建路径:

ViewModelProvider 创建流程 Activity / Fragment ViewModelStoreOwner ViewModelProvider(owner) ViewModelStore ViewModelProvider.Factory ViewModel 实例 注意 不传 Activity/Fragment 实例, 只传 ViewModelStoreOwner

4.6 调试与验证技巧

如果你怀疑 ViewModel 被多次创建,可以在 ViewModel 的 init 块或构造方法中添加日志并打印 hashCode:

class MyViewModel : ViewModel() {
    init {
        Log.d("ViewModel", "Created instance: ${hashCode()}")
    }
    override fun onCleared() {
        super.onCleared()
        Log.d("ViewModel", "Cleared: ${hashCode()}")
    }
}

旋转屏幕后若看到两次“Created”日志,说明 ViewModelProvider 使用方式有误(可能传了错误的 Owner)。

4.7 性能与稳定性影响

正确使用 ViewModelProvider 能显著提升应用稳定性:

  • 避免 OOM:不持有 Activity/Fragment 强引用,配置变更时旧实例及时回收。
  • 降低 UI 重启开销:ViewModel 内数据不随配置变更重建,避免重复网络请求或数据库查询。
  • 减少 ANR 风险:生命周期方法 onCleared() 被正确调用,确保后台协程或线程及时终止。
最佳实践总结:
1. 所有 ViewModel 必须通过 ViewModelProvider(owner) 或 by viewModels() 获取。
2. 永远不要直接 new ViewModel()。
3. 如果需要自定义 Factory,使用 viewModels { factory } 或重写 ViewModelProvider.Factory,不允许在构造时传入 Activity/Fragment 引用。