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 版提供一個空實現,能夠學習一下。
相關文章
相關標籤/搜索