LeakCanary 源码分析

1. 前言

LeakCanary 是由 Square 开发的一款内存泄露检测工具。相比与用 IDE dump memory 的繁琐,它以轻便的日志被广大开发者所喜好。让咱们看看它是如何实现的吧。java

ps: Square 以著名框架 Okhttp 被广大开发者所熟知。api

2. 源码分析

2.1 设计架构

分析一个框架,咱们能够尝试先分层。好的框架层次清晰,像TCP/IP那样,一层一层的封装起来。这里,我按照主流程大体分了一下。架构

一图流,你们能够参考这个图,来跟源码。 oracle

java_concurrent

2.2 业务层

按照教程,咱们一般会有以下初始化代码:app

  1. Applicaion 中:mRefWatcher = LeakCanary.install(this);
  2. 基类 Activity/Fragment onDestory() 中: mRefWatcher.watch(this);

虽然是用户端的代码,不过做为分析框架的入口,不妨称为业务层。框架

这一层咱们考虑的是检测咱们的业务对象 Activity。固然你也能够用来检测 Service。dom

2.3 Api层

从业务层切入,咱们引出了两个类LeakCanaryRefWatcher,组成了咱们的 api 层。异步

这一层咱们要考虑如何对外提供接口,并隐藏内部实现。一般会使用 Builder单例、适当的包私有权限ide

2.3.1 主线1 install()

public final class LeakCanary {

  public static @NonNull RefWatcher install(@NonNull Application application) {
    return refWatcher(application)
        .listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }
  
  public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
    return new AndroidRefWatcherBuilder(context);
  }
}
复制代码

咱们先看install(),先拿到一个RefWatcherBuilder,转而使用Builder模式构造一个RefWatcher做为返回值。 大概能够知道是框架的一些初始配置。忽略其余,直接看buildAndInstall()工具

public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {
  ...
  private boolean watchActivities = true;
  private boolean watchFragments = true;
  
  public @NonNull RefWatcher buildAndInstall() {
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      ...
      if (watchActivities) { // 1
        ActivityRefWatcher.install(context, refWatcher);
      }
      if (watchFragments) {  // 2
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    return refWatcher;
  }
}
复制代码

能够看到 1, 2 两处,默认行为是,监控 Activity 和 Fragment。 以 Activity为例:

public final class ActivityRefWatcher {

  public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
    ...
    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          refWatcher.watch(activity);
        }
      };
}
复制代码

使用了Application.ActivityLifecycleCallbacks,看来咱们基类里的watch()是多余的。Fragment 也是相似的,就不分析了,使用了FragmentManager.FragmentLifecycleCallbacks

PS: 老版本默认只监控 Activity,watchFragments 这个字段是 2018/6 新增的。

2.3.2 主线2 watch()

以前的分析,引出了RefWatcher.watch(),它能够检测任意对象是否正常销毁,不仅仅是 Activity。咱们来分析看看:

public final class RefWatcher {

  private final WatchExecutor watchExecutor;

  public void watch(Object watchedReference, String referenceName) {
    ...
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);

    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(),咱们能够注意到这几点:

  1. 为了避免阻塞咱们的onDestory(),特地设计成异步调用——WatchExecutor
  2. 有一个弱引用 KeyedWeakReference,干吗用的呢?

咱们该怎么设计 WatchExecutor 呢?AsyncTask?线程池?咱们接着往下看

2.4 日志产生层

如今咱们来到了很是关键的一层,这一层主要是分析是否泄露,产物是.hprof文件。 咱们日常用 IDE dump memory 的时候,生成的也是这种格式的文件。

2.4.1 WatchExecutor 异步任务

接以前的分析,WatchExecutor主要是用于异步任务,同时提供了失败重试的机制。

public final class AndroidWatchExecutor implements WatchExecutor {
  private final Handler mainHandler;
  private final Handler backgroundHandler;

  public AndroidWatchExecutor(long initialDelayMillis) {
    mainHandler = new Handler(Looper.getMainLooper());
    HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
    handlerThread.start();
    backgroundHandler = new Handler(handlerThread.getLooper());
    ...
  }

  @Override public void execute(@NonNull Retryable retryable) {
    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
      waitForIdle(retryable, 0);
    } else {
      postWaitForIdle(retryable, 0);
    }
  }
  ...
}
复制代码

看来是使用了HandlerThread。没啥说的,要注意一会儿线程Handler的使用方式。以后便会回调ensureGone(),注意此时执行环境已经切到子线程了。

2.4.2 ReferenceQueue 检测泄露

分析下一步以前,咱们先介绍一下 ReferenceQueue

  1. 引用队列 ReferenceQueue 做为参数传入 WeakReference.
  2. WeakReference 中的 value 变得不可达,被 JVM 回收以前,WeakReference 会被加到该队列中,等待回收。

说白了,ReferenceQueue 提供了一种通知机制,以便在 GC 发生前,咱们能作一些处理。

