007 LeakCanary 內存泄漏原理徹底解析

LeakCanary 的工做原理是什麼?跟我一塊兒揭開它的神祕面紗。java

1、 什麼是LeakCanary

LeakCanary 是大名鼎鼎的 square 公司開源的內存泄漏檢測工具。目前上大部分App在開發測試階段都會接入此工具用於檢測潛在的內存泄漏問題,作的好一點的可能會搭建一個服務器用於保存各個設備上的內存泄漏問題再集中處理。android

本文首發於個人微信公衆號:Android開發實驗室,歡迎你們關注和我一塊兒學Android,掉節操。git

2、 爲何要使用LeakCanary

咱們知道內存泄漏問題的排查有不少種方法, 好比說,Android Studio 自帶的 Profile 工具、MAT(Memory Analyzer Tool)、以及LeakCanary。 選擇 LeakCanary 做爲首選的內存泄漏檢測工具主要是由於它能實時檢測泄漏並以很是直觀的調用鏈方式展現內存泄漏的緣由。github

3、 LeakCanary 作不到的(待定)

雖然 LeakCanary 有諸多優勢,可是它也有作不到的地方,好比說檢測申請大容量內存致使的OOM問題、Bitmap內存未釋放問題,Service 中的內存泄漏可能沒法檢測等。shell

4、 LeakCanary 源碼解析

本章內容先後依賴關係強烈,建議順序閱讀。bash

4.1 ActivityLifecycleCallbacks 與 FragmentLifeCycleCallbacks

在開始 LeakCanary 原理解析以前,有必要簡單說下 ActivityLifecycleCallbacks 與 FragmentLifeCycleCallbacks。服務器

// ActivityLifecycleCallbacks 接口
public interface ActivityLifecycleCallbacks {
    void onActivityCreated(Activity var1, Bundle var2);

    void onActivityStarted(Activity var1);

    void onActivityResumed(Activity var1);

    void onActivityPaused(Activity var1);

    void onActivityStopped(Activity var1);

    void onActivitySaveInstanceState(Activity var1, Bundle var2);

    void onActivityDestroyed(Activity var1);
  }
複製代碼

Application 類提供了 registerActivityLifecycleCallbacksunregisterActivityLifecycleCallbacks 方法用於註冊和反註冊 Activity 的生命週期監聽類,這樣咱們就能在 Application 中對全部的 Activity 生命週期回調中作一些統一處理。微信

public abstract static class FragmentLifecycleCallbacks {

    public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {}

    public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {}

    public void onFragmentDestroyed(FragmentManager fm, Fragment f) {}

    // 省略其餘的生命週期 ...
  }
複製代碼

FragmentManager 類提供了 registerFragmentLifecycleCallbacksunregisterFragmentLifecycleCallbacks 方法用戶註冊和反註冊 Fragment 的生命週期監聽類,這樣咱們對每個 Activity 進行註冊,就能獲取全部的 Fragment 生命週期回調。併發

4.2 LeakCanary 的使用

4.2.1 使用方法

咱們直接在 Application 類中,添加一下代碼便可。app

public class ExampleApplication extends Application {
  @Override public void onCreate() {
    super.onCreate();
    setupLeakCanary();
  }

  protected void setupLeakCanary() {
    // 啓用嚴格模式
    enabledStrictMode();
    // 判斷是不是 HeapAnalyzerService 所屬進程
    if (LeakCanary.isInAnalyzerProcess(this)) {
      // This process is dedicated to LeakCanary for heap analysis.
      // You should not init your app in this process.
      return;
    }
    // 註冊 LeakCanary
    LeakCanary.install(this);
  }

  private static void enabledStrictMode() {
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() //
        .detectAll() //
        .penaltyLog() //
        .penaltyDeath() //
        .build());
  }
}
複製代碼
<service android:name=".internal.HeapAnalyzerService" android:process=":leakcanary" android:enabled="false" />
複製代碼

因爲 LeakCanary 的核心 hropf 文件解析服務 HeapAnalyzerService 所屬進程是與主進程獨立的一個進程,因此在 setupLeakCanary中,咱們須要排除其餘進程,只對 leakcanary 進程註冊 LeakCanary 監聽處理。

