1. 从现象到警讯:ViewModel 内存泄漏的四个典型表现
ViewModel 持有 Activity/Fragment 引用导致无法释放时,不会立即崩溃,而是逐步侵蚀用户体验。早期识别这些现象,能显著缩短排查周期。以下四类信号是 ViewModel 泄漏的常见“报警灯”,每一类都对应着不同的泄漏路径和严重等级。
1.1 四种现象速查表
| 现象 | 典型表现 | 初步判断规则 |
|---|---|---|
| 用户反馈 | “操作后页面回退卡顿” “频繁出现白屏/闪退” “内存占用越用越高” |
反馈集中在 返回/销毁 场景,且随使用时长恶化 |
| ANR 日志 | 日志中包含 Input dispatching timed out主线程阻塞在 ViewModel 内回调或数据加载 |
堆栈中 ViewModel 的 onCleared() 未被调用,或持有 View 引用 |
| OOM 崩溃 | OutOfMemoryError 时 hprof 中大量 ViewModel 实例存活伴有 Bitmap/大集合未释放 |
同一页面被多次打开后,ViewModel 实例数持续增长(>2) |
| 界面响应异常 | 旋转屏幕后数据重复加载 返回上个页面时 UI 闪烁 LiveData 观察者收到旧数据 |
ViewModel 的 onCleared() 内未清空对 View 的强引用 |
1.2 现象之间的递进关系:故障树
下图展示了四种现象如何从“用户感知”逐步收敛到“泄漏嫌疑”,并触发数据采集流程。实际排查时,只要出现 2 种以上 现象组合,就应该立即进入数据采集阶段。
排查经验: 多数 ViewModel 泄漏首次被发现并非通过 OOM,而是用户抱怨“手机发烫”或“返回页面卡顿”。建议将用户反馈转化为可量化的性能指标(如页面销毁后 Heap 是否下降),与 ANR/OOM 数据关联分析。
1.3 现象识别判断规则
当系统日志、用户反馈或性能监控系统出现以下 任意 3 条 时,应优先怀疑 ViewModel 泄漏:
- 同一 Activity 被反复创建/销毁后,堆中该 Activity 的 ViewModel 实例数 > 1(且未触发
onCleared())。 - 用户反馈描述与“页面切换”“横竖屏旋转”“后台恢复”场景强相关。
- ANR 堆栈中
androidx.lifecycle.ViewModel或自定义 ViewModel 的某个方法正处于主线程执行。 - OOM 发生时
hprof中可见大量Activity及其 ViewModel 形成 GC Root 链。 - 通过
adb shell dumpsys meminfo看到Activities数量持续增长且无法回落。
1.4 示例:从 ANR 日志中识别 ViewModel 泄漏嫌疑
以下是一段真实的 ANR 日志截取,注意主线程卡在 MyViewModel.onSomething(),而该 ViewModel 对应的 Activity 已经处于销毁状态:
ANR in com.example.app (com.example.app/.MainActivity)
PID: 12345
Reason: Input dispatching timed out (Waiting to send key event because the touched window has not finished processing certain input events that were delivered to it over 500.0ms ago.)
...
"main" prio=5 tid=1 Blocked
at com.example.viewmodel.MyViewModel.onSomething(MyViewModel.java:56)
at com.example.viewmodel.MyViewModel$1.onChanged(MyViewModel.java:30)
at android.arch.lifecycle.LiveData.considerNotify(LiveData.java:109)
at android.arch.lifecycle.LiveData.dispatchingValue(LiveData.java:126)
...
at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:4567)
...
"Binder:12345_1" prio=5 tid=2 Native
... (等待锁释放)
关键定位点: 堆栈中 ViewModel 的调用链出现在
ActivityThread.handleDestroyActivity 之后,说明该 ViewModel 仍在处理回调,而 Activity 已被销毁 —— 这是 ViewModel 泄漏的经典证据。下一步应抓住 hprof 确认引用链。
1.5 现象识别清单(供日常监控接入使用)
| 监控维度 | 数据来源 | 阈值/规则 |
|---|---|---|
| 用户反馈文本 | 客服工单 / 应用内反馈 | 关键词包含“卡”“闪”“闪退”“内存” |
| ANR 堆栈 | /data/anr/traces.txt |
主线程持有 ViewModel 方法栈且对应 Activity 已销毁 |
| OOM Heap Dump | Firebase / 自建 hprof 采集 | 同一 ViewModel 类实例数 > 3 |
| 界面渲染性能 | FrameMetrics / Choreographer | 返回操作后仍有 > 200ms 的掉帧 |