带你学开源项目:LeakCanary-如何检测 Activity 是否泄漏

OOM 是 Android 开发中常见的问题,而内存泄漏往往是罪魁祸首。

为了简单方便的检测内存泄漏,Square 开源了 LeakCanary ,它可以实时监测 Activity 是否发生了泄漏,一旦发现就会自动弹出提示及相关的泄漏信息供分析。

本文的目的是试图通过分析 LeakCanary 源码来探讨它的 Activity 泄漏检测机制。

LeakCanary 使用方式

为了将 LeakCanary 引入到我们的项目里,我们只需要做以下两步:

 
 
 
 
  1. dependencies { 
  2.  debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1' 
  3.  releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1' 
  4.  testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'}public class ExampleApplication extends Application {  @Override public void onCreate() {    super.onCreate();    if (LeakCanary.isInAnalyzerProcess(this)) {      // This process is dedicated to LeakCanary for heap analysis. 
  5.       // You should not init your app in this process. 
  6.       return
  7.     } 
  8.     LeakCanary.install(this); 
  9.   } 

 可以看出,最关键的就是 LeakCanary.install(this); 这么一句话,正式开启了 LeakCanary 的大门,未来它就会自动帮我们检测内存泄漏,并在发生泄漏是弹出通知信息。

从 LeakCanary.install(this); 开始

下面我们来看下它做了些什么?

 
 
 
 
  1. public static RefWatcher install(Application application) {  return install(application, DisplayLeakService.class, 
  2.       AndroidExcludedRefs.createAppDefaults().build()); 
  3. }public static RefWatcher install(Application application, 
  4.     Class<? extends AbstractAnalysisResultService> listenerServiceClass, 
  5.     ExcludedRefs excludedRefs) {  if (isInAnalyzerProcess(application)) {    return RefWatcher.DISABLED; 
  6.   } 
  7.   enableDisplayLeakActivity(application); 
  8.   HeapDump.Listener heapDumpListener =      new ServiceHeapDumpListener(application, listenerServiceClass); 
  9.   RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs); 
  10.   ActivityRefWatcher.installOnIcsPlus(application, refWatcher);  return refWatcher; 
  11.  

首先,我们先看最重要的部分,就是:

 
 
 
 
  1. RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs); 
  2. ActivityRefWatcher.installOnIcsPlus(application, refWatcher);  

先生成了一个 RefWatcher ,这个东西非常关键,从名字可以看出,它是用来 watch Reference 的,也就是用来一个监控引用的工具。然后再把 refWatcher 和我们自己提供的 application 传入到 ActivityRefWatcher.installOnIcsPlus(application, refWatcher); 这句里面,继续看。

 
 
 
 
  1. public static void installOnIcsPlus(Application application, RefWatcher refWatcher) { 
  2.     ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher); 
  3.     activityRefWatcher.watchActivities(); 
  4.  

创建了一个 ActivityRefWatcher ,大家应该能感受到,这个东西就是用来监控我们的 Activity 泄漏状况的,它调用 watchActivities() 方法,就可以开始进行监控了。下面就是它监控的核心原理:

 
 
 
 
  1. public void watchActivities() { 
  2.   application.registerActivityLifecycleCallbacks(lifecycleCallbacks); 
  3.  

它向 application 里注册了一个 ActivitylifecycleCallbacks 的回调函数,可以用来监听 Application 整个生命周期所有 Activity 的 lifecycle 事件。再看下这个 lifecycleCallbacks 是什么?

 
 
 
 
  1. private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =    new Application.ActivityLifecycleCallbacks() {      @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) { 
  2.       }      @Override public void onActivityStarted(Activity activity) { 
  3.       }      @Override public void onActivityResumed(Activity activity) { 
  4.       }      @Override public void onActivityPaused(Activity activity) { 
  5.       }      @Override public void onActivityStopped(Activity activity) { 
  6.       }      @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) { 
  7.       }      @Override public void onActivityDestroyed(Activity activity) { 
  8.         ActivityRefWatcher.this.onActivityDestroyed(activity); 
  9.       } 
  10.     }; 

 原来它只监听了所有 Activity 的 onActivityDestroyed 事件,当 Activity 被 Destory 时,调用 ActivityRefWatcher.this.onActivityDestroyed(activity); 函数。

猜测下,正常情况下,当一个这个函数应该 activity 被 Destory 时,那这个 activity 对象应该变成 null 才是正确的。如果没有变成null,那么就意味着发生了内存泄漏。

因此我们向,这个函数 ActivityRefWatcher.this.onActivityDestroyed(activity); 应该是用来监听 activity 对象是否变成了 null。继续看。

 
 
 
 
  1. void onActivityDestroyed(Activity activity) { 
  2.   refWatcher.watch(activity); 
  3. RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);  

可以看出,这个函数把目标 activity 对象传给了 RefWatcher ,让它去监控这个 activity 是否被正常回收了,若未被回收,则意味着发生了内存泄漏。

RefWatcher 如何监控 activity 是否被正常回收呢?

我们先来看看这个 RefWatcher 究竟是个什么东西?

 
 
 
 
  1. public static RefWatcher androidWatcher(Context context, HeapDump.Listener heapDumpListener, 
  2.     ExcludedRefs excludedRefs) { 
  3.   AndroidHeapDumper heapDumper = new AndroidHeapDumper(context, leakDirectoryProvider); 
  4.   heapDumper.cleanup();  int watchDelayMillis = 5000; 
  5.   AndroidWatchExecutor executor = new AndroidWatchExecutor(watchDelayMillis);  return new RefWatcher(executor, debuggerControl, GcTrigger.DEFAULT, heapDumper, 
  6.       heapDumpListener, excludedRefs); 
  7.  

这里面涉及到两个新的对象: AndroidHeapDumper 和 AndroidWatchExecutor ,前者用来 dump 堆内存状态的,后者则是用来 watch 一个引用的监听器。具体原理后面再看。总之,这里已经生成好了一个 RefWatcher 对象了。

现在再看上面 onActivityDestroyed(Activity activity) 里调用的 refWatcher.watch(activity); ,下面来看下这个最为核心的 watch(activity) 方法,了解它是如何监控 activity 是否被回收的。

 
 
 
 
  1. private final Set<String> retainedKeys;public void watch(Object activity, String referenceName) { 
  2.   String key = UUID.randomUUID().toString(); 
  3.   retainedKeys.add(key);  final KeyedWeakReference reference =      new KeyedWeakReference(activity, key, referenceName, queue); 
  4.  
  5.   watchExecutor.execute(new Runnable() {    @Override public void run() { 
  6.       ensureGone(reference, watchStartNanoTime); 
  7.     } 
  8.   }); 
  9. }final class KeyedWeakReference extends WeakReference<Object> {  public final String key;  public final String name
  10.  

可以看到,它首先把我们传入的 activity 包装成了一个 KeyedWeakReference (可以暂时看成一个普通的 WeakReference),然后 watchExecutor 会去执行一个 Runnable,这个 Runnable 会调用 ensureGone(reference, watchStartNanoTime) 函数。

看这个函数之前猜测下,我们知道 watch 函数本身就是用来监听 activity 是否被正常回收,这就涉及到两个问题:

  1. 何时去检查它是否回收?
  2. 如何有效地检查它真的被回收?

所以我们觉得 ensureGone 函数本身要做的事正如它的名字,就是确保 reference 被回收掉了,否则就意味着内存泄漏。

核心函数:ensureGone(reference) 检测回收

下面来看这个函数实现:

 
 
 
 
  1. void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) { 
  2.   removeWeaklyReachableReferences();  if (gone(reference) || debuggerControl.isDebuggerAttached()) {    return
  3.   } 
  4.   gcTrigger.runGc(); 
  5.   removeWeaklyReachableReferences();  if (!gone(reference)) { 
  6.     File heapDumpFile = heapDumper.dumpHeap(); 
  7.     heapdumpListener.analyze(        new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs, 
  8.             gcDurationMs, heapDumpDurationMs)); 
  9.   } 
  10. }private boolean gone(KeyedWeakReference reference) {  return !retainedKeys.contains(reference.key); 
  11. }private void removeWeaklyReachableReferences() { 
  12.   KeyedWeakReference ref;  while ((ref = (KeyedWeakReference) queue.poll()) != null) { 
  13.     retainedKeys.remove(ref.key); 
  14.   } 
  15.  

这里先来解释下 WeakReference 和 ReferenceQueue 的工作原理。

     1.弱引用 WeakReference

 被强引用的对象就算发生 OOM 也永远不会被垃圾回收机回收;被弱引用的对象,只要被垃圾回收器发现就会立即被回收;被软引用的对象,具备内存敏感性,只有内存不足时才会被回收,常用来做内存敏感缓存器;虚引用则任意时刻都可能被回收,使用较少。

     2.引用队列 ReferenceQueue

我们常用一个 WeakReference<Activity> reference = new WeakReference(activity); ,这里我们创建了一个 reference 来弱引用到某个 activity ,当这个 activity 被垃圾回收器回收后,这个 reference 会被放入内部的 ReferenceQueue 中。也就是说,从队列 ReferenceQueue 取出来的所有 reference ,它们指向的真实对象都已经成功被回收了。

然后再回到上面的代码。

在一个 activity 传给 RefWatcher 时会创建一个***的 key 对应这个 activity,该key存入一个集合 retainedKeys 中。也就是说,所有我们想要观测的 activity 对应的*** key 都会被放入 retainedKeys 集合中。

基于我们对 ReferenceQueue 的了解,只要把队列中所有的 reference 取出来,并把对应 retainedKeys 里的key移除,剩下的 key 对应的对象都没有被回收。

  1. ensureGone 首先调用 removeWeaklyReachableReferences 把已被回收的对象的 key 从 retainedKeys 移除,剩下的 key 都是未被回收的对象;
  2. if
THE END