android:enabled="false" 這是什麼? 這裏簡單說下,AndroidManifest文件中的 enabled 屬性,能夠看到 HeapAnalyzerService 這個組件默認是不可用的,因此若是在代碼中動態啓用這個組件,可使用如下方法:

public static void setEnabledBlocking(Context appContext, Class<?> componentClass, boolean enabled) {
  ComponentName component = new ComponentName(appContext, componentClass);
  PackageManager packageManager = appContext.getPackageManager();
  int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
  // Blocks on IPC.
  packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}
複製代碼

4.3 LeakCanary.install(this) 幹了什麼

LeakCanary 的 install 方法實際上構造了一個 RefWatcher,

/** * Creates a {@link RefWatcher} that works out of the box, and starts watching activity * references (on ICS+). */
public static @NonNull RefWatcher install(@NonNull Application application) {
  return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
      .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
      .buildAndInstall();
}
複製代碼

咱們一個個來看這個註冊方法。首先是 refWatcher 方法構造了一個 AndroidRefWatcherBuilder, 傳入參數是當前Application 的 Context.

public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
  return new AndroidRefWatcherBuilder(context);
}
複製代碼

listenerServiceClass 和 excludedRefs 方法是基於建造者模式傳入分析Service 和 排除已知的泄漏問題 AndroidExcludedRefs,這裏我就不貼代碼了。

重點看下 buildAndInstall 方法,這個方法很形象的表示將要進行建造者模式的最後一步 build 和 註冊一些監聽器,下面咱們來看具體代碼:

public @NonNull RefWatcher buildAndInstall() {
  // 只容許 install 一次
  if (LeakCanaryInternals.installedRefWatcher != null) {
    throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
  }
  // 建造者模式的最後一步,構造對象
  RefWatcher refWatcher = build();
  // 判斷是否開啓了 LeakCanary,沒有開啓默認會返回 DISABLED 對象
  if (refWatcher != DISABLED) {
    // 手動開啓 DisplayLeakActivity 組件,會在桌面上顯示一個查看內存泄漏結果的入口
    LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
    // 是否檢測 Activity 的 內存泄漏,默認開啓
    if (watchActivities) {
      ActivityRefWatcher.install(context, refWatcher);
    }
    // 是否檢測 Fragment 的 內存泄漏,默認開啓
    if (watchFragments) {
      FragmentRefWatcher.Helper.install(context, refWatcher);
    }
  }
  // 複製給全局靜態變量,防止二次調用
  LeakCanaryInternals.installedRefWatcher = refWatcher;
  return refWatcher;
}
複製代碼

以上代碼做用大部分都在代碼中註釋了,剩下 ActivityRefWatcher.install 和 FragmentRefWatcher.Helper.install 方法沒有註釋。下面咱們就來具體看看這兩個方法究竟幹了什麼。

(1). ActivityRefWatcher.install

ActivityRefWatcher 的靜態方法 install 獲取到了當前 Application,而後添加了一個生命週期監聽器 ActivityLifecycleCallbacks,這裏的 lifecycleCallbacks 僅僅關注了 Activity 銷燬的回調 onActivityDestroyed,在這裏將傳入的對象 activity 監聽起來, refWatcher.watch(activity); 的具體代碼咱們稍後分析。

public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
 Application application = (Application) context.getApplicationContext();
 ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);

 application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}

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

(2). FragmentRefWatcher.Helper.install FragmentRefWatcher.Helper 的靜態方法 install 裏一樣會註冊一個 ActivityLifecycleCallbacks 用於監聽 Activity 生命週期中的 onActivityCreated 的建立完成的回調,在 Activity 建立完成後,會對這個 Activity 註冊 Fragment 的生命週期監聽器。install 方法首先會判斷系統是否大於等於 Android O, 若是是那麼會使用 android.app.FragmentManager 進行註冊,若是須要兼容 Android O 如下須要自行在依賴中添加對 leakcanary-support-fragment 組件的依賴,而後經過反射構造出SupportFragmentRefWatcher; 而後將fragmentRefWatchers全部監聽器取出,在 Activity 建立完成後,添加 Fragment 的生命監聽,主要關注 Fragment 的 onFragmentViewDestroyedonFragmentDestroyed 方法。具體代碼以下:

