LeakCanary 源碼解析

一、 前言

1. Java 內存模型
image.png
運行時數據區域
名稱 特征 作用 配置參數 異常
程序計數器 占用內存小,線程私有,生命周期與線程相同 大致為字節碼行號指示器
虛擬機棧 線程私有,生命周期與線程相同,使用連續的內存空間 Java 方法執行的內存模型,存儲局部變量表、操作棧、動態鏈接、方法出口等信息 -Xss StackOverflowError、OutOfMemoryError
java堆 線程共享,生命周期與虛擬機相同,可以不使用連續的內存地址 保存對象實例,所有對象實例(包括數組)都要在堆上分配 -Xms、-Xsx、-Xmn OutOfMemoryError
方法區 線程共享,生命周期與虛擬機相同,可以不使用連續的內存地址 存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據 -XX:PermSize:16M 、-XX:MaxPermSize64M OutOfMemoryError
運行時常量池 方法區的一部分,具有動態性 存放字面量及符號引用
2. Java垃圾回收策略
  • 引用計數算法:給對象中添加一個引用計數器,每當有引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器為0的對象就是不可能再被使用。
  • 可達性分析算法:通過一系列的稱為"GC Root"的對象作為起點,從這些節點開始向下搜索,搜素所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Root沒有任何的引用鏈相連,就判定對象可以被回收
對象可達性
3. Java 四種引用類型
  • 強引用:默認的引用方式,當內存空間不足,JVM寧愿觸發OOM,也不會對這部分內存進行回收。
Object obj = new Object();
obj = null;
  • 軟引用(SoftReference):當內存空間不足的時候,JVM會回收這部分內存,一般用來做緩存策略。
  • 弱引用(WeakReference): 當GC回收的時候,一旦發現了只具有弱引用的對象,不管當前內存空間足夠與否,都會回收這部分內存。
  • 虛引用(PhantomReference):當 GC回收的時候會回收這部分內存, 主要用于檢測對象是否已經從內存中刪除。
代碼示例
public class TestReference {

    public static void main(String[] args) throws InterruptedException {
        ReferenceQueue<Object> referenceQueue = new ReferenceQueue<>();
        String sw = "虛引用";

        switch (sw) {
            case "軟引用":
                Object objSoft = new Object();
                SoftReference<Object> softReference = new SoftReference<>(objSoft, referenceQueue);
                System.out.println("GC前獲取:" + softReference.get());
                objSoft = null;
                System.gc();
                Thread.sleep(1000);
                System.out.println("GC后獲取:" + softReference.get());
                System.out.println("隊列中的結果:" + referenceQueue.poll());
                break;
            case "弱引用":
                Object objWeak = new Object();
                WeakReference<Object> weakReference = new WeakReference<>(objWeak, referenceQueue);
                System.out.println("GC前獲取:" + weakReference.get());
                objWeak = null;
                System.gc();
                Thread.sleep(1000);
                System.out.println("GC后獲取:" + weakReference.get());
                System.out.println("隊列中的結果:" + referenceQueue.poll());
                break;
            case "虛引用":
                Object objPhan = new Object();
                PhantomReference<Object> phantomReference = new PhantomReference<>(objPhan, referenceQueue);
                System.out.println("GC前獲取:" + phantomReference.get());
                objPhan = null;
                System.gc();
                //此處的區別是當objPhan的內存被gc回收之前虛引用就會被加入到ReferenceQueue隊列中,其他的引用都為當引用被gc掉時候,引用會加入到ReferenceQueue中
                Thread.sleep(1000);
                System.out.println("GC后獲取:" + phantomReference.get());
                System.out.println("隊列中的結果:" + referenceQueue.poll());
                break;
        }
    }
}

軟引用運行結果:

軟引用運行結果

弱引用運行結果:

弱引用運行結果

虛引用運行結果:

虛引用運行結果

二、 集成

build.gradle文件

dependencies {
   // debug 版本依賴
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
  // release 版本依賴  
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
  // 如果使用了 support fragment,請同時依賴
  debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'

  // 最新2.0版本,只需要導入如下依賴,不再需要在Application里面添加其他代碼
  // debugImplementation because LeakCanary should only run in debug builds.
  // debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-2'
}

Application#onCreate方法

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(this);

三、 LeakCanary源碼導讀

3.1. LeakCanary項目架構

LeakCanary 項目結構圖:

LeakCanary項目結構

  • leakcanary-analyzer : 負責分析內存泄漏
  • leakcanary-android : android 核心模塊以及通知、界面展示等
  • leakcanary-android-instrumentation : 單元測試使用
  • leakcanary-sample : 使用案例
  • leakcanary-support-fragment : 支持 fragment v4包
  • leakcanary-watcher : 負責監聽內存泄漏
  • leakcanary-android-no-op : release 版本使用
3.2. LeakCanary執行過程
LeakCanary執行過程
3.3. LeakCanary初始化
  /**
   * Whether the current process is the process running the {@link HeapAnalyzerService}, which is
   * a different process than the normal app process.
   */
  public static boolean isInAnalyzerProcess(@NonNull Context context) {
    Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
    // This only needs to be computed once per process.
    Log.d("LeakCanary", "isInAnalyzerProcess = " + isInAnalyzerProcess);
    if (isInAnalyzerProcess == null) {
      // HeapAnalyzerService進程名是否等于主進程名稱
      isInAnalyzerProcess = isInServiceProcess(context, HeapAnalyzerService.class);
      LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;
    }
    return isInAnalyzerProcess;
  }

  public static @NonNull void install(@NonNull Application application) {
    refWatcher(application)
        .listenerServiceClass(DisplayLeakService.class)  // 設置 heapDumpListener
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) // 去除Android SDK 引起的內存泄漏
        .buildAndInstall();  //創建RefWatcher
  }

  public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
    return new AndroidRefWatcherBuilder(context);
  }

isInAnalyzerProcess方法,用于檢測內存分析進程是否和主進程名相同,如果是相同的,則設置RefWatcher = DISABLED ,不做任何處理。release 版本是通過以下以來確定的。
// release 版本依賴
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'

3.4 AndroidRefWatcherBuilder 對象
  public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
      @NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    // isAssignableFrom()方法是判斷是否為某個類的父類,instanceof()方法是判斷是否某個類的子類。
    //(https://www.cnblogs.com/bethunebtj/p/4681438.html)
    enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
  }

  public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
      this.heapDumpListener = heapDumpListener;
      return self();
  }

  public final T excludedRefs(ExcludedRefs excludedRefs) {
    heapDumpBuilder.excludedRefs(excludedRefs);
    return self();
  }

  @Override protected @NonNull HeapDumper defaultHeapDumper() {
    LeakDirectoryProvider leakDirectoryProvider =
        LeakCanaryInternals.getLeakDirectoryProvider(context);
    return new AndroidHeapDumper(context, leakDirectoryProvider);
  }

  public @NonNull RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      Log.d("LeakCanary", "enableDisplayLeakActivity = " + enableDisplayLeakActivity);
      if (enableDisplayLeakActivity) {
        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      }
      Log.d("LeakCanary", "watchActivities = " + watchActivities);
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }
      Log.d("LeakCanary", "watchFragments = " + watchFragments);
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }

  public final RefWatcher build() {
      if (isDisabled()) {
          return RefWatcher.DISABLED; 
      }
      if (heapDumpBuilder.excludedRefs == null) {
          heapDumpBuilder.excludedRefs(defaultExcludedRefs());
      }
      HeapDump.Listener heapDumpListener = this.heapDumpListener;
      if (heapDumpListener == null) {
          heapDumpListener = defaultHeapDumpListener();
      }
      DebuggerControl debuggerControl = this.debuggerControl;
      if (debuggerControl == null) {
          // 走這里
          debuggerControl = defaultDebuggerControl();
      }
      HeapDumper heapDumper = this.heapDumper;
      if (heapDumper == null) {
          // 走這里
          heapDumper = defaultHeapDumper();
      }
      WatchExecutor watchExecutor = this.watchExecutor;
      if (watchExecutor == null) {
          // 走這里
          watchExecutor = defaultWatchExecutor();
      }
      GcTrigger gcTrigger = this.gcTrigger;
      if (gcTrigger == null) {
          // 走這里
          gcTrigger = defaultGcTrigger();
      }
      if (heapDumpBuilder.reachabilityInspectorClasses == null) {
          heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
      }
      return new RefWatcher(watchExecutor, debuggerControl, gcTrigger
                            , heapDumper, heapDumpListener, heapDumpBuilder);
  }

  @Override protected @NonNull WatchExecutor defaultWatchExecutor() {
    return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
  }

buildAndInstall方法則用于設置heapDumpListener用于分析hprof文件, 過濾掉因為Android SDK引起的系統內存泄漏引用, 展示DisplayLeakActivity桌面icon,監聽Activity 以及Fragment生命周期,在其銷毀的時候調用RefWatcher的watch方法分析是否會發生內存泄漏對象。build方法則是初始化RefWatcher對象,

關于設置DisplayLeakActivity enabled = true的地方,需要看看詳細的代碼

    LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);

    public static void setEnabledAsync(Context context, final Class<?> componentClass,
                                       final boolean enabled) {
        final Context appContext = context.getApplicationContext();
        AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
            @Override
            public void run() {
                setEnabledBlocking(appContext, componentClass, enabled);
            }
        });
    }

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

其實就是利用AsyncTask的THREAD_POOL_EXECUTOR線程池去執行PackageManager的setComponentEnabledSetting方法,動態設置COMPONENT_ENABLED_STATE_ENABLED


image.png
3.5 RefWatcher 對象
  private final Set<String> retainedKeys;
  private final ReferenceQueue<Object> queue; // 引用隊列

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

  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);

    // 移除所有弱引用可達對象
    removeWeaklyReachableReferences();

    // 如果VM正連接到Debuger,忽略這次檢測,因為Debugger可能會持有一些在當前上下文中不可見的對象,導致誤判
    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生成Hprof文件
      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);  // 開始分析hprof文件
    }
    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);
    }
  }

RefWatcher很重要的方法就是watch方法, 其中參數watchedReference就是對應的view、activity、fragment對象。 通過UUID 函數生成唯一的標識添加到Set<String>集合當中。 KeyedWeakReference繼承了WeakReference,關聯了watchedReference。當進弱引用對象發生回收的時候,虛擬機就會向該引用添加到與之關聯的引用隊列當中。

檢測內存是否泄漏的過程也很簡單:首先會清除所有的弱引用可達對象, 判斷watchedReference對象是否已經被回收了, 如果沒有,則手動進行一個GC,二次確認watchedReference對象是否被回收,如果沒被回收則dump hprof文件,通知heapdumpListener分析hprof文件

3.6. AndroidWatchExecutor 類
public final class AndroidWatchExecutor implements WatchExecutor {

 static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";
 private final Handler mainHandler;
 private final Handler backgroundHandler;
 private final long initialDelayMillis;
 private final long maxBackoffFactor;

 public AndroidWatchExecutor(long initialDelayMillis) {
   mainHandler = new Handler(Looper.getMainLooper());
   HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
   handlerThread.start();
   backgroundHandler = new Handler(handlerThread.getLooper());
   this.initialDelayMillis = initialDelayMillis;
   maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
 }

 @Override public void execute(@NonNull Retryable retryable) {
   if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
     waitForIdle(retryable, 0);
   } else {
     postWaitForIdle(retryable, 0);
   }
 }

 private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
   mainHandler.post(new Runnable() {
     @Override public void run() {
       waitForIdle(retryable, failedAttempts);
     }
   });
 }

 private void waitForIdle(final Retryable retryable, final int failedAttempts) {
   // This needs to be called from the main thread.
   Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
     @Override public boolean queueIdle() {
       postToBackgroundWithDelay(retryable, failedAttempts);
       return false;
     }
   });
 }

 private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
   long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
   long delayMillis = initialDelayMillis * exponentialBackoffFactor;
   // 利用HandlerThread對象里面的Looper進行子線程任務
   backgroundHandler.postDelayed(new Runnable() {
     @Override public void run() {
       Retryable.Result result = retryable.run();
       if (result == RETRY) {
         postWaitForIdle(retryable, failedAttempts + 1);
       }
     }
   }, delayMillis);
 }
}

WatchExecutor 是一個線程調度器。包含了一個主線程 mainHandler 和后臺線程backgroundHandler。整個的邏輯就是當主線程空閑的時候,才回去啟動后臺線程去執行Retryable.run方法。

3.7. AndroidHeapDumper 生成hprof文件
  public File dumpHeap() {
    // 生成hprof文件
    File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();

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

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

    if (!waitingForToast.wait(5, SECONDS)) {
      Log.d("LeakCanary", "Did not dump heap, too much time waiting for Toast.");
      CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
      return RETRY_LATER;
    }
    // 創建正在dumping通知
    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類提供的方法
      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;
    }
  }

  private void showToast(final FutureResult<Toast> waitingForToast) {
    mainHandler.post(new Runnable() {
      @Override public void run() {
        if (resumedActivity == null) {
          waitingForToast.set(null);
          return;
        }
        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.show();
        // Waiting for Idle to make sure Toast gets rendered.
        Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
          @Override public boolean queueIdle() {
            waitingForToast.set(toast);
            Log.d("LeakCanary", "latch.countDown()");
            return false;
          }
        });
      }
    });
  }

dumpHeap方法利用Debug.dumpHprofData(String filePath)生成hprof文件,同時會展示Toast以及通知欄狀態。如果dump失敗或者Toast展示時間太長就會返回RETRY_LATER。

3.8 HeapAnalyzerService 分析hprof文件,找出泄漏路徑
  @Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
    if (intent == null) {
      CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
      return;
    }
    String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);

    HeapAnalyzer heapAnalyzer =
        new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);

    Log.d("LeakCanary", "leak path:" + heapDump.heapDumpFile.getAbsolutePath());

    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
        heapDump.computeRetainedHeapSize);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }

HeapAnalyzer 是hprof文件解析類, 使用了squareup的另外一個開源庫—Haha。

  public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
      @NonNull String referenceKey,
      boolean computeRetainedSize) {
    long analysisStartNanoTime = System.nanoTime();

    Log.d("LeakCanary", "heapDumpFile path:" + heapDumpFile.getAbsolutePath());
    Log.d("LeakCanary", "referenceKey = " + referenceKey);
    if (!heapDumpFile.exists()) {
      Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
      return failure(exception, since(analysisStartNanoTime));
    }

    // Hprof 文件協議介紹: https://my.oschina.net/u/217380/blog/1507542
    try {
      listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
      // 把hprof文件映射到內存 ByteBuffer[]
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      HprofParser parser = new HprofParser(buffer);
      listener.onProgressUpdate(PARSING_HEAP_DUMP);
      // 創建了HprofParser對象,parse方法解析hprof協議,生成Snapshot
      Snapshot snapshot = parser.parse();
      listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
      // 去除重復的GC root對象
      deduplicateGcRoots(snapshot);
      listener.onProgressUpdate(FINDING_LEAKING_REF);
      Instance leakingRef = findLeakingReference(referenceKey, snapshot);

      // 此對象不存在,表示已經被gc清除了,不存在泄露因此返回無泄漏
      // False alarm, weak reference was cleared in between key check and heap dump.
      if (leakingRef == null) {
        String className = leakingRef.getClassObj().getClassName();
        return noLeak(className, since(analysisStartNanoTime));
      }
      // 此對象存在, 也不能確認它內存泄漏了,要檢測此對象的gc root
      return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }
image.png
3.9 DisplayLeakService 生成內存泄漏通知欄
  protected final void onHeapAnalyzed(@NonNull AnalyzedHeap analyzedHeap) {
    Log.d("LeakCanary", "[DisplayLeakService]: onHeapAnalyzed");
    HeapDump heapDump = analyzedHeap.heapDump;
    AnalysisResult result = analyzedHeap.result;

    String leakInfo = leakInfo(this, heapDump, result, true);
    CanaryLog.d("%s", leakInfo);

    heapDump = renameHeapdump(heapDump);
    boolean resultSaved = saveResult(heapDump, result);

    String contentTitle;
    if (resultSaved) {
      PendingIntent pendingIntent =
          DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
      if (result.failure != null) {
        contentTitle = "Leak analysis failed";
      } else {
        String className = classSimpleName(result.className);
        if (result.leakFound) {
          if (result.retainedHeapSize == AnalysisResult.RETAINED_HEAP_SKIPPED) {
            if (result.excludedLeak) {
              contentTitle = getString(R.string.leak_canary_leak_excluded, className);
            } else {
              contentTitle = getString(R.string.leak_canary_class_has_leaked, className);
            }
          } else {
            String size = formatShortFileSize(this, result.retainedHeapSize);
            if (result.excludedLeak) {
              contentTitle =
                  getString(R.string.leak_canary_leak_excluded_retaining, className, size);
            } else {
              contentTitle =
                  getString(R.string.leak_canary_class_has_leaked_retaining, className, size);
            }
          }
        } else {
          contentTitle = getString(R.string.leak_canary_class_no_leak, className);
        }
      }
      String contentText = getString(R.string.leak_canary_notification_message);
      showNotification(pendingIntent, contentTitle, contentText);
    } else {
      onAnalysisResultFailure(getString(R.string.leak_canary_could_not_save_text));
    }

    afterDefaultHandling(heapDump, result, leakInfo);
  }
四、LeakCanary不足

雖然 LeakCanary 有諸多優點,但是它也有做不到的地方,比如說檢測申請大容量內存導致的OOM問題、Bitmap內存未釋放問題,Service 中的內存泄漏可能無法檢測等。

五、延申閱讀

1. Matrix ResourceCanary -- Activity 泄漏及Bitmap冗余檢測
2. Hprof 文件協議
3. GC那些事兒--Android內存優化第一彈

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容