第1章 / 30章:Flow泄漏现象识别 —— 内存持续增长与协程不取消
定位泄漏的前提是感知异常。用户反馈、监控告警或开发过程中的直觉,往往是排查的起点。本章系统梳理Flow泄漏的五大典型表象,并建立从表象到工具的快速映射关系,协助你在第一时间确认问题的存在。
1.1 五大核心表象与检测工具映射
| 现象 | 核心特征 | 首选检测手段 | 危险等级 |
|---|---|---|---|
| 内存持续增长 | Memory Profiler中呈现锯齿状阶梯上升,GC后无法回落至基线 | adb shell dumpsys / Profiler | 高 |
| 协程不取消 | ViewModel onCleared或Activity onDestroy后,协程Job仍处于Active状态 | Kotlin Coroutines Debug插件 | 严重 |
| GC无法回收 | Heap Dump中发现FlowCollector或Continuation对象被GC Root强引用持有 | Heap Dump + MAT | 严重 |
| 应用卡顿 | 主线程Choreographer频繁跳帧,Systrace显示Flow发射任务阻塞UI线程 | Systrace / CPU Profiler | 中 |
| OOM风险 | 因Flow缓存或内部队列积压导致OutOfMemoryError崩溃 | Crash Upload / Bugly | 灾难 |
1.2 实战现象识别流程
步骤一:宏观内存检查(快速排除法)
通过adb命令直接读取应用整体内存状态,持续观察特定指标的波动。
adb shell dumpsys meminfo com.example.leakyapp | grep -E "TOTAL|PSS"
判断规则:反复执行3次(每次间隔5秒),如果TOTAL PSS持续增长且单次增量超过512KB,基本可以判定存在内存泄漏嫌疑。
步骤二:协程存活检查(溯源协程)
利用IDE的调试功能直接查看当前进程中所有存活的协程及其所属Scope。
1. 在Android Studio中运行应用。
2. 打开 Debug 工具窗口(Alt + Shift + F9)。
3. 找到 Kotlin Coroutines 标签页。
4. 展开列表,搜索 "flow" 关键字。
5. 检查是否有协程挂载在已销毁的 Scope(如 onDestroy 后的 ActivityScope)上。
典型泄漏日志特征:协程名称中包含flow,并且其所在的Scope处于Empty状态(表示父Scope已被GC回收,但协程仍存活)。
步骤三:GC回收验证(最具说服力)
强行触发GC并抓取Heap Dump,分析引用路径。
// 强行触发GC
adb shell am broadcast -a android.intent.action.DISMISS_KEYBOARD_SHORTCUTS
// 手动触发GC(Profiler中点击“垃圾桶”图标)
// 等待1分钟后抓取Heap Dump
// 使用MAT或Android Studio自带Profiler分析
// 搜索 "FlowCollector" 或 "SafeCollector" 查看GC Root路径
判断规则:如果Heap Dump中 SafeCollector 或 FlowCollector 实例数超过3个,并且每个实例都有强引用路径(如 Fragment → ViewModel → Job → Continuation → FlowCollector),即可确认为Flow泄漏。
避坑:仅凭堆内存持续增长不能直接判定为Flow泄漏,需要结合GC是否能够回收以及协程是否取消综合判断。部分原生内存(如Bitmap)增长并非Flow泄漏。后续章节将对此进行交叉验证。
1.3 泄漏现象快速排查决策表
你遇到的是什么情况?
├── 应用不卡顿,但内存不断涨
│ └── ➔ 查看协程是否活跃(Debug Plugin) ➔ 进入第2章
├── 应用卡顿明显,甚至有ANR
│ └── ➔ 查看主线程堆栈(Systrace) ➔ 是否在collect?
│ ├── 是 ➔ Flow泄漏导致主线程阻塞
│ └── 否 ➔ 检查其他主线程耗时操作
├── OOM崩溃
│ └── ➔ 查看崩溃堆栈是否有Flow相关方法
│ ├── 是 ➔ 进入第3章深入分析
│ └── 否 ➔ 检查其他OOM原因(如大图加载)
├── 明显感觉GC频繁(Logcat: GC_FOR_ALLOC)
│ └── ➔ 抓取Heap Dump查看大量FlowCollector实例 ➔ 进入第4章
└── 无感知异常,但监控告警内存超标
└── ➔ 使用adb dumpsys定时采集数据 ➔ 进入第2章