public static void install(Context context, RefWatcher refWatcher) {
      List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
      // 系統是否大於等於 Android O,若是是,添加 AndroidOFragmentRefWatcher
      if (SDK_INT >= O) {
        fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
      }
      // 若是添加了leakcanary-support-fragment的依賴,經過反射能夠構造SupportFragmentRefWatcher
      try {
        Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
        Constructor<?> constructor =
            fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
        FragmentRefWatcher supportFragmentRefWatcher =
            (FragmentRefWatcher) constructor.newInstance(refWatcher);
        fragmentRefWatchers.add(supportFragmentRefWatcher);
      } catch (Exception ignored) {
      }

      if (fragmentRefWatchers.size() == 0) {
        return;
      }

      Helper helper = new Helper(fragmentRefWatchers);
      // 先監聽 Activity 的建立完成回調
      Application application = (Application) context.getApplicationContext();
      application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
    }

    private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
          // Activity 建立完成後,對Activity中的Fragment註冊生命週期監聽
          for (FragmentRefWatcher watcher : fragmentRefWatchers) {
            watcher.watchFragments(activity);
          }
        }
    };
複製代碼
// AndroidOFragmentRefWatcher.java

private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
  new FragmentManager.FragmentLifecycleCallbacks() {
    // Fragment 中的View 視圖銷燬時
    @Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
      View view = fragment.getView();
      if (view != null) {
        refWatcher.watch(view);
      }
    }
    // Fragment 銷燬時
    @Override
    public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
      refWatcher.watch(fragment);
    }
  };

