1. 为什么需要 ViewModel:生命周期问题、配置改变、内存泄漏、数据持久化需求
Android 应用组件(Activity / Fragment)的生命周期不受开发者直接控制。旋转屏幕、切换语言、进入多窗口模式等配置改变会导致 Activity 重建,而普通成员变量在重建时全部丢失。同时,在异步回调中持有 Activity 引用极易造成内存泄漏。这些问题催生了 ViewModel——一个专为跨越生命周期存储界面相关数据而设计的架构组件。
1.1 四个核心痛点
| 痛点 | 典型场景 | 后果 |
|---|---|---|
| 生命周期割裂 | Activity 的 onCreate 中加载数据,onDestroy 时销毁 | 每次重建都需要重新加载,产生不必要的网络/数据库请求 |
| 配置改变 | 横竖屏旋转、分辨率变更、夜间模式切换 | Activity 销毁重建,临时数据全部清空 |
| 内存泄漏 | 匿名内部类 / 异步回调持有 Activity 引用 | Activity 无法被 GC 回收,OOM 风险上升 |
| 数据持久化缺失 | 用户填写表单时接听电话,返回 App | 未保存的表单数据丢失,用户体验极差 |
1.2 配置改变带来的 Activity 重建流程
下面这张 生命周期对比图 展示了在配置改变时,没有 ViewModel 与使用 ViewModel 的处理差异。注意 onSaveInstanceState 只能存储少量序列化数据,不适合保存列表、位图等较大对象。
1.3 内存泄漏的典型错误代码
下面这段代码展示了在异步回调中直接使用 Activity 引用导致的泄漏风险。当 Activity 已经销毁而回调尚未执行时,整个 Activity 实例无法被回收。
// ❌ 错误写法:匿名内部类隐式持有 Activity 引用
public class MainActivity extends AppCompatActivity {
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = findViewById(R.id.text);
// 模拟网络请求回调
NetworkManager.fetchData(new Callback() {
@Override
public void onSuccess(String data) {
// 此时若 Activity 已销毁,引用仍被持有 → 内存泄漏
mTextView.setText(data);
}
});
}
}
1.4 数据持久化需求 vs ViewModel 的职责边界
需要注意的是,ViewModel 不直接替代持久化存储。它保存的是内存级数据,在进程被系统杀死后仍然会丢失。真正的持久化需求(保存到磁盘、数据库、SharedPreferences)应交由 Room、DataStore 或 SavedStateHandle 处理。ViewModel 配合 SavedStateHandle 可以在进程被 kill 后自动恢复部分状态,这是后续章节的重点。
1.5 ViewModel 的生命周期范围
准确理解 ViewModel 的生命周期边界是正确使用的前提。下面这张表总结了 ViewModel 的存活范围与常见反例:
| 场景 | ViewModel 存活 | ViewModel 销毁 |
|---|---|---|
| Activity 正常旋转(配置改变) | ✅ 存活 | — |
| Activity 正常返回(finish) | — | ✅ 销毁 |
| Fragment 替换但宿主 Activity 未销毁 | ✅ 存活(Fragment 级别的 ViewModel 仍保留) | — |
| 进程被系统杀死(低内存) | — | ✅ 销毁(可借助 SavedStateHandle 恢复) |
| 开发者手动调用 finish() | — | ✅ 销毁 |
理解这些边界后,你就能明确哪些数据应放入 ViewModel,哪些应存入持久层。ViewModel 的核心价值是在配置改变时无痛保留数据,同时通过解耦 Activity 引用解决内存泄漏问题。接下来的章节将逐步讲解 ViewModel 的创建、使用、测试与进阶技巧。