详见 Reference 、ReferenceQueue 详解

好了,让咱们回到 RefWatcher。

final class KeyedWeakReference extends WeakReference<Object> {
  public final String key;		// 因为真正的 value 正等待回收,咱们追加一个 key 来识别目标。
  public final String name;

  KeyedWeakReference(Object referent, String key, String name,
      ReferenceQueue<Object> referenceQueue) {
    super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
    this.key = checkNotNull(key, "key");
    this.name = checkNotNull(name, "name");
  }
}

public final class RefWatcher {
  private final Set<String> retainedKeys;  // 保存未回收的引用的 key。 watch()时 add, 在 queue 中找到则 remove。
  private final ReferenceQueue<Object> queue; // 收集全部变得不可达的对象。

  public void watch(Object watchedReference, String referenceName) {
    ...
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);  // 1
    final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);

    ensureGoneAsync(watchStartNanoTime, reference);
  }

  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    removeWeaklyReachableReferences();	// 2
    ...
    if (gone(reference)) {  // 3
      return DONE;
    }
    gcTrigger.runGc();	// 4
    removeWeaklyReachableReferences();	// 5
    if (!gone(reference)) {	// 6
      // 发现泄漏
      ...
    }
    return DONE;
  }
  
  private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
  }

  private void removeWeaklyReachableReferences() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
      retainedKeys.remove(ref.key);
    }
  }
}
复制代码

咱们有这样的策略:用retainedKeys保存未回收的引用的 key。

  1. 主线程 onDestroy() -> watch() -> retainedKeys.add(ref.key)。WatchExecutor 启动,主线程 Activity 销毁。
  2. WatchExecutor.execute() -> ensureGone() -> removeWeaklyReachableReferences() -> 遍历 ReferenceQueue,从 retainedKeys.remove(ref.key)
  3. 判断 gone(ref), 若是 Activity 已经不可达,那么直接返回,不然可能有内存泄漏。

4-6. 引用还在,然而这里没有当即断定为泄漏,而是很谨慎的手动触发 gc,再次校验。

2.4.3 GcTrigger 手动触发 Gc

这里注意一点 Android 下边的 jdk 和 oracle 公司的 jdk 在一些方法的实现上有区别。好比这个 System.gc()就被改了,再也不保证一定触发 gc。做者使用Runtime.getRuntime().gc()做为代替。

了解更多:System.gc() 源码解读

2.4.4 HeapDumper 生成堆快照 .hprof

public final class RefWatcher {
  private final HeapDumper heapDumper;
  private final HeapDump.Listener heapdumpListener;

  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    ...
    gcTrigger.runGc();	
    removeWeaklyReachableReferences();	
    if (!gone(reference)) {	
      // 发现泄漏
      File heapDumpFile = heapDumper.dumpHeap();

      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile)
          ...
          .build();

      heapdumpListener.analyze(heapDump);
    }
    return DONE;
  }
}
复制代码

咱们跟进 heapDumper.dumpHeap(),略去一些 UI 相关代码:

public final class AndroidHeapDumper implements HeapDumper {

  @Override @Nullable 
  public File dumpHeap() {
    File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
    ...
    try {
      Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
      ...
      return heapDumpFile;
    } catch (Exception e) { ... }
  }
}
复制代码

最后用了 Android 原生的 api —— Debug.dumpHprofData(),生成了堆快照。

2.5 日志分析层 && 日志展现层

生成 .hprof 以后,以后由 heapdumpListener.analyze(heapDump) 把数据转到下一层。其实这两层没啥好分析的,.hprof 已是标准的堆快照格式,平时用 AS 分析内存生成的也是这个格式。

因此,LeakCanary 在这一层只是帮咱们读取了堆中的引用链。而后,日志展现层也没啥说的,就一个 ListView。

3. 总结

最后,咱们能够看到一个优秀的框架须要那些东西:

分层

  • 分层的意义在于逻辑清晰,每一层的任务都很明确,尽可能避免跨层的依赖,这符合单一职责的设计原则。
  • 对于使用者来讲,只用关心api层有哪些接口以及业务层怎么使用;而对于维护者来讲,不少时候只须要关心核心逻辑日志产生层,UI层不怎么改动丑一点也不要紧。方便使用也方便维护。

ReferenceQueue 的使用

  • 学到了如何检测内存回收状况,而且作一些处理。之前只会傻傻的new WeakReference()

手动触发 gc

  • Runtime.getRuntime().gc() 是否能当即触发 gc,这点感受也比较含糊。这是一个 native 方法,依赖于 JVM 的实现,深究起来须要去看 Dalvik 的源码,先打一个问号。
  • 框架中 gc 的这段代码是从 AOSP 里拷贝出来的。。。因此说,多看源码是个好习惯。

leakcanary-no-op

  • release 版提供一个空实现,能够学习一下。
相关文章
相关标签/搜索