@Override public void watchFragments(Activity activity) {
  // 經過FragmentManager 註冊 FragmentLifecycleCallbacks
  FragmentManager fragmentManager = activity.getFragmentManager();
  fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
複製代碼

上述流程咱們已經徹底搞清楚了,用一個流程圖能夠表示爲:

LeakCanary註冊流程

4.4 LeakCanary 內存泄漏檢測原理

從行文結構上,本小節應屬於上一節後半部份內容,可是RefWatcher 的 watch 方法足夠重要和複雜,因此有必要單獨列一節仔細講解內部原理。

4.4.1 基礎知識——弱引用 WeakReference 和 引用隊列 ReferenceQueue

關於引用類型和引用隊列相關知識,讀者能夠參考白話 JVM——深刻對象引用,這篇文章我認爲講解的比較清晰。

這裏,我簡單舉個例子,弱引用在定義的時候能夠指定引用對象和一個 ReferenceQueue,弱引用對象在垃圾回收器執行回收方法時,若是原對象只有這個弱引用對象引用着,那麼會回收原對象,並將弱引用對象加入到 ReferenceQueue,經過 ReferenceQueue 的 poll 方法,能夠取出這個弱引用對象,獲取弱引用對象自己的一些信息。看下面這個例子。

mReferenceQueue = new ReferenceQueue<>();
// 定義一個對象
o = new Object();
// 定義一個弱引用對象引用 o,並指定引用隊列爲 mReferenceQueue
weakReference = new WeakReference<Object>(o, mReferenceQueue);
// 去掉強引用
o = null;
// 觸發應用進行垃圾回收
Runtime.getRuntime().gc();
// hack: 延時100ms,等待gc完成
try {
    Thread.sleep(100);
} catch (InterruptedException e) {
    e.printStackTrace();
}
Reference ref = null;
// 遍歷 mReferenceQueue,取出全部弱引用
while ((ref = mReferenceQueue.poll()) != null) {
    System.out.println("============ \n ref in queue");
}
複製代碼

打印結果爲:

============ ref in queue

4.4.2 基礎知識——hprof文件

hprof 文件能夠展現某一時刻java堆的使用狀況,根據這個文件咱們能夠分析出哪些對象佔用大量內存和未在合適時機釋放,從而定位內存泄漏問題。

Android 生成 hprof 文件總體上有兩種方式:

  1. 使用 adb 命令
adb shell am dumpheap <processname> <FileName>
複製代碼
  1. 使用 android.os.Debug.dumpHprofData 方法 直接使用 Debug 類提供的 dumpHprofData 方法便可。
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
複製代碼

Android Studio 自帶 Android Profiler 的 Memory 模塊的 dump 操做使用的是方法一。這兩種方法生成的 .hprof 文件都是 Dalvik 格式,須要使用 AndroidSDK 提供的 hprof-conv 工具轉換成J2SE HPROF格式才能在MAT等標準 hprof 工具中查看。

hprof-conv dump.hprof converted-dump.hprof  
複製代碼

至於hprof內部格式如何,本文不作具體介紹,之後有機會再單獨寫一篇文章來仔細講解。LeakCanary 解析 .hprof 文件用的是 square 公司開源的另外一項目:haha.

4.4.3 watch方法

終於到了 LeakCanary 關鍵部分了。咱們從 watch 方法入手,前面的代碼都是爲了加強魯棒性,咱們直接從生成惟一id開始,LeakCanary 構造了一個帶有 key 的弱引用對象,而且將 queue 設置爲弱引用對象的引用隊列。

這裏解釋一下,爲何須要建立一個帶有 key 的弱引用對象,不能直接使用 WeakReference 麼? 舉個例子,假設 OneActivity 發生了內存泄漏,那麼執行 GC 操做時,確定不會回收 Activity 對象,這樣 WeakReference 對象也不會被回收。假設當前啓動了 N 個 OneActivity,Dump內存時咱們能夠獲取到內存中的全部 OneActivity,可是當咱們準備去檢測其中某一個 Activity 的泄漏問題時,咱們就沒法匹配。可是若是使用了帶有 key 的 WeakReference 對象,發生泄露時泄漏時,key 的值也會 dump 保存下來,這樣咱們根據 key 的一一對應關係就能映射到某一個 Activity。

而後,LeakCanary 調用了 ensureGoneAsync 方法去檢測內存泄漏。

public void watch(Object watchedReference, String referenceName) {
  if (this == DISABLED) {
    return;
  }
  checkNotNull(watchedReference, "watchedReference");
  checkNotNull(referenceName, "referenceName");
  final long watchStartNanoTime = System.nanoTime();
  // 對當前監視對象設置一個惟一 id
  String key = UUID.randomUUID().toString();
  // 添加到 Set<String> 中
  retainedKeys.add(key);
  // 構造一個帶有id 的 WeakReference 對象
  final KeyedWeakReference reference =
      new KeyedWeakReference(watchedReference, key, referenceName, queue);
  // 檢測對象是否被回收了
  ensureGoneAsync(watchStartNanoTime, reference);
}
複製代碼

4.4.4 ensureGoneAsync 方法

ensureGoneAsync 方法構造了一個 Retryable 對象,並將它傳給 watchExecutor 的 execute 方法。

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
  // watchExecutor 是 AndroidWatchExecutor的一個實例
  watchExecutor.execute(new Retryable() {
    @Override public Retryable.Result run() {
      return ensureGone(reference, watchStartNanoTime);
    }
  });
}
複製代碼

watchExecutor 是 AndroidWatchExecutor 的一個實例, AndroidWatchExecutor 的 execute 方法的做用就是判斷當前線程是不是主線程,若是是主線程,那麼直接執行 waitForIdle 方法,不然經過 Handler 的 post 方法切換到主線程再執行 waitForIdle 方法。

@Override public void execute(@NonNull Retryable retryable) {
  // 判斷當前線程是不是主線程
  if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
    waitForIdle(retryable, 0);
  } else {
    postWaitForIdle(retryable, 0);
  }
}
複製代碼

waitForIdle 方法經過調用 addIdleHandler 方法,指定當主進程中沒有須要處理的事件時,在這個空閒期間執行 postToBackgroundWithDelay 方法。

private void waitForIdle(final Retryable retryable, final int failedAttempts) {
  // 因爲上面的 execute 方法,已經保證了此方法在主線程中執行,因此Looper.myQueue()獲取的主線程的消息隊列
  Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    @Override public boolean queueIdle() {
      postToBackgroundWithDelay(retryable, failedAttempts);
      // return false 表示執行完以後,就當即移除這個事件
      return false;
    }
  });
}
複製代碼

