LeakCanary源码解析之检测篇
源码分析基于: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的检测原理了吧。
后续将在下一篇文章中分析该框架是怎么分析内存泄漏的,敬请期待~
更多相关文章
- 安卓修改屏幕超时后,系统不进入休眠的方法。
- 初学Android的一些注意事项
- 【Android】项目常用功能集锦(一)
- Android(安卓)Thread第二次Thread.start()报错的疑问
- android跟服务器使用json传递数据
- android实现倒计时功能的方法
- Android获取手机SIM卡运营商信息的方法
- Android异步更新UI的方式之使用Handler的post(Runnabel r)方法
- Android(安卓)利用Gson生成或解析json