1. 程式人生 > >Android記憶體洩漏框架LeakCanary原始碼分析

Android記憶體洩漏框架LeakCanary原始碼分析

LeakCanary原始碼分析

LeakCanary是一個記憶體洩漏檢測的框架,預設只會檢測Activity的洩漏,如果需要檢測其他類,可以使用LeakCanary.install返回的RefWatcher,呼叫RefWatcher.watch(obj)就可以觀測obj物件是否出現洩漏。

從install方法開始:

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

最終返回了一個RefWatcher物件:

  public RefWatcher buildAndInstall() {
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      LeakCanary.enableDisplayLeakActivity(context);
      ActivityRefWatcher.install((Application) context, refWatcher);
    }
    return
refWatcher; }

DisplayLeakService

繼承自AbstractAnalysisResultService,它是一個IntentService,用來對洩漏資料進行分析處理。

 public abstract class AbstractAnalysisResultService extends IntentService {

     ...

    @Override protected final void onHandleIntent(Intent intent) {
        HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
        AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
        try
{ onHeapAnalyzed(heapDump, result); } finally { //noinspection ResultOfMethodCallIgnored heapDump.heapDumpFile.delete(); } } }

在AbstractAnalysisResultService的實現DisplayLeakService中,會開啟一個DisplayLeakActivity活動進行洩漏的顯示:

 public class DisplayLeakService extends AbstractAnalysisResultService {

  @Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
    String leakInfo = leakInfo(this, heapDump, result, true);
    CanaryLog.d("%s", leakInfo);

    boolean resultSaved = false;
    boolean shouldSaveResult = result.leakFound || result.failure != null;
    if (shouldSaveResult) {
      heapDump = renameHeapdump(heapDump);
      resultSaved = saveResult(heapDump, result); //儲存heapdump到本地
    }

    PendingIntent pendingIntent;
    String contentTitle;
    String contentText;

    if (!shouldSaveResult) {
      contentTitle = getString(R.string.leak_canary_no_leak_title);
      contentText = getString(R.string.leak_canary_no_leak_text);
      pendingIntent = null;
    } else if (resultSaved) { //heapdump檔案儲存成功才開啟活動
      pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);

      if (result.failure == null) {
        String size = formatShortFileSize(this, result.retainedHeapSize);
        String className = classSimpleName(result.className);
        if (result.excludedLeak) {
          contentTitle = getString(R.string.leak_canary_leak_excluded, className, size);
        } else {
          contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size);
        }
      } else {
        contentTitle = getString(R.string.leak_canary_analysis_failed);
      }
      contentText = getString(R.string.leak_canary_notification_message);
    } else {
      contentTitle = getString(R.string.leak_canary_could_not_save_title);
      contentText = getString(R.string.leak_canary_could_not_save_text);
      pendingIntent = null;
    }
    // New notification id every second.
    int notificationId = (int) (SystemClock.uptimeMillis() / 1000);
    showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
    afterDefaultHandling(heapDump, result, leakInfo);   //最後呼叫一個擴充套件的介面,我們可以重寫這個方法來實現heapdump上傳到伺服器的功能
  }
 }

RefWatch

RefWatch是最主要的類:
我們看一下watch方法:

   public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) { //首先判斷是否禁用了
      return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    String key = UUID.randomUUID().toString(); //為這個引用新增鍵值
    retainedKeys.add(key);  //retainedKeys存放未被GC回收的被觀察物件
    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);
      }
    });
  }

檢查引用是否有效;GC;再次檢查引用是否有效。
弱引用的關聯引用佇列:當弱引用的物件,弱引用會放入關聯的引用佇列queue中。

  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();   //gc開始時間
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);  //已經觀察的時間

    removeWeaklyReachableReferences();  //把有效引用(執行緒可達)移出弱引用集合

    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    if (gone(reference)) {  //如果弱引用佇列裡面已經沒有被觀測物件的引用了,表示這個物件已經被回收。
      return DONE;
    }
    gcTrigger.runGc();  //執行GC,並且等待100ms
    removeWeaklyReachableReferences();  //再次移出有效引用
    if (!gone(reference)) { //如果這時候被觀測物件的引用不等於null,也就是它仍然被執行緒可達,說明已經發生了記憶體洩漏
      long startDumpHeap = System.nanoTime();
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); //計算gc所用時間

      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));   //最後交給heapdump監聽器來處理
    }
    return DONE;
  }

heapdump資訊最終會傳遞給繼承自IntentService的listenerServiceClass方法處理:

   public static void runAnalysis(Context context, HeapDump heapDump,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    context.startService(intent);
  }

AndroidWatchExecutor

我們來看一下記憶體洩漏檢查的執行器:

   public AndroidWatchExecutor(long initialDelayMillis) {
    mainHandler = new Handler(Looper.getMainLooper());  //主執行緒Handler
    HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);   //新建一個帶訊息迴圈Looper的子執行緒
    handlerThread.start();
    backgroundHandler = new Handler(handlerThread.getLooper()); //後臺Handler
    this.initialDelayMillis = initialDelayMillis;
    maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
  }

執行檢查任務:

 @Override public void execute(Retryable retryable) {
    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
      waitForIdle(retryable, 0);    //主執行緒
    } else {
      postWaitForIdle(retryable, 0);    //非主執行緒
    }

最終是在主執行緒呼叫waitForIdle:

  void waitForIdle(final Retryable retryable, final int failedAttempts) {
    // This needs to be called from the main thread.
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {    //新增一個閒置處理器(在主執行緒訊息佇列閒置的時候會執行queueIdle)
      @Override public boolean queueIdle() {
        postToBackgroundWithDelay(retryable, failedAttempts);
        return false;
      }
    });
  }

    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.Result result = retryable.run();  //這時候才會執行鍼對物件的記憶體洩漏檢查
        if (result == RETRY) {
          postWaitForIdle(retryable, failedAttempts + 1);
        }
      }
    }, delayMillis);
  }

ActivityRefWatcher

這個類是用來監聽一個應用內的所有活動的生命週期,當活動執行了onDestory的時候,開始對Activity記憶體洩漏的檢查。

   void onActivityDestroyed(Activity activity) {
    refWatcher.watch(activity);
  }

總結

整個Activity記憶體洩漏的檢測從Activity執行了onDestory後開始:
1. Activity執行onDestory
2. refWatcher把該Activity新增到弱引用列表裡面
3. 然後使用IdelHandler機制,在主執行緒空閒的時候延時 5 秒傳送一個Runnable到後臺執行緒HandlerThread的訊息佇列裡
4. HandlerThread取出訊息,移除被回收物件的弱引用,然後查詢活動是否還存在於弱引用列表中,如果不在,則說明沒有發生記憶體洩漏
5. 如果在,則呼叫一次gc,等待100ms,再次移除被回收物件的弱引用,查詢活動是否還存在弱引用列表裡面,如果在,則說明發生了記憶體洩漏
6. 然後就執行dumpheap,把堆疊資訊用Intent的方式傳送給IntentService
7. IntentService拿到dumheap之後,儲存heapdump到本地,在通知欄上顯示記憶體洩漏的通知,然後執行afterDefaultHandling(我們可以重寫)
8. 最後刪除本地的heapdump檔案

幾個問題
1. 為什麼要使用idlehandler? 在主執行緒空閒的時候才把任務放到子執行緒中執行
2. HandlerThread會退出嗎?
改進,用HandlerThread設定為守護執行緒,當主執行緒退出時,該執行緒也就死亡了:
handlerThread.setDaemon(true);
3. 執行緒安全嗎?安全retainedKeys = new CopyOnWriteArraySet<>();