postToBackgroundWithDelay 方法首先會計算延遲時間 delayMillis,這個延時是有 exponentialBackoffFactor(指數因子) 乘以初始延時時間獲得的, exponentialBackoffFactor(指數因子)會在2^n 和 Long.MAX_VALUE / initialDelayMillis 中取較小值,也就說延遲時間delayMillis = initialDelayMillis * 2^n,且不能超過 Long.MAX_VALUE。

private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
   long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
   // 計算延遲時間
   long delayMillis = initialDelayMillis * exponentialBackoffFactor;
   // 切換到子線程中執行
   backgroundHandler.postDelayed(new Runnable() {
     @Override public void run() {
       // 執行 retryable 裏的 run 方法
       Retryable.Result result = retryable.run();
       // 若是須要重試,那麼再添加到主線程的空閒期間執行
       if (result == RETRY) {
         postWaitForIdle(retryable, failedAttempts + 1);
       }
     }
   }, delayMillis);
}
複製代碼

postToBackgroundWithDelay 方法每次執行會指數級增長延時時間,延時時間到了後,會執行 Retryable 裏的方法,若是返回爲重試,那麼會增長延時時間並執行下一次。

retryable.run() 的run 方法又執行了什麼呢?別忘了咱們ensureGoneAsync中的代碼,一直在重試的代碼正式 ensureGone 方法。

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

4.4.5 ensureGone 方法

我如今講ensureGone方法的完整代碼貼出來,咱們逐行分析:

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
  long gcStartNanoTime = System.nanoTime();
  // 前面不是有一個重試的機制麼,這裏會計下此次重試距離第一次執行花了多長時間
  long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
  // 移除全部弱引用可達對象,後面細講
  removeWeaklyReachableReferences();
  // 判斷當前是否正在開啓USB調試,LeakCanary 的解釋是調試時可能會觸發不正確的內存泄漏
  if (debuggerControl.isDebuggerAttached()) {
    // The debugger can create false leaks.
    return RETRY;
  }
  // 上面執行 removeWeaklyReachableReferences 方法,判斷是否是監視對象已經被回收了,若是被回收了,那麼說明沒有發生內存泄漏,直接結束
  if (gone(reference)) {
    return DONE;
  }
  // 手動觸發一次 GC 垃圾回收
  gcTrigger.runGc();
  // 再次移除全部弱引用可達對象
  removeWeaklyReachableReferences();
  // 若是對象沒有被回收
  if (!gone(reference)) {
    long startDumpHeap = System.nanoTime();
    long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
    // 使用 Debug 類 dump 當前堆內存中對象使用狀況
    File heapDumpFile = heapDumper.dumpHeap();
    // dumpHeap 失敗的話,會走重試機制
    if (heapDumpFile == RETRY_LATER) {
      // Could not dump the heap.
      return RETRY;
    }
    long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
    // 將hprof文件、key等屬性構造一個 HeapDump 對象
    HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
        .referenceName(reference.name)
        .watchDurationMs(watchDurationMs)
        .gcDurationMs(gcDurationMs)
        .heapDumpDurationMs(heapDumpDurationMs)
        .build();
    // heapdumpListener 分析 heapDump 對象
    heapdumpListener.analyze(heapDump);
  }
  return DONE;
}
複製代碼

看完上述代碼,基本把檢測泄漏的大體過程走了一遍,下面咱們來看一些具體的細節。

(1). removeWeaklyReachableReferences 方法

removeWeaklyReachableReferences 移除全部弱引用可達對象是怎麼工做的?

private void removeWeaklyReachableReferences() {
  KeyedWeakReference ref;
  while ((ref = (KeyedWeakReference) queue.poll()) != null) {
    retainedKeys.remove(ref.key);
  }
}
複製代碼

還記得咱們在 refWatcher.watch 方法保存了當前監視對象的 ref.key 了麼,若是這個對象被回收了,那麼對應的弱引用對象會在回收時被添加到queue中,經過 poll 操做就能夠取出這個弱引用,這時候咱們從retainedKeys中移除這個 key, 表明這個對象已經被正常回收,不須要再被監視了。

那麼如今來看,判斷這個對象是否被回收就比較簡單了?

private boolean gone(KeyedWeakReference reference) {
  // retainedKeys 中不包含 reference.key 的話,就表明這個對象已經被回收了
  return !retainedKeys.contains(reference.key);
}
複製代碼

