内存检测工具一直在用,但是它的原理却只是一知半解,看了遍源码同时站在巨人的肩膀捋了一遍思路。发现前人分析的源码与当前新码有所不同,但是精髓原理是一致的,所以总结记录下。
1、使用方法简单,引入依赖,在Application中install就OK;
2、内部处理其实是在Application中regist了一个Activity生命周期的回调,监听了Activity在onDestroy状态;
3、通过一系列引用存储、手动GC立即执行(调用系统GC不确定什么时候执行)、判断引用是否存在来确定Activity是否内存泄漏;
4、发出通知,将泄漏点显示在UI上。
具体看代码:
public static RefWatcher install(Application application) { return refWatcher(application).listenerServiceClass(DisplayLeakService.class) .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) .buildAndInstall(); }
以前的版本是在install方法中实例化RefWatcher,现在是用build模式在最后buildAndInstall方法中处理。
public RefWatcher buildAndInstall() { RefWatcher refWatcher = build(); if (refWatcher != DISABLED) { LeakCanary.enableDisplayLeakActivity(context); ActivityRefWatcher.install((Application) context, refWatcher); } return refWatcher; }
在build方法中返回一个RefWatcher,
public final RefWatcher build() { if (isDisabled()) { return RefWatcher.DISABLED; } ExcludedRefs excludedRefs = this.excludedRefs; if (excludedRefs == null) { excludedRefs = defaultExcludedRefs(); } HeapDump.Listener heapDumpListener = this.heapDumpListener; if (heapDumpListener == null) { heapDumpListener = defaultHeapDumpListener(); } DebuggerControl debuggerControl = this.debuggerControl; if (debuggerControl == null) { debuggerControl = defaultDebuggerControl(); } HeapDumper heapDumper = this.heapDumper; if (heapDumper == null) { heapDumper = defaultHeapDumper(); } WatchExecutor watchExecutor = this.watchExecutor; if (watchExecutor == null) { watchExecutor = defaultWatchExecutor(); } GcTrigger gcTrigger = this.gcTrigger; if (gcTrigger == null) { gcTrigger = defaultGcTrigger(); } return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener, excludedRefs); }
插一句:RefWatcher到底是什么东西?其实他是一个监视器,监视Reference引用的。
在install中,用到一个ActivityRefWatcher类,这个类有点关键,
public static void install(Application application, RefWatcher refWatcher) { new ActivityRefWatcher(application, refWatcher).watchActivities(); }
重点看watchActivities方法,通过名称就知道是干嘛的,
public void watchActivities() { // Make sure you don't get installed twice. stopWatchingActivities(); application.registerActivityLifecycleCallbacks(lifecycleCallbacks); }
首先停止监听所有Activity,确保不会install两次,然后就调用了Application的注册监听方法,将生命周期回调传入Application中,如果生命周期发生变化,将会回调
public interface ActivityLifecycleCallbacks { void onActivityCreated(Activity activity, Bundle savedInstanceState); void onActivityStarted(Activity activity); void onActivityResumed(Activity activity); void onActivityPaused(Activity activity); void onActivityStopped(Activity activity); void onActivitySaveInstanceState(Activity activity, Bundle outState); void onActivityDestroyed(Activity activity); }
但是ActivityRefWatcher却只监听了onDestroy的回调
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);
}
};
void onActivityDestroyed(Activity activity) { refWatcher.watch(activity); }
然后这个refWatcher就是Application中install时,生成的监视工具,要对这个生命周期到达onDestroy的Activity进行处理。再看watch方法
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
public void watch(Object watchedReference, String referenceName) { ...... ensureGoneAsync(watchStartNanoTime, reference); }
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
这里watch的是一个Object类型哦,可不可以说LeakCanary可以监听所有Java中对象的内存泄漏呢(猜的)。
当Activity生命周期走到了onDestroy时,RefWatcher收到回调后什么时候去执行这个泄漏检测呢?
经过耐心寻找,终于找到了蛛丝马迹:
再AndroidRefWatcherBuilder中有个常量
private static final long DEFAULT_WATCH_DELAY_MILLIS = SECONDS.toMillis(5);
5秒的时间,看下它在哪里用到
@Override protected WatchExecutor defaultWatchExecutor() { return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS); }
new实例的时候传入的默认时间,AndroidWatchExecutor继承于WatchExecutor,构造传参接收了这个5秒默认时间,同时还创建了一个工作线程的Handler
public AndroidWatchExecutor(long initialDelayMillis) {
mainHandler = new Handler(Looper.getMainLooper());
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
handlerThread.start();
backgroundHandler = new Handler(handlerThread.getLooper());
this.initialDelayMillis = initialDelayMillis;
maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
}
在上面watch里会调用到extcutor.execute方法,看下有哪些操作
@Override public void execute(Retryable retryable) { if (Looper.getMainLooper().getThread() == Thread.currentThread()) { waitForIdle(retryable, 0); } else { postWaitForIdle(retryable, 0); } }
先判断当前线程是否为主线程,如果是就waitForIdle,这是啥,是一个主线程中的操作,但是不会阻塞线程,会在主线程处理完msg后空闲的回调,然后可以做耗时操作,renturn false只执行一次,return true一直执行!
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;
}
});
}
void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) { long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor); long delayMillis = initialDelayMillis * exponentialBackoffFactor; backgroundHandler.postDelayed(new Runnable() { @Override public void run() { Retryable.Result result = retryable.run(); if (result == RETRY) { postWaitForIdle(retryable, failedAttempts + 1); } } }, delayMillis); }
initialDelayMillis就是延时post的5秒钟,backgroundHandler就是工作线程handler,所以,监听到Activity走到onDestroy时先收到主线程空闲的回调,再执行一个5秒延迟的postDelay,才会去检测这个Activity是否内存泄漏。
回正题,RefWatcher如何检测Activity是否内存泄漏呢?
具体分析看下前人文章具体过程
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
处理过程中有个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();
}
调用Runtime.getRuntime().gc(),可以立即执行。
两个彩蛋:一个是知道了主线程监听线程空闲的回调;
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {}
一个是系统GC可以手动调用立即执行。