本文链接:https://rainmonth.github.io/posts/A200811.html
LeakCanary本质上就是一个基于MAT进行Android应用程序内存泄漏自动化检测的的开源工具。注意:本文基于1.6.3版本进行分析。
内存泄漏检测的原理
主要就是利用检测对象的声明周期(Activity
和Fragment
)回调中开启监听,依据弱引用在gc触发后会被加入到ReferenceQueue
中,然后根据自己记录的要回收的对象key和已经加入到ReferenceQueue
中的对象key做对比,如果自己记录的key中包含了ReferenceQueue中不存在的内容,则说明有内存泄漏发生了。
下面通过方法一步步跟踪来看看LeakCanary是如何实现内存泄漏检测的。
基本使用
调用install方法即可开启泄漏监听
1 |
|
buildAndInstall()
代码如下:
1 |
|
有此可见,监听Activity是否泄漏发生在ActivityRefWatcher.install(this.context, refWatcher);
,监听Fragment是否泄漏发生在Helper.install(this.context, refWatcher);
,先看前者Activity内存泄漏的监听。
Activity 泄漏监听流程
ActivtyRefWatcher.java
1 | public static void install( { Context context, RefWatcher refWatcher) |
看代码:
1 | private final ActivityLifecycleCallbacks lifecycleCallbacks = new ActivityLifecycleCallbacksAdapter() { |
很简单,在Activity的onDestroyed方法执行时,将destroy的Activity对象作为参数传递给RefWatcher的watch方法,看代码:
1 | public void watch(Object watchedReference, String referenceName) { |
ensureGoneAsync
看名字就是一个异步方法,主要是通过AndroidWatchExcutor(内部开启了一个HandlerThread,可以通过HandlerThread获取Looper,然后通过改Looper构造一个Handler对象(子线程Handler)),调用连如下方法如下:
1 | public void execute( { Retryable retryable) |
本质就是想主线程消息队列中添加一个IdleHandler,当主线程空闲时从下午队列获取消息进行处理,处理是在AndroidWatchExecutor的HandlerThread线程进行的,见下面代码:
1 | private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) { |
retryable.run(),调用的就是RefWatcher.this.ensureGone(reference, watchStartNanoTime);
,现在看该方法:
1 | Result ensureGone(KeyedWeakReference reference, long watchStartNanoTime) { |
ServiceHeapDumpListener里面有一个
Context
和一个AbstractAnalysisResultService
(实质上是一个IntentService,内部会开启一个HandlerThead,又是一个线程),看看analyze方法:
1 | public void analyze( { HeapDump heapDump) |
HeapAnalyzerService本身也是一个IntentService
1 | public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass) { |
最终会调用 HeapAnalyzerService 的 onHandleIntentInForeground 方法:
1 | protected void onHandleIntentInForeground( { Intent intent) |
可见heapDump文件的分析工作最终是有HeapAnalyzer来完成的,其运行完checkForLeak方法后会得到 AnalysisResult 对象,最终交给 DisplayLeakService进行显示处理。其实就是将HeapDump文件和分析结果保存到文件中,将保存的文件路径传递给 DisplayLeakService,接下就是 DisplayLeakService的工作了。主要在它的onHeapAnalyzed
中:
1 | protected final void onHeapAnalyzed( { AnalyzedHeap analyzedHeap) |
其实到DisplayLeakActivity内存泄漏的分析工作就已经结束了,后面就是泄漏信息的展示了,这里不作赘述。
Fragment泄漏检测
有了上面Activity检测的经验,我才Fragment泄漏的检测原理应该也差不多,这里把整个流程快速过一遍。
FragmentRefWatcher.Helper.install(Context context, RefWatcher refWatcher)
- 负责添加
FragmentRefWatcher
; - 负责注册
Activity
生命周期回调,并在onActivityCreated
中调用FragmentRefWatcher
的watchFragments(Activity activity)
方法; watchFragments
中根据Activity
获取到对应的FragmentManager
,然后通过manager注册FragmentLifecycleCallbacks
回调;- 在FragmentLifecycleCallbacks中,监听
onFragmentViewDestroyed
和onFragmentDestroyed
方法,两个都会调用RefWatcher的watch
方法,前者watch的是View,后者watch的时Fragment - 后面的流程就和Activity 泄漏检测相同了。
2、LeakCanary是如何处理泄漏结果的?
- 手动触发GC来dump文件,根据添加的对象的引用情况,来判断是否有内存泄漏发生;
- 如果有内存泄漏发生,会采用DisplayLeakService来进行分析;
- 分析是找到最短泄漏路径,组装好数据,交给UI层来处理;
常用名词解释
- RefWatcher
即 Watcher Reference,用来监听弱引用的 - RefWatcherBuilder 和 AndroidRefWatchBuilder
用来进行RefWatcher的构造,好多参数都有默认的对应的defaultXXX实现。 - IdleHandler
通过王主线程消息队列中添加IdleHandler,已到达在主线程空闲时来进行内存泄漏分析的目的,这里有一个延时,时间是5s。 - IntentService
LeakCanary
中大量的用到了IntentService
,如HeapAnalyzerService
和DisplayLeakService
,前者分析HeapDump文件并得到分析结果最终保存到文件,后者解析分析到的结果并开启DisplayLeakActivity最终在UI上进行展示。IntentService会在创建后,开启一个HandlerThread,这就可以很好的进行分析文件、处理分析结果这些耗时任务了。
几个问题
Dumping memory, app will freeze. Brrr是如何产生的?
Dumping memory, app will freeze. Brrr
这句话是AndroidHeapDumper
的showToast
方法里面输出的,里面采用CountDownLatch
,让其他线程等待5秒,起到冻结App的作用LeakCanary桌面上的Leak图标和Block图标是如何产生的?
看leakcanary-android包下面的AndroidManifest.xml文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14<activity
android:name="com.squareup.leakcanary.internal.DisplayLeakActivity"
android:enabled="false"
android:icon="@mipmap/leak_canary_icon"
android:label="@string/leak_canary_display_activity_label"
android:process=":leakcanary"
android:taskAffinity="com.squareup.leakcanary.${applicationId}"
android:theme="@style/leak_canary_LeakCanary.Base" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>看
DisplayLeakActivity
的声明就可以知道了,它定义了显示icon,并且是一个android.intent.action.MAIN
,同时还是android.intent.category.LAUNCHER
的分析结果的类型
分析的结果有三种,在AnalysisResult中定义
- noLeak,没有泄漏
- leakDetected,有泄漏,此时也分多种情况:
- 发现的泄漏是已知的被排除在外的,可以通过exclude关键字来判断,这种泄漏可以忽略不处理;
- 发现的泄漏是未知的,此时需要对泄漏进行处理,统计泄漏的信息;
- failure,泄漏检测失败
LeakCanary如何确保调用gc后就会触发弱引用回收的?
1
2
3
4
5
6
7
8
9public void runGc() {
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is more likely to perfom a gc.
Runtime.getRuntime().gc();//调用Runtime.gc()来执行GC操作
enqueueReferences();//等待100ms中,等待弱引用对象进入引用队列中
System.runFinalization();//执行对象的finalize()方法
}其实,gc的调用并不是有你是否调用System.gc()或者Runtime.getRuntime().gc()决定的,调用这两个方法知识告诉JVM需要GC,JVM的GC是周期性自动运行的,是有JVM决定运行时机。
排除一些已知内存泄漏的方法?
自定义ExcludeRef,其实有一个默认的实现可供参考。
小结
本文通过跟踪源码,了解到了LeakCanary内存泄漏检测的基本原理,并对之前自己存在的几点疑问都做了一些简单的解答。