(2). dumpHeap 方法

heapDumper.dumpHeap() 是執行生成hprof的方法,heapDumper 是 AndroidHeapDumper 的一個對象,咱們來具體看看它的 dump 方法。

public File dumpHeap() {
  // 生成一個存儲 hprof 的文件
  File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
  // 文件建立失敗
  if (heapDumpFile == RETRY_LATER) {
    return RETRY_LATER;
  }
  // FutureResult 內部有一個 CountDownLatch,用於倒計時
  FutureResult<Toast> waitingForToast = new FutureResult<>();
  // 切換到主線程顯示 toast
  showToast(waitingForToast);
  // 等待5秒,確保 toast 已完成顯示
  if (!waitingForToast.wait(5, SECONDS)) {
    CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
    return RETRY_LATER;
  }
  // 建立一個通知
  Notification.Builder builder = new Notification.Builder(context)
      .setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
  Notification notification = LeakCanaryInternals.buildNotification(context, builder);
  NotificationManager notificationManager =
      (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
  int notificationId = (int) SystemClock.uptimeMillis();
  notificationManager.notify(notificationId, notification);

  Toast toast = waitingForToast.get();
  try {
    // 開始 dump 內存到指定文件
    Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
    cancelToast(toast);
    notificationManager.cancel(notificationId);
    return heapDumpFile;
  } catch (Exception e) {
    CanaryLog.d(e, "Could not dump heap");
    // Abort heap dump
    return RETRY_LATER;
  }
}
複製代碼

這段代碼裏咱們須要看看 showToast() 方法,以及它是如何確保 toast 已完成顯示(有點黑科技的感受)。

private void showToast(final FutureResult<Toast> waitingForToast) {
  mainHandler.post(new Runnable() {
    @Override public void run() {
      // 當前 Activity 已經 paused的話,直接返回
      if (resumedActivity == null) {
        waitingForToast.set(null);
        return;
      }
      // 構建一個toast 對象
      final Toast toast = new Toast(resumedActivity);
      toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
      toast.setDuration(Toast.LENGTH_LONG);
      LayoutInflater inflater = LayoutInflater.from(resumedActivity);
      toast.setView(inflater.inflate(R.layout.leak_canary_heap_dump_toast, null));
      // 將toast加入顯示隊列
      toast.show();
      // Waiting for Idle to make sure Toast gets rendered.
      // 主線程中添加空閒時操做,若是主線程是空閒的,會將CountDownLatch執行 countDown 操做
      Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
        @Override public boolean queueIdle() {
          waitingForToast.set(toast);
          return false;
        }
      });
    }
  });
}
複製代碼

首先咱們須要知道全部的Toast對象,並非咱們一調用 show 方法就當即顯示的。NotificationServiceManager會從mToastQueue中輪詢去除Toast對象進行顯示。若是Toast的顯示不是實時的,那麼咱們如何知道Toast是否已經顯示完成了呢?咱們在 Toast 調用 show 方法後調用 addIdleHandler, 在主進程空閒時執行 CountDownLatch 的減一操做。因爲咱們知道咱們順序加入到主線程的消息隊列中的操做:先顯示Toast,再執行 CountDownLatch 減一操做。因此在 if (!waitingForToast.wait(5, SECONDS)) 的判斷中,咱們最多等待5秒,若是超時會走重試機制,若是咱們的 CountDownLatch 已經執行了減一操做,則會正常走後續流程,同時咱們也能推理出它前面 toast 確定已經顯示完成了。

Debug.dumpHprofData(heapDumpFile.getAbsolutePath());是系統Debug類提供的方法,我就不作具體分析了。

(3). heapdumpListener.analyze(heapDump) 方法

heapdumpListener 是 ServiceHeapDumpListener 的一個對象,最終執行了HeapAnalyzerService.runAnalysis方法。

