3、工具链搭建:Android Profiler基础使用、Memory Profiler实战、Heap Dump分析

说到内存泄漏定位,工具就是我们的眼睛。我见过太多人凭感觉猜泄漏点,结果改了一堆代码问题还在。其实,Android Studio自带的Profiler已经够强大了,关键是你得会用。

这一章,我带你把工具链搭起来。从基础操作到实战分析,一步步来。

3.1 Android Profiler 基础使用

Android Profiler是Android Studio 3.0以后集成的性能分析工具。它把CPU、内存、网络、电量四个模块整合到了一起。我个人习惯,先打开Memory标签,因为内存问题最隐蔽。

启动方式很简单:

  • 点击底部工具栏的「Profiler」图标
  • 或者:View → Tool Windows → Profiler
  • 选择你要分析的设备和进程
小技巧: 我建议用真机调试。模拟器的内存行为跟真机有差异,尤其是低端机上的表现。曾经有个泄漏在模拟器上死活复现不了,换到红米上一跑就崩。

Profiler界面主要分三块:

  • 时间轴:显示内存、CPU的实时变化曲线
  • 事件轴:记录用户操作、GC、Activity跳转等
  • 详细面板:点击具体时间点,展开详细数据

嗯,这里要注意。时间轴上的内存曲线如果呈现「锯齿状」——也就是不断上升然后突然下降——这通常是正常的GC行为。但如果曲线是「阶梯状」持续上升,从不回落,那就要警惕了。

3.2 Memory Profiler 实战

Memory Profiler是定位泄漏的核心武器。说白了,它就是帮你回答三个问题:

  1. 谁占用了内存?
  2. 这些对象该不该被回收?
  3. 为什么没被回收?

实战步骤:

第一步:录制内存分配

点击Memory Profiler上的「Record」按钮,开始记录。然后操作你的App,执行那些你觉得可能有泄漏的场景。比如:打开一个Activity,再返回,重复几次。

注意: 录制时间不要太长,30秒到1分钟就够了。录制太久,数据量太大,分析起来反而麻烦。我一般只录制关键操作的前后5秒。

第二步:触发GC

操作结束后,点击「Force GC」按钮。这个操作会强制触发垃圾回收。如果回收后内存占用依然很高,那基本可以确定有泄漏了。

第三步:导出Heap Dump

点击「Dump Java Heap」按钮,生成当前内存的快照。这个快照就是我们要分析的Heap Dump文件(.hprof格式)。

核心判断标准: 重复执行某个操作后,GC回收的内存应该跟操作前基本一致。如果每次操作后内存都多出几百KB,且GC后不下降,那就是泄漏。

3.3 Heap Dump 分析

Heap Dump分析是内存优化的重头戏。我刚开始学的时候,看着一堆类名和引用链,头都大了。后来总结出一套方法,分享给你。

分析入口:

导出Heap Dump后,Android Studio会自动打开分析视图。主要看这几个标签:

标签 作用
Class List 按类名列出所有对象,显示实例数和占用大小
Package Tree 按包名分组,方便定位自己的代码
Reference Tree 显示对象的引用链,找GC Root

实战分析流程:

1. 按包名过滤

在Package Tree里找到你自己的包名。系统类、第三方库的泄漏一般不用管,先看自己的代码。我曾经排查过一个泄漏,发现是某个第三方图片库的缓存没释放,但那是人家的bug,我们只能绕过去。

2. 找大对象

按「Retained Size」排序,从大到小看。大对象通常是Bitmap、数组、或者持有大量子对象的集合类。如果一个Activity的实例数超过1,那基本就是泄漏了。

3. 分析引用链

选中可疑对象,右键 → 「Show Nearest GC Root」。引用链会告诉你:这个对象为什么没被回收。常见的GC Root有:

  • 静态变量(static field)
  • 活跃线程(Thread)
  • 系统服务(System Service)
  • JNI全局引用
避坑指南: 我曾经遇到一个情况,Heap Dump里Activity实例有3个,但引用链显示都是被「InputMethodManager」持有。查了半天才发现是软键盘的bug。后来在onDestroy里手动清理了InputMethodManager的引用,问题解决。

4. 对比分析

如果你不确定某个对象是不是泄漏,可以导出两次Heap Dump:操作前一次,操作后一次。用Android Studio的「Compare」功能对比差异。多出来的对象,就是泄漏的嫌疑对象。

3.4 常见泄漏模式与工具配合

工具是死的,人是活的。我总结了几种常见泄漏模式,配合Profiler可以快速定位:

泄漏模式 Profiler表现 Heap Dump特征
Activity泄漏 反复打开关闭Activity,内存持续上升 Activity实例数 > 1,被静态变量持有
Handler泄漏 操作频繁时内存波动大 Handler持有Activity引用,MessageQueue中有未处理消息
单例泄漏 内存占用稳定偏高 单例对象持有Context或View引用
线程泄漏 GC后内存不降 Thread对象存活,持有外部类引用
我的习惯: 每次发版前,我都会用Profiler跑一遍核心流程。打开首页→登录→进入详情页→返回→退出登录。重复3次,看内存曲线是否平稳。如果每次操作后内存都比之前高,那就得查了。

你想想看,如果等到线上用户反馈「App越来越卡」或者「闪退」,那时候再查泄漏,成本就高多了。所以工具链一定要在开发阶段就搭好。

嗯,这一章的内容就这些。工具只是手段,关键是你得养成「用数据说话」的习惯。下一章,我们聊聊怎么用LeakCanary做自动化检测,那又是另一套玩法了。