LeakCannary 源码分析

313 阅读3分钟

用 LeakCannary 也不短时间了,源码还没仔细研究过,很好奇他是如何监听内存泄漏,并且能显示到界面上的过程,所以我就带着问题去查看源码来一探究竟,来满足我的好奇心。

  • 如何监听内存泄漏,具体流程是什么?

首先我们建立一个简单的项目,按照官方的文档,我们开始在 Application 中初始化,然后调用LeakCanary 的install的方法。

public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        LeakCanary.install(this);
    }
}

下边我们追踪一下 LeakCanary里边的方法install() 方法,我们看到了我们所熟悉的 builder 模式,开始构造方法,最终调用buildAndInstall()返回RefWatcher。

public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }

为了我们开始追踪源码buildAndInstall() 方法,最终会追踪到ActivityrefWathch,然后我们仔细分析一下这个,如何和生命周期互相绑定的呢,我们分析一下

 private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new Application.ActivityLifecycleCallbacks() {
         ...
        @Override public void onActivityDestroyed(Activity activity) {
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
  };
  public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }

通过上边方法我们可以看到通过,流程大概如下,首先在Application 中初始化,它会生成 refWatch 并通过application 来监听 Activity 的生命周期中的 onDestroy 方法,然后我们在追踪一下 onActivityDestroyed 的方法,就是在页面销毁的时候我们进行如何的操作才能监听到对象的回收。

 void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);
  }
  
public void watch(Object watchedReference) {
    watch(watchedReference, "");
}

public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    // 生成一个随机 UUID 
    String key = UUID.randomUUID().toString();
    // retainedKeys (Set<String> 的集合)
    retainedKeys.add(key);
    // 这里是将对象,随机key,名字,还有弱引用队列,然后生成一个自定义弱引用
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    // 异步进行检查是否内存泄漏
    ensureGoneAsync(watchStartNanoTime, reference);
  }

然后我们继续追踪 ensureGoneAsync 方法,我们发现,他启动了线程池来调用工作线程来工作。

  private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }

我们继续看看线程的run() 方法中的 ensureGone() 进行了什么操作

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    // gc 的启动开始时间
    long gcStartNanoTime = System.nanoTime();
    // 计算监听的时间差值
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    // 遍历 queue 队列,然后一个个移除
    removeWeaklyReachableReferences();
    // 检测是否是正在调试的状态
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    // 判断 retainedKeys 中是否reference.key 如果没有的话表名没有泄露,否则继续进行
    if (gone(reference)) {
      return DONE;
    }
    // 再次进行一次 GC
    gcTrigger.runGc();
    
    removeWeaklyReachableReferences();
    
    // 判断是否内存泄漏,如果为 true 表明内存泄漏了,这样就会下边继续分析具体哪一部分代码内存泄漏
    if (!gone(reference)) {
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);

      File heapDumpFile = heapDumper.dumpHeap();
      if (heapDumpFile == RETRY_LATER) {
        // Could not dump the heap.
        return RETRY;
      }
      long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
  }

小结

我们可以发现就是 LeakCanary 所做的就是监听 Activity 中的 onDestroy方法,然后将引用的 Activity 进行封装为一个弱引用对象,然后进行一次 Gc 回收,引用队列里边还有这个对象,就表明内存泄漏,否则就是被正常gc 回收了。