@Override public void analyze(@NonNull HeapDump heapDump) {
  checkNotNull(heapDump, "heapDump");
  HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
複製代碼
// 啓動前臺服務
public static void runAnalysis(Context context, HeapDump heapDump, Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
  setEnabledBlocking(context, HeapAnalyzerService.class, true);
  setEnabledBlocking(context, listenerServiceClass, true);
  Intent intent = new Intent(context, HeapAnalyzerService.class);
  intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
  intent.putExtra(HEAPDUMP_EXTRA, heapDump);
  ContextCompat.startForegroundService(context, intent);
}
複製代碼

HeapAnalyzerService 繼承自 IntentService,IntentService的具體原理我就很少作解釋了。IntentService會將全部併發的啓動服務操做,變成順序執行 onHandleIntent 方法。

@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
  if (intent == null) {
    CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
    return;
  }
  // 監聽 hprof 文件分析結果的類
  String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
  // hprof 文件類
  HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);

  HeapAnalyzer heapAnalyzer =
      new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
  // checkForLeak 會調用 haha 組件中的工具,分析 hprof 文件
  AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
      heapDump.computeRetainedHeapSize);
  // 將分析結果發送給監聽器 listenerClassName
  AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
複製代碼

咱們來看下 checkForLeak 方法,咱們一塊兒來看下吧。

public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile, @NonNull String referenceKey, boolean computeRetainedSize) {
  long analysisStartNanoTime = System.nanoTime();
  // 文件不存在的話,直接返回
  if (!heapDumpFile.exists()) {
    Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
    return failure(exception, since(analysisStartNanoTime));
  }

  try {
    // 更新進度回調
    listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
    // 將 hprof 文件解析成 Snapshot
    HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
    HprofParser parser = new HprofParser(buffer);
    listener.onProgressUpdate(PARSING_HEAP_DUMP);
    Snapshot snapshot = parser.parse();
    listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
    // 移除相同 GC root項
    deduplicateGcRoots(snapshot);
    listener.onProgressUpdate(FINDING_LEAKING_REF);
    // 查找內存泄漏項
    Instance leakingRef = findLeakingReference(referenceKey, snapshot);

    // False alarm, weak reference was cleared in between key check and heap dump.
    // 沒有找到,就表明沒有泄漏
    if (leakingRef == null) {
      return noLeak(since(analysisStartNanoTime));
    }
    // 找到泄漏處的引用關係鏈
    return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
  } catch (Throwable e) {
    return failure(e, since(analysisStartNanoTime));
  }
}
複製代碼

hprof 文件的解析是由開源項目 haha 完成的,我這裏不作過多展開。

findLeakingReference 方法是查找泄漏的引用處,咱們看下代碼:

private Instance findLeakingReference(String key, Snapshot snapshot) {
  // 從 hprof 文件保存的對象中找到全部 KeyedWeakReference 的實例
  ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
  if (refClass == null) {
    throw new IllegalStateException(
        "Could not find the " + KeyedWeakReference.class.getName() + " class in the heap dump.");
  }
  List<String> keysFound = new ArrayList<>();
  // 對 KeyedWeakReference 實例列表進行遍歷
  for (Instance instance : refClass.getInstancesList()) {
    // 獲取每一個實例裏的全部字段
    List<ClassInstance.FieldValue> values = classInstanceValues(instance);
    // 找到 key 字段對應的值
    Object keyFieldValue = fieldValue(values, "key");
    if (keyFieldValue == null) {
      keysFound.add(null);
      continue;
    }
    // 將 keyFieldValue 轉爲 String 對象
    String keyCandidate = asString(keyFieldValue);
    // 若是這個對象的 key 和 咱們查找的 key 相同,那麼返回這個弱對象持有的原對象
    if (keyCandidate.equals(key)) {
      return fieldValue(values, "referent");
    }
    keysFound.add(keyCandidate);
  }
  throw new IllegalStateException(
      "Could not find weak reference with key " + key + " in " + keysFound);
}
複製代碼

到如今爲止,咱們已經把 LeakCanary 檢測內存泄漏的所有過程的源碼看完了。我的認爲 LeakCanary 源碼寫的不錯,可讀性很高,查找調用關係也比較方便(這裏黑一下 bilibili 的 DanmakusFlameMaster)。

五. 版權聲明

本文首發於個人微信公衆號:Android開發實驗室,歡迎你們關注和我一塊兒學Android,掉節操。未經容許,不得轉載。

https://user-gold-cdn.xitu.io/2018/12/3/16774bbd97cdf7a8?w=258&h=258&f=jpeg&s=27337
相關文章
相關標籤/搜索