LeakCanary 是Android开发中常用的内存泄漏检测工具。
我们以Activity的监控为例,分析下是如何做泄漏分析的。
Activity监控
首先看LeakCanary初始化时做的操作:
/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
buildAndInstall 方法里面启动了ActivityRefWatcher:
/**
* Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
*/
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
ActivityRefWatcher里面有对Activity生命周期的监控:
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override public void onActivityStarted(Activity activity) {
}
@Override public void onActivityResumed(Activity activity) {
}
@Override public void onActivityPaused(Activity activity) {
}
@Override public void onActivityStopped(Activity activity) {
}
@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
private final RefWatcher refWatcher;
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
所以RefWatcher里面是对象引用监控的核心代码。下面我们来看下RefWarcher里面具体是如何做监控的。
RefWatcher 如何工作
RefWatcher属于LeakCanary 的leakcanary-watcher包下,是Java通用而非Android特有。
RefWarcher 提供watch方法,在对象被回收之后调用RefWatch的watch方法即可监控对象是否发生了泄漏。
private final ReferenceQueue<Object> queue;
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
补充知识: ReferenceQueue
Reference queues, to which registered reference objects are appended by the garbage collector after the appropriate reachability changes are detected.
可以简单的理解为,创建对象的WeakReference时,可以传入一个ReferenceQueue,当对象变为仅弱引用可达时,会将WeakReference的包装对象放入这个队列。可以通过检查ReferenceQueue队列中是否存在WeakReference这个包装对象来判断一个实例是否被回收。
LeakCanary 通过以下流程判断是否发生内存泄漏:
RefWatch.java 中 ensureGone 的流程HAHA分析Dump的结果会通过回调的形式返回。可以得到泄漏的对象的引用路径。
PS.
HAHA(Headless Android Heap Analyzer)是Square出的Dump分析工具。
检测的时机
RefWatcher 的 watch 方法会异步做对象是否被回收的检测。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
可以看到,其实调用是通过WatchExecutor watchExecutor来调用的。我们关注下WatchExecutor的Android实现类AndroidWatchExecutor:
@Override public void execute(Retryable retryable) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}
void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
可以看到都是在主线程调用的 Looper.myQueue().addIdleHandler()方法。
补充知识2 MessageQueue.addIdleHandler
/**
* Callback interface for discovering when a thread is going to block
* waiting for more messages.
*/
public static interface IdleHandler {
/**
* Called when the message queue has run out of messages and will now
* wait for more. Return true to keep your idle handler active, false
* to have it removed. This may be called if there are still messages
* pending in the queue, but they are all scheduled to be dispatched
* after the current time.
*/
boolean queueIdle();
}
MessageQueue.addIdleHandler 可以简单理解为MQ提供的一个接口,允许MQ在队列空闲的时候做些事情。比如在空闲的时候做个GC啊。
补充知识3 如何手动GC
直接上代码:
@Override public 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();
enqueueReferences();
System.runFinalization();
}
白名单机制
RefWatcher 构造器支持设计白名单。
/** @see ExcludedRefs */
public final T excludedRefs(ExcludedRefs excludedRefs) {
this.excludedRefs = excludedRefs;
return self();
}
目前Android系统级别的一些内存泄漏已经加入了默认的白名单中AndroidExcludedRefs.java。
用户也可以根据自己的需要自创建ExcludedRefs的实例添加到白名单Set中。
结果展示
Heap Dump 分析成功后会回调 onHeapAnalyzed 方法。
/**
* Called after a heap dump is analyzed, whether or not a leak was found.
* Check {@link AnalysisResult#leakFound} and {@link AnalysisResult#excludedLeak} to see if there
* was a leak and if it can be ignored.
*
* This will be called from a background intent service thread.
* <p>
* It's OK to block here and wait for the heap dump to be uploaded.
* <p>
* The heap dump file will be deleted immediately after this callback returns.
*/
protected abstract void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result);
默认的实现类 是 DisplayLeakService。它会把Leak信息默认显示成通知格式。
当然也提供了供用户重写的方法,允许用户对检测结果做后续操作,保存\上报等。
/**
* You can override this method and do a blocking call to a server to upload the leak trace and
* the heap dump. Don't forget to check {@link AnalysisResult#leakFound} and {@link
* AnalysisResult#excludedLeak} first.
*/
protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
}
总结
LeakCanary核心思想是通过ReferenceQueue来检测对象是否被回收。在主线程空闲时才会做检测。 有必要的情况下会先手动出发GC。GC后还有引用会Dump Heap,通过HAHA做分析获取引用路径。
网友评论