Leakcanary-流程分析篇

一. 概述:
流程分為三塊:
1.監(jiān)聽(tīng)
2.檢測(cè)泄露
3.分析

監(jiān)聽(tīng)
在Android中,當(dāng)一個(gè)Activity走完onDestroy生命周期后,說(shuō)明該頁(yè)面已經(jīng)被銷毀了,應(yīng)該被系統(tǒng)GC回收。通過(guò)Application.registerActivityLifecycleCallbacks()方法注冊(cè)Activity生命周期的監(jiān)聽(tīng),每當(dāng)一個(gè)Activity頁(yè)面銷毀時(shí)候,獲取到這個(gè)Activity去檢測(cè)這個(gè)Activity是否真的被系統(tǒng)GC。

檢測(cè)
當(dāng)獲取了待分析的對(duì)象后,需要確定這個(gè)對(duì)象是否產(chǎn)生了內(nèi)存泄漏。
通過(guò)WeakReference + ReferenceQueue來(lái)判斷對(duì)象是否被系統(tǒng)GC回收,WeakReference 創(chuàng)建時(shí),可以傳入一個(gè) ReferenceQueue 對(duì)象。當(dāng)被 WeakReference 引用的對(duì)象的生命周期結(jié)束,一旦被 GC 檢查到,GC 將會(huì)把該對(duì)象添加到 ReferenceQueue 中,待ReferenceQueue處理。當(dāng) GC 過(guò)后對(duì)象一直不被加入 ReferenceQueue,它可能存在內(nèi)存泄漏。
當(dāng)我們初步確定待分析對(duì)象未被GC回收時(shí)候,手動(dòng)觸發(fā)GC,二次確認(rèn)。

分析
分析這塊使用了Square的另一個(gè)開(kāi)源庫(kù)haha,https://github.com/square/haha,利用它獲取當(dāng)前內(nèi)存中的heap堆信息的快照snapshot,然后通過(guò)待分析對(duì)象去snapshot里面去查找強(qiáng)引用關(guān)系。

二. 源碼流程
1.監(jiān)聽(tīng):
在Application里加入代碼:

if (LeakCanary.isInAnalyzerProcess(this)) {
                    // This process is dedicated to LeakCanary for heap analysis.
                    // You should not init your app in this process.
                    return;
                }
                LeakCanary.install(getApplication());

然后, install里調(diào)用的是:

if (refWatcher != DISABLED) {
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }

所以, Application里默認(rèn)會(huì)放對(duì)Activity和Fragment的監(jiān)控。

放的過(guò)程比較有意思:
先: application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
在這里lifecycleCallbacks的定義是:

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

Application里這個(gè)Callback的調(diào)用時(shí)機(jī)是:
在Activity.java里, 每個(gè)方法比如onCreate里,會(huì)有調(diào)用:
getApplication().dispatchActivityCreated(this, savedInstanceState);
而dispatchActivityCreated里就會(huì)調(diào)用自己的callback相應(yīng)的代碼

2 檢測(cè)
在onActivityDestroy里調(diào)用的是:refWatcher.watch(activity);
watch的主要工作就是這個(gè):

final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);

對(duì)每一個(gè)監(jiān)控的對(duì)象, 隨機(jī)生成一個(gè)key,放在retainedKeys(private final Set<String> retainedKeys;)中。這個(gè)是作為以后對(duì)象是否還存活的依據(jù)。
這里的關(guān)鍵點(diǎn)在創(chuàng)建KeyedWeakReference的時(shí)候, 傳入了queue。這個(gè)是WeakReference的一個(gè)特有功能,如果創(chuàng)建一個(gè)弱引用, 并且傳入一個(gè)queue的話, 那么當(dāng)gc回收這個(gè)弱引用的時(shí)候, 會(huì)將相應(yīng)的弱引用放在傳入的queue中。這個(gè)很重要!

接下來(lái), 看ensureGoneAsync的實(shí)現(xiàn):

Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    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();
    removeWeaklyReachableReferences();
    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);

      HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
          .referenceName(reference.name)
          .watchDurationMs(watchDurationMs)
          .gcDurationMs(gcDurationMs)
          .heapDumpDurationMs(heapDumpDurationMs)
          .build();

      heapdumpListener.analyze(heapDump);
    }
    return DONE;
  }

其中的removeWeaklyReachableReferences就是

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);
    }
  }

根據(jù)gc回收時(shí)放入queue里的弱引用,將剛才生成key時(shí)放入的retainKeys里的被回收的對(duì)象的key去掉。
而怎么根據(jù)queue來(lái)找到key呢?就是在創(chuàng)建弱引用的時(shí)候, 將key作為參數(shù)傳給了弱引用并且存下來(lái)當(dāng)做一個(gè)屬性。

下面繼續(xù)看ensureGone的步驟:
remove后retainkeys依然包含我們監(jiān)控的key的話, 就繼續(xù)執(zhí)行g(shù)c(gc這里也有一點(diǎn)值得注意的, 放在gc篇里了)。然后看reference是否依然包含key:

 private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
  }

然后,就檢測(cè)到有內(nèi)存泄露了。下面是分析的步驟

  1. 分析
    File heapDumpFile = heapDumper.dumpHeap(); 先拿到dump文件。
    AndroidHeapDumper.java中的dumpHeap:
@Override
 public File dumpHeap() {
    File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();

    if (heapDumpFile == RETRY_LATER) {
      return RETRY_LATER;
    }

    FutureResult<Toast> waitingForToast = new FutureResult<>();
    showToast(waitingForToast);

    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 {
      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;
    }
  }

dump生成hprof文件是調(diào)用:Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
這是集成在vm中的一個(gè)功能,不做詳細(xì)討論,大致就是把當(dāng)前的內(nèi)存狀態(tài)(對(duì)象引用關(guān)系)放到一個(gè)hprof文件中。生成文件后,彈窗消失。

然后就是生成一個(gè)HeapDump的對(duì)象, 用heapdumpListner:heapdumpListener.analyze(heapDump);
ServiceHeapDumpListener.java:

@Override public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }

而這里的HeapAnalyzerService是一個(gè)IntentService。在這個(gè)新啟動(dòng)的service中執(zhí)行對(duì)hprof文件的分析查找, 最后打印出泄露棧。具體HeapAnalyzerService的工作流程, 會(huì)在HeapAnalyzerService篇講。

參考文章:
https://allenwu.itscoder.com/leakcanary-source
https://blog.csdn.net/xiaohanluo/article/details/78196755
https://allenwu.itscoder.com/leakcanary-source
https://ivanljt.github.io/blog/2017/12/15/%E6%8B%86%E8%BD%AE%E5%AD%90%E7%B3%BB%E5%88%97%E2%80%94%E2%80%94LeakCanary%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/
https://cloud.tencent.com/developer/article/1327724

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容