第 2 章 泄漏数据采集工具全景
当应用出现“内存只增不减”“GC 频繁触发 ANR”或“Flow 订阅者停不掉”等异常时,第一步不是猜原因,而是用正确的工具采集第一手泄漏数据。Android 生态下有四种主力采集手段,本课将按照 现象 → 采集 → 分析 → 复测 → 预防 的排查逻辑,逐一拆解它们的用法和输出含义。
2.1 现象识别:什么时候该采集?
Flow 订阅泄漏往往没有显式异常,但以下几种“红灯”信号必须触发采集动作:
- 多次返回页面后,空闲内存持续下降(用
dumpsys meminfo或adb shell top观测) - GC 日志频繁出现
ConcurrentStarvation或Allocation failure - LeakCanary 桌面通知弹出(即使没弹出,也应周期性巡检)
- 使用 Android Studio Profiler 观察到对象计数不减反增
出现上述任意一种,立即进入数据采集流程。
2.2 数据采集:四种工具实战用法
| 工具 | 适用场景 | 采集方式 | 输出格式 | 定位精度 | 上手难度 |
|---|---|---|---|---|---|
| Android Studio Profiler | 开发阶段,实时观察 | IDE 内连接设备、点击 Record | 火焰图、对象堆、方法调用 | 中等(显示引用链但需手动过滤) | 低 |
| LeakCanary | 回归测试、真机日常 | 依赖注入 + 自动监控 | 泄漏痕迹(引用链)通知 | 高(直接给出导致泄漏的引用) | 低 |
| dumpsys meminfo | 线上/线下快速检查内存水位 | adb shell dumpsys meminfo <包名> |
纯文本内存统计(PSS/Private/Shared) | 低(无引用链,只能判断总量异常) | 极低 |
| hprof 文件 | 深度分析、离线复盘 | Profiler 导出 / am dumpheap |
二进制 .hprof,需 MAT/Perfetto 打开 | 最高(可追踪所有 GC Root 引用) | 高 |
2.2.1 Android Studio Profiler 实时采集
- 操作步骤:在 Android Studio 中连接设备 → 点击 Profiler 面板 → 选择 Memory → 点击 Record。
- 关键参数:勾选 Record Java/Kotlin allocations;配合 Record native allocations 可排查 JNI 泄漏。
- 数据输出:录制自动生成对象堆快照,可在 Heap Dump 标签页中按类名搜索,查看实例数量和引用关系。
- Flow 泄漏线索:搜索
Flow、Continuation、AbstractCoroutine等类名,看是否有残留实例。
2.2.2 LeakCanary 自动化采集
在 build.gradle 中添加依赖后,LeakCanary 会自动监控 Activity、Fragment、ViewModel 以及自定义 Watcher 注册的对象。当它检测到某个对象在 lifecycle 结束后仍未被 GC,就会生成一个 hprof 快照并解析出引用链。
避坑:LeakCanary 默认只监控 Android 组件生命周期对象。如果需要监控自定义 Flow 订阅者,必须手动调用
LeakCanary.config 注册自定义 ObjectWatcher。
// Kotlin 示例:注册自定义对象监视
LeakCanary.config = LeakCanary.config.copy(
watcherFactories = listOf(
SupportFragmentWatcherFactory(application),
ActivityWatcherFactory(application),
object : ObjectWatcherFactory {
override fun create(application: Application): ObjectWatcher {
return ObjectWatcher(application, name = "FlowSubscriber",
reachabilityInspectors = listOf(FlowSubscriberReachabilityInspector()))
}
}
)
)
当泄漏发生时,Logcat 会打印类似以下信息:
====================================
HEAP ANALYSIS RESULT
====================================
1 RETAINED OBJECTS
* com.example.app.MainViewModel (0x12c)
- referencing FlowCollector (0x234)
- referencing ContinuationImpl (0x345)
- referencing JobSupport (0x456)
- Root: thread main, system class
2.2.3 dumpsys meminfo 快速水位采集
适合在 CI 或线上容灾场景获取整体内存状态。用法:
adb shell dumpsys meminfo com.example.app -d
关键输出行及其含义:
App Summary
Pss(Total) Private Dirty Private Clean ...
Native Heap 12345 10000 0 ...
Java Heap 23456 20000 0 ...
...
Total PSS by OOM adjustment: ...
Foreground: 35791 kB
...
Pss(Total):进程占用的物理内存比例值,越线意味着可疑。Java Heap区域的 Private Dirty 持续增长通常是 Java 泄漏(包括 Flow 泄漏对象)的征兆。- 多次执行并对比 Total PSS,若每次操作后回不到初始值,说明存在泄漏。
2.2.4 hprof 文件深度采集
当 Profiler 或 LeakCanary 给出快照后,你可以导出 .hprof 文件进行离线分析。手动导出命令:
adb shell am dumpheap com.example.app /data/local/tmp/dump.hprof
adb pull /data/local/tmp/dump.hprof ~/analysis/
然后用 MAT(Memory Analyzer Tool)或 Perfetto UI 打开。在 MAT 中:
- 选择 Histogram,搜索
Flow、Continuation。 - 右键任意实例 → Merge Shortest Paths to GC Roots → 排除软/弱/虚引用。
- 查看导致泄漏的引用链,确认是否由 Flow 的
collect未取消导致。
2.3 原因定位:工具输出配合判断
四种工具的输出各有侧重,但判断泄漏原因时建议遵循以下规则:
- dumpsys meminfo 只用作“是不是泄漏”的开关,无法定位哪个对象。
- Profiler / hprof 可找到残留对象和引用链,但需要人工筛选 Flow 相关类。
- LeakCanary 直接给出
retained object和最短 GC 路径,几乎不需要二次分析。
常见陷阱:Profiler 堆快照中的 “Shallow Size” 很低不代表没泄漏。Flow 订阅者的
ContinuationImpl 可能只占几十字节,但引用链上的 Job、Scope 可能持有大量外部对象。必须看全部引用路径。
2.4 复测:验证采集是否有效
每次采集后,必须执行一次“复现 → 离开 → 再次采集”的闭环,确认泄漏量是否稳定:
- 执行完整泄漏触发场景(如反复进入/退出 Fragment 10 次)。
- 使用
dumpsys meminfo记录 Java Heap PSS 基线。 - 等待 5 秒钟(让 GC 运行),再用 Profiler 捕获堆快照。
- 对比 10 次前后的对象计数,若
FlowCollector实例数不等于已销毁实例数,则采集成功,泄漏已捕获。
2.5 预防与自动化策略
- CI 门禁:在自动化测试后添加
dumpsys meminfo比较脚本,超标即 fail。 - LeakCanary 灰度:在内部版本始终开启,重点监控新写的 Flow 页面。
- hprof 定期导出:通过
am dumpheap定时采集慢路径(见第 12 章),用于离线回归。
实战建议:在日常开发中,将 LeakCanary 作为第一道防线;遇到漏报时再用 Profiler 手动录制;线上问题必须用 dumpsys 做快速监控,必要时主动 dump hprof 上传进行分析。四种工具组合使用才能不漏掉任意一种 Flow 订阅泄漏。