LeakCanary : https://github.com/square/leakcanary
version : 1.6.3
Header
LeakCanary 是一款专门用来侦测 Android 内存泄漏的类库。使用方式简单,代码侵入性低,基本上算是 Android 开发必备工具了。
今天就主要来分析一下 LeakCanary 的实现原理。在开头就简单地讲讲它的实现思路:LeakCanary 将检测的对象(一般是 Activity 或 Fragment)放入弱引用中,并且弱引用关联到引用队列中,触发 GC 之后,查看引用队列中是否存在该弱引用,如果发现没有,那么有可能发生内存泄漏了,dump 出堆内存快照进行分析。分析出泄漏实例后再查找到它的引用链,最后发送通知给开发者。
Prepare
这里先简单讲解一下 WeakReference 的知识。
Q: 如何检测一个对象是否被回收?
A: 采用 WeakReference + ReferenceQueue 的方案检测
Reference
Reference 把内存分为 4 种状态,Active 、 Pending 、 Enqueued 、 Inactive。
- Active :一般说来 Reference 被创建出来分配的状态都是 Active
- Pending :马上要放入队列(ReferenceQueue)的状态,也就是马上要回收的对象
- Enqueued :Reference 对象已经进入队列,即 Reference 对象已经被回收
- Inactive :Reference 从队列中取出后的最终状态,无法变成其他的状态。
ReferenceQueue
引用队列,在 Reference 被回收的时候,Reference 会被添加到 ReferenceQueue 中。
作用:用来检测 Reference 是否被回收。
代码解释
下面这段代码来自于 「Leakcanary 源码分析」看这一篇就够了
1 | //创建一个引用队列 |
在 Reference 类加载的时候,Java 虚拟机会会创建一个最大优先级的后台线程,这个线程的工作就是不断检测 pending 是否为 null,如果不为 null,那么就将它放到 ReferenceQueue。因为 pending 不为 null,就说明引用所指向的对象已经被 GC,变成了不也达。
源码解析
LeakCanary 初始化的代码就一句 LeakCanary.install(application)
。所以我们就从入口开始看吧。
LeakCanary.install
1 | public static @NonNull RefWatcher install(@NonNull Application application) { |
在 install 方法中,使用了构造者模式来创建 RefWatcher 。我们直接看 AndroidRefWatcherBuilder 的 buildAndInstall 模式。
AndroidRefWatcherBuilder.buildAndInstall
1 | public @NonNull RefWatcher buildAndInstall() { |
重点来看 ActivityRefWatcher.install(context, refWatcher);
在这里我们就只看 ActivityRefWatcher 了,因为 FragmentRefWatcher 的原理也是差不多。
AndroidRefWatcher.install
1 | public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) { |
在 AndroidRefWatcher 中,只是去注册了 ActivityLifecycleCallbacks 接口。在 onActivityDestroyed 方法中调用 refWatcher 去观察该 Activity 有没有内存泄漏。这样,就不需要开发者手动地去写代码监听每一个 Activity 了。
RefWatcher.watch
1 | public void watch(Object watchedReference) { |
创建出一个有唯一标示的 WeakReference ,然后调用 ensureGone 来看看 Activity 有没有被回收。
RefWatcher.ensureGone
1 | @SuppressWarnings("ReferenceEquality") // Explicitly checking for named null. |
ensureGone 中逻辑就是反复地确认 Set 集合中还有没有 key ,如果没有的话就代表没有内存泄漏;反之,就很有可能发生了内存泄漏。
ServiceHeapDumpListener.analyze
1 | public final class ServiceHeapDumpListener implements HeapDump.Listener { |
ServiceHeapDumpListener 这里主要调用了 HeapAnalyzerService 来分析内存。注意,HeapAnalyzerService 是运行在另外一个进程中的,不是主进程。
HeapAnalyzerService.runAnalysis
1 | public static void runAnalysis(Context context, HeapDump heapDump, |
HeapAnalyzerService 其实是继承了 IntentService 的。所以只要看 onHandleIntent 中的内容就好了,对应着也就是 onHandleIntentInForeground 方法。
HeapAnalyzerService.onHandleIntentInForeground
1 | @Override protected void onHandleIntentInForeground(@Nullable Intent intent) { |
分析内存的步骤主要在 HeapAnalyzer 中。
HeapAnalyzer.checkForLeak
1 | public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile, |
这里主要有两个方法的看点:
- findLeakingReference
- findLeakTrace
我们先来看第一个 findLeakingReference 。
HeapAnalyzer.findLeakingReference
1 | private Instance findLeakingReference(String key, Snapshot snapshot) { |
还记得之前 KeyedWeakReference 中的那个唯一标示 key 吗?对,这里找内存泄漏的实例也是靠它。
通过那个 key 可以找出 KeyedWeakReference 实例,然后 KeyedWeakReference 实例中 referent 全局变量就是我们要找的内存泄漏实例。也就是我们的 Activity/Fragment 对象。
这样,就完成了内存泄漏的实例查找。然后我们再来看第二个点 findLeakTrace 方法。
HeapAnalyzer.findLeakTrace
1 | private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot, |
findLeakTrace 方法总体的逻辑就是
- 建立内存泄漏点到 GC Roots 的最短引用链
- 计算整个内存泄漏的大小 retained size
这里的在内存快照中引用链建立等都是在 haha 库中完成的。haha 是 square 出品一款 Android Heap 分析库。
具体可以看这里 :https://github.com/square/haha
到这里,LeakCanary 整体的逻辑分析就讲完了。下面再给出一张流程图。
流程图
Footer
其实 LeakCanary 整体的代码流程很清晰,阅读起来也比较易懂,也给我们好好地上了一课。
Read the fucking source code!