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 标准实现流程
- 获取 ViewModelStoreOwner:在 Activity 中
this即自身;在 Fragment 中推荐使用this(Fragment 本身的 ViewModel)或requireActivity()(共享 Activity 级 ViewModel)。 - 构造 ViewModelProvider:
ViewModelProvider(owner),如果 ViewModel 需要构造参数则传入ViewModelProvider.Factory。 - 获取 ViewModel 实例:调用
.get(MyViewModel::class.java)或 Kotlin 属性委托by viewModels()。 - 使用 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 是实例引用(已弃用或内部持有强引用)
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 实例的完整创建路径:
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 引用。