源码分析基于:1.6.3
对于Android开发者而言,内存泄漏是一种很常见的问题。LeakCanary就是捕获内存泄漏的一把利器。我们在这里就分析一下它的工作原理。
使用方法就是我们在Application中添加代码:
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not onStart your app in this process.
return;
}
LeakCanary.install(this);
看到这些估计你也会和我一样,这样我们就能捕获到内存泄露了么?不禁会产生这样的疑问。我们就带着疑问阅读一下源码寻找答案吧。
LeakCanary这个类是一个工具类,我们先看一下LeakCanary.install(this)这个方法的源码:
/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
该方法中refWatcher(application)方法获取AndroidRefWatcherBuilder实例对象;“.listenerServiceClass(DisplayLeakService.class)”方法是对内存泄漏分析结果的监听;“.buildAndInstall()”方法用于生成RefWatcher对象,关于该方法的详细代码如下:
/**
* Creates a {@link RefWatcher} instance and makes it available through {@link
* LeakCanary#installedRefWatcher()}.
*
* Also starts watching activity references if {@link #watchActivities(boolean)} was set to true.
*
* @throws UnsupportedOperationException if called more than once per Android process.
*/
public @NonNull RefWatcher buildAndInstall() {
//保证RefWatcher的唯一性
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
//如果没有创建过RefWatcher,则创建一个;
RefWatcher refWatcher = build();
//非空置对象(内存分析相关工具对象都是空壳对象)
if (refWatcher != DISABLED) {
//是否允许使用内置展示页面展示内存泄露信息
if (enableDisplayLeakActivity) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
//是否允许监测activity
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
//是否允许检测fragment
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
//RefWatcher对象赋值
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
在这里RefWatcher就是内存泄漏的监控器。该方法就是生成RefWatcher对象,该框架默认允许检测Activity和Fragment的内存泄漏,那么它又是怎么监控的呢?原理由是啥?以什么为判断内存泄漏的标准呢?
我们看一下ActivityRefWatcher中install()方法的逻辑:
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
//Application注册生命周期回调
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
lifecycleCallbacks是该类中的成员变量,通过注册生命周期回调RefWatcher就可以通过页面销毁的方法回调触发RefWatcher的watch()方法。我们先不管watch()方法中的具体逻辑,下面在分析一下fragment中的监测机制,下面我们分析一下FragmentRefWatcher.Helper这个内部类的源码:
final class Helper {
private static final String SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME =
"com.squareup.leakcanary.internal.SupportFragmentRefWatcher";
public static void install(Context context, RefWatcher refWatcher) {
List fragmentRefWatchers = new ArrayList<>();
//大于26的Android版本使用AndroidOFragmentRefWatcher
if (SDK_INT >= O) {
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
}
//否则使用SupportFragmentRefWatcher
try {
Class> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
Constructor> constructor =
fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
FragmentRefWatcher supportFragmentRefWatcher =
(FragmentRefWatcher) constructor.newInstance(refWatcher);
fragmentRefWatchers.add(supportFragmentRefWatcher);
} catch (Exception ignored) {
}
if (fragmentRefWatchers.size() == 0) {
return;
}
Helper helper = new Helper(fragmentRefWatchers);
Application application = (Application) context.getApplicationContext();
//注册生命周期回调
application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
//对应的FragmentManager注册回调
watcher.watchFragments(activity);
}
}
};
private final List fragmentRefWatchers;
private Helper(List fragmentRefWatchers) {
this.fragmentRefWatchers = fragmentRefWatchers;
}
}
该类中主要就是通过Activity的生命周期回调的onActivityCreated()方法,来获取activity实例对象,具体需要分析watcher.watchFragments(activity)中的逻辑,由于存在两种fragment对应的watcher,我们在这里只分析SupportFragmentRefWatcher中对应的代码逻辑。
@Override public void watchFragments(Activity activity) {
if (activity instanceof FragmentActivity) {
FragmentManager supportFragmentManager =
((FragmentActivity) activity).getSupportFragmentManager();
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
}
方法中获取FragmentManager实例对象,并注册生命周期方法。对于Android版本大于26的,请阅读AndroidOFragmentRefWatcher中的相关源码,原理是一致的。通过上面的源码我们分析得知,对于Activity而言,是在activity的onDestory()方法内进行监测的;对于fragment而言,在生命周期回调方法内的onFragmentViewDestroyed()和onFragmentDestroyed()方法内进行监测,在这里onFragmentViewDestroyed()方法对应于Fragment的onDestroyView()方法,onFragmentDestroyed()方法对应于Fragment#onDestroy()方法。
我们知道了LeakCanary的监测位置了,我们下面就通过源码分析一下它是怎么做到监测内存泄漏的。下面是RefWatcher类中的watch()方法的源码:
/**
* Watches the provided references and checks if it can be GCed. This method is non blocking,
* the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
* with.
*
* @param referenceName An logical identifier for the watched object.
*/
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
//生成随机的UUID作为该引用对象的key值
String key = UUID.randomUUID().toString();
//添加的集合中
retainedKeys.add(key);
//保存引用信息
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
//判断引用是否存在
ensureGoneAsync(watchStartNanoTime, reference);
}
在该方法中,将之前生命周期中的方法中拿到的activity对象实例或者fragment对象实例进行保存并进行相关的处理,在保存时会生成一个唯一的key值,后面会通过key值进行相应的业务逻辑。下面我们看一下ensureGoneAsync()方法相关的逻辑,源码如下:
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
该方法中watchExecutor是AndroidWatchExecutor的一个具体实例对象,最终是通过该实现类的execute()方法去执行引用相关的逻辑,这部分后面会分析。同时需要注意在excute()方法的内部类中run()方法会通过ensureGone()方法返回Retryable.Result的值。这个值会在后面的分析中用到。我们在这里先看一下AndroidWatchExecutor的具体实现。AndroidWatchExecutor是WatchExecutor接口的具体实现类,该接口主要用来执行Rertyable对象,并根据需要进行重试操作。我们看一下AndroidWatchExecutor的源码:
/**
* {@link WatchExecutor} suitable for watching Android reference leaks. This executor waits for the
* main thread to be idle then posts to a serial background thread with the delay specified by
* {@link AndroidRefWatcherBuilder#watchDelay(long, TimeUnit)}.
*/
public final class AndroidWatchExecutor implements WatchExecutor {
static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";
private final Handler mainHandler;
private final Handler backgroundHandler;
private final long initialDelayMillis;
private final long maxBackoffFactor;
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;
}
@Override public void execute(@NonNull Retryable retryable) {
//当前线程是否为主线程
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}
private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}
private 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;
}
});
}
private 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);
}
}
AndroidWatchExecutor 是Android平台中内存泄漏的监测工具类。该类的主要方法就是execute()方法,其主要逻辑是判断当前正在执行的线程是否为主线程,如果是主线程,则通过IdleHandler监测主线程是否空闲,当空闲的时候,执行postToBackgroundWithDelay方法,在此方法中,先通过计算exponentialBackoffFactor(补偿因子),并通过该因子计算出需要延迟的时间(delayMillis ),说实话为什么这么算没想明白。最后通过backgroundHandler延迟执行一条线程去获取Retryable.Result的结果,通过是否需要重试也就是“result == RETRY”来判断是否需要继续postWaitForIdle方法,同时在重试的时候failedAttempts参数会加1。那么我们不禁会思考,retryable.run()的结果,也就是Retryable.Result是在什么时候改变的呢?这个我们在RefWatcher中的ensureGoneAsync()方法的分析中提到,result的值是在ensureGone()方法返回的。好了就是那么清晰。当正在执行的线程非主线程的的时候postWaitForIdle()方法被执行,这里就不再说明了。
下面我们看一下RefWatcher中ensureGone()方法的源码:
@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
//GC开始检测时间
long gcStartNanoTime = System.nanoTime();
//从开始watch到GC开始检测的时间差
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
//移除已经被GC的引用信息
removeWeaklyReachableReferences();
//如果调试模式,则后续重试
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
//如果不包含应用信息,则表示监测已经完成,没有发生泄漏;
if (gone(reference)) {
return DONE;
}
//触发GC
gcTrigger.runGc();
//再次确认移除已经被GC的引用信息
removeWeaklyReachableReferences();
//如果队列中还存在引用信息,
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//使用Dumper类dump当前堆内存中的对象信息
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
//将hprof文件和reference引用信息构造HeapDump对象
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
//分析dump信息
heapdumpListener.analyze(heapDump);
}
return DONE;
}
要理解LeackCanary是怎么定义内存泄漏的呢?此方法就是关键。同时不了解Java中的弱引用,强引用的概念的可以查阅一下这方面的资料,这里就不在进行过多的解释了。首先该方法先调用了removeWeaklyReachableReferences()方法,该方法就是移除已经被GC的引用信息,在该方法里就是看queue队列中是否还存在引用的信息KeyedWeakReference,如果存在就从retainedKeys中进行移除。在RefWatcher类中的watch()方法中已经将观察对象watchReference对应的key保存在retainedKeys中,同时关联到了ReferenceQueue的实例对象queue。当弱引用持有的对象被GC之后,与之关联的引用信息KeyedWeakReference的对象reference就会被添加到关联的队列中queue.所以可以通过queue中是否存在对应的引用信息来判断是否发生内存泄漏。下面说一下为什么在开启Debug模式下需要返回RETRY,那是因为在该模式下对象引用的时间会变长。我们继续向下分析,gone(refreence)方法就是看retainedKeys是否包含相应的引用的信息,如果不存在则说明就完成了GC,本次检测也告一段落。否则, 通过gcTrigger.runGc()手动触发一次GC,并在此通过removeWeaklyReachableReferences()方法进行引用信息的移除。如果还存在该引用信息那么说明app当前发生了内存泄漏。后续就是通过heapDumper.dumpHeap()将当前堆内存中的信息保存下来,并通过heapdumpListener.analyze(heapDump)分析获取的dump信息。到此,相信大家就清楚了我们Android开发中内存泄漏检测神器LeakCanary的检测原理了吧。
后续将在下一篇文章中分析该框架是怎么分析内存泄漏的,敬请期待~