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

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

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

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

LeakCanary 使用方式

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

dependencies {
 debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1'
 releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1'
 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.
      // You should not init your app in this process.
      return;
    }
    LeakCanary.install(this);
  }
}

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

LeakCanary.install(this); 开始

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

  public static RefWatcher install(Application application) {
    return install(application, DisplayLeakService.class,
        AndroidExcludedRefs.createAppDefaults().build());
  }

  public static RefWatcher install(Application application,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass,
      ExcludedRefs excludedRefs) {
    if (isInAnalyzerProcess(application)) {
      return RefWatcher.DISABLED;
    }
    enableDisplayLeakActivity(application);
    HeapDump.Listener heapDumpListener =
        new ServiceHeapDumpListener(application, listenerServiceClass);
    RefWatcher refWatcher = androidWatcher(application, heapDumpListener, excludedRefs);
    ActivityRefWatcher.installOnIcsPlus(application, refWatcher);
    return refWatcher;
  }

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

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

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

public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
    activityRefWatcher.watchActivities();
}

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

public void watchActivities() {
  application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}

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

  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);
        }
      };

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

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

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

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

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

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

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

  public static RefWatcher androidWatcher(Context context, HeapDump.Listener heapDumpListener,
      ExcludedRefs excludedRefs) {
    AndroidHeapDumper heapDumper = new AndroidHeapDumper(context, leakDirectoryProvider);
    heapDumper.cleanup();

    int watchDelayMillis = 5000;
    AndroidWatchExecutor executor = new AndroidWatchExecutor(watchDelayMillis);

    return new RefWatcher(executor, debuggerControl, GcTrigger.DEFAULT, heapDumper,
        heapDumpListener, excludedRefs);
  }

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

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

  private final Set<String> retainedKeys;
  public void watch(Object activity, String referenceName) {
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);

    final KeyedWeakReference reference =
        new KeyedWeakReference(activity, key, referenceName, queue);

    watchExecutor.execute(new Runnable() {
      @Override public void run() {
        ensureGone(reference, watchStartNanoTime);
      }
    });
  }

  final class KeyedWeakReference extends WeakReference<Object> {
    public final String key;
    public final String name;
  }

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

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

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

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

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

下面来看这个函数实现:
```
void ensureGone(KeyedWeakReference reference, long watchStartNanoTime) {
removeWeaklyReachableReferences();
if (gone(reference) || debuggerControl.isDebuggerAttached()) {
return;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if (!gone(reference)) {
File heapDumpFile = heapDumper.dumpHeap();
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
}

private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}

top Created with Sketch.