一步步拆解 LeakCanary

本篇文章已受權微信公衆號 guolin_blog (郭霖)獨家發佈java

java 源碼系列 - 帶你讀懂 Reference 和 ReferenceQueueandroid

blog.csdn.net/gdutxiaoxu/…git

一步步拆解 LeakCanarygithub

blog.csdn.net/gdutxiaoxu/…面試

前言

內存泄露,一直是咱們性能優化方面的重點。今天,就讓咱們一塊兒來拆解 LeakCanary,一步步理解它的原理性能優化

原理概覽

講解 LeakCannary 原理以前,咱們先來講一下它的主要原理,給你們吃顆定心丸,其實挺簡單的,大概能夠分爲如下幾步:bash

  • 監聽 Activity 的生命週期
  • 在 onDestroy 的時候,建立相應的 Refrence 和 RefrenceQueue,並啓動後臺進程去檢測
  • 一段時間以後,從 RefrenceQueue 讀取,若讀取不到相應 activity 的 Refrence,有可能發生泄露了,這個時候,再促發 gc,一段時間以後,再去讀取,若在從 RefrenceQueue 仍是讀取不到相應 activity 的 refrence,能夠判定是發生內存泄露了
  • 發生內存泄露以後,dump,分析 hprof 文件,找到泄露路徑(使用 haha 庫分析),發送到通知欄

原理分析

LeakCanary#Install

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

listenerServiceClass 方法

public AndroidRefWatcherBuilder listenerServiceClass(
    Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
  return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}

public final class ServiceHeapDumpListener implements HeapDump.Listener {

  private final Context context;
  private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;

  public ServiceHeapDumpListener(Context context,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
     // 啓動後臺服務監聽
    setEnabled(context, listenerServiceClass, true);
    // 啓動 HeapAnalyzerService ,用來分析 dump 文件
    setEnabled(context, HeapAnalyzerService.class, true);
    this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
    this.context = checkNotNull(context, "context").getApplicationContext();
  }

  ----  
}
複製代碼

listenerServiceClass() 方法綁定了一個後臺服務 DisplayLeakService,這個服務主要用來分析內存泄漏結果併發送通知。你能夠繼承並重寫這個類來進行一些自定義操做,好比上傳分析結果等。微信

RefWatcherBuilder.excludedRefs

public final T excludedRefs(ExcludedRefs excludedRefs) {
  this.excludedRefs = excludedRefs;
  return self();
}
AndroidExcludedRefs.java
/**
 * This returns the references in the leak path that can be ignored for app developers. This
 * doesn't mean there is no memory leak, to the contrary. However, some leaks are caused by bugs * in AOSP or manufacturer forks of AOSP. In such cases, there is very little we can do as app * developers except by resorting to serious hacks, so we remove the noise caused by those leaks. */ public static ExcludedRefs.Builder createAppDefaults() { return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class)); } public static ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) { ExcludedRefs.Builder excluded = ExcludedRefs.builder(); for (AndroidExcludedRefs ref : refs) { if (ref.applies) { ref.add(excluded); ((ExcludedRefs.BuilderWithParams) excluded).named(ref.name()); } } return excluded; } 複製代碼

excludedRefs() 方法定義了一些對於開發者能夠忽略的路徑,意思就是即便這裏發生了內存泄漏,LeakCanary 也不會彈出通知。這大可能是系統 Bug 致使的,無需用戶進行處理。併發

AndroidRefWatcherBuilder.buildAndInstall

buildAndInstall 所作的工做,調用 build 構建 refWatcher,判斷 refWatcher 是否 DISABLED,若不是 DISABLED 狀態,調用 install 方法,並將 refWatcher 返回回去app

/**
 * Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+).
 */
public RefWatcher buildAndInstall() {
  // 構建 refWatcher 對象
  RefWatcher refWatcher = build();
  // 判斷是否 DISABLED,若不是 DISABLED 狀態,調用 
  if (refWatcher != DISABLED) {
    LeakCanary.enableDisplayLeakActivity(context);
    ActivityRefWatcher.install((Application) context, refWatcher);
  }
  return refWatcher;
}
複製代碼

瞭解 build 方法 以前,咱們先來看一下 RefWatcherBuilder 是什麼東東?

RefWatcherBuilder

public class RefWatcherBuilder<T extends RefWatcherBuilder<T>> {

  private ExcludedRefs excludedRefs;
  private HeapDump.Listener heapDumpListener;
  private DebuggerControl debuggerControl;
  private HeapDumper heapDumper;
  private WatchExecutor watchExecutor;
  private GcTrigger gcTrigger;



  /** Creates a {@link RefWatcher}. */
  public final RefWatcher build() {
    if (isDisabled()) {
      return RefWatcher.DISABLED;
    }

    ExcludedRefs excludedRefs = this.excludedRefs;
    if (excludedRefs == null) {
      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();
    }

    return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
        excludedRefs);
  }
  
  ----


複製代碼

build 方法看到這裏你是否是有一種很眼熟的感受,沒錯,它運用了建造者模式,與咱們 Android 中的 AlertDialog.build 同出一轍。 建造者模式(Builder)及其應用

RefWatcherBuilder 主要有幾個重要的成員變量

  • watchExecutor : 線程控制器,在 onDestroy() 以後而且主線程空閒時執行內存泄漏檢測
  • debuggerControl : 判斷是否處於調試模式,調試模式中不會進行內存泄漏檢測
  • gcTrigger : 用於 GC,watchExecutor 首次檢測到可能的內存泄漏,會主動進行 GC,GC 以後會再檢測一次,仍然泄漏的斷定爲內存泄漏,進行後續操做
  • heapDumper : dump 內存泄漏處的 heap 信息,寫入 hprof 文件
  • heapDumpListener : 解析完 hprof 文件,進行回調,並通知 DisplayLeakService 彈出提醒
  • excludedRefs : 排除能夠忽略的泄漏路徑

接下來,咱們一塊兒來看一下 ActivityRefWatcher.install 方法

ActivityRefWatcher.install((Application) context, refWatcher);
複製代碼
public final class ActivityRefWatcher {

  /** @deprecated Use {@link #install(Application, RefWatcher)}. */
  @Deprecated
  public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
    install(application, refWatcher);
  }

  public static void install(Application application, RefWatcher refWatcher) {
    new ActivityRefWatcher(application, refWatcher).watchActivities();
  }

  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new Application.ActivityLifecycleCallbacks() {
        @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        }

        @Override public void onActivityStarted(Activity activity) {
        }

        @Override public void onActivityResumed(Activity activity) {
        }

        @Override public void onActivityPaused(Activity activity) {
        }

        @Override public void onActivityStopped(Activity activity) {
        }

        @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
        }

        @Override public void onActivityDestroyed(Activity activity) {
          ActivityRefWatcher.this.onActivityDestroyed(activity);
        }
      };

  private final Application application;
  private final RefWatcher refWatcher;

  /**
   * Constructs an {@link ActivityRefWatcher} that will make sure the activities are not leaking
   * after they have been destroyed.
   */
  public ActivityRefWatcher(Application application, RefWatcher refWatcher) {
    this.application = checkNotNull(application, "application");
    this.refWatcher = checkNotNull(refWatcher, "refWatcher");
  }

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

  public void watchActivities() {
    // Make sure you don't get installed twice. stopWatchingActivities(); application.registerActivityLifecycleCallbacks(lifecycleCallbacks); } public void stopWatchingActivities() { application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks); } } 複製代碼

install 來講,主要作如下事情

  • 建立 ActivityRefWatcher,並調用 watchActivities 監聽 activity 的生命週期
  • 在 activity 被銷燬的時候,會回調 lifecycleCallbacks 的 onActivityDestroyed 方法,這時候會調用 onActivityDestroyed 去分析,而 onActivityDestroyed 方法又會回調 refWatcher.watch(activity)

咱們回到 refWatcher.watch 方法

public void watch(Object watchedReference) {
  watch(watchedReference, "");
}

/**
 * Watches the provided references and checks if it can be GCed. This method is non blocking,
 * the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed
 * with.
 *
 * @param referenceName An logical identifier for the watched object.
 */
public void watch(Object watchedReference, String referenceName) {
  if (this == DISABLED) {
    return;
  }
  checkNotNull(watchedReference, "watchedReference");
  checkNotNull(referenceName, "referenceName");
  final long watchStartNanoTime = System.nanoTime();
  // 保證 key 的惟一性
  String key = UUID.randomUUID().toString();
  // 添加到 set 集合中
  retainedKeys.add(key);
  // 穿件 KeyedWeakReference 對象
  final KeyedWeakReference reference =
      new KeyedWeakReference(watchedReference, key, referenceName, queue);

  ensureGoneAsync(watchStartNanoTime, reference);
}


複製代碼
  • retainedKeys : 一個 Set 集合,每一個檢測的對象都對應着一個惟一的 key,存儲在 retainedKeys 中
  • KeyedWeakReference : 自定義的弱引用,持有檢測對象和對用的 key 值

咱們先來看一下 KeyedWeakReference ,能夠看到 KeyedWeakReference 繼承於 WeakReference,並定義了 key,name 字段

final class KeyedWeakReference extends WeakReference<Object> {
  public final String 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");
  }
}
複製代碼
  • key 對應的 key 值名稱
  • referenceQueue 引用隊列,當結合 Refrence 使用的時候,垃圾回收器回收的時候,會把相應的對象加入到 refrenceQueue 中。

弱引用和引用隊列 ReferenceQueue 聯合使用時,若是弱引用持有的對象被垃圾回收,Java 虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。即 KeyedWeakReference 持有的 Activity 對象若是被垃圾回收,該對象就會加入到引用隊列 queue 中。具體的能夠參考個人這一篇博客 java 源碼系列 - 帶你讀懂 Reference 和 ReferenceQueue

ensureGoneAsync 方法

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

ensureGoneAsync 這個方法,在 watchExecutor 的回調裏面執行了 ensureGone 方法,watchExecutor 是 AndroidWatchExecutor 的實例。

接下來,咱們一塊兒來看一下 watchExecutor,主要關注 execute 方法

watchExecutor

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(Retryable retryable) {
    // 當前線程是主線程
    if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
      waitForIdle(retryable, 0);
    } else { // 當前線程不是主線程
      postWaitForIdle(retryable, 0);
    }
  }

   --------
}

複製代碼

execute 方法,首先判斷是不是主線程,若是是主線程,調用 waitForIdle 方法,等待空閒的時候執行,若是不是主線程,調用 postWaitForIdle 方法。咱們一塊兒來看一下 postWaitForIdle 和 waitForIdle 方法。

// 調用 mainHandler 的 post 方法,,確保在主線程中執行
  void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
    mainHandler.post(new Runnable() {
      @Override public void run() {
        waitForIdle(retryable, failedAttempts);
      }
    });
  }

 // 噹噹前線程 looper 空閒的時候執行
  void waitForIdle(final Retryable retryable, final int failedAttempts) {
    // This needs to be called from the main thread.
    // 當 looper 空閒的時候,會回調 queueIdle 方法
    Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
      @Override public boolean queueIdle() {
        postToBackgroundWithDelay(retryable, failedAttempts);
        return false;
      }
    });
  }
複製代碼

能夠看到 postWaitForIdle 方法實際上是 調用 mainHandler 的 post 方法,,確保在主線程中執行,以後再 runnable 的 run 方法在調用 waitForIdle 方法。而 waitForIdle 方法是在等當前 looper 空閒以後,執行 postToBackgroundWithDelay 方法

void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
  // 取 Math.pow(2, failedAttempts), maxBackoffFactor 的最小值,maxBackoffFactor = Long.MAX_VALUE / 5,
  // 第一次執行的時候 failedAttempts 是 0 ,因此 exponentialBackoffFactor 是1
    long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
    // initialDelayMillis 的默認值是 5
    long delayMillis = initialDelayMillis * exponentialBackoffFactor;
    // 因此第一次延遲執行的時候是 5s,若
    backgroundHandler.postDelayed(new Runnable() {
      @Override public void run() {
        Retryable.Result result = retryable.run();
        // 過 result == RETRY,再次調用 postWaitForIdle,下一次的 delayMillis= 上一次的  delayMillis *2;
        // 正常狀況下,不會返回 RETRY,當 heapDumpFile == RETRY_LATER (即 dump heap 失敗的時候),會返回 RETRY
        if (result == RETRY) {
          postWaitForIdle(retryable, failedAttempts + 1);
        }
      }
    }, delayMillis);
  }
複製代碼

postToBackgroundWithDelay 方法有點相似遞歸,正常狀況下,若 retryable.run() 返回的結果不等於 RETRY,只會執行一次。若 retryable.run() 返回 RETRY,則會執行屢次,退出的條件是 retryable.run() 返回結果不等於 RETRY;

delay 的時間 取 Math.pow(2, failedAttempts), maxBackoffFactor 兩個數的最小值,maxBackoffFactor = Long.MAX_VALUE / 5,而,第一次執行的時候 failedAttempts 是 0 ,因此 exponentialBackoffFactor 是 1,即 delayMillis = initialDelayMillis * exponentialBackoffFactor= 5*1=5;

所以,綜合上面的例子,第一次執行的時間是 activity destroy 以後 5s。

OK,咱們回到 ensureGone 方法,這纔是咱們的重點

@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
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;
  }
  // 判斷 reference,即 activity 是否內回收了,若被回收了,直接返回
  if (gone(reference)) {
    return DONE;
  }
  // 調用 gc 方法進行垃圾回收
  gcTrigger.runGc();
   // 移除已經被回收的引用
  removeWeaklyReachableReferences();
  // activity 尚未被回收,證實發生內存泄露
  if (!gone(reference)) {
    long startDumpHeap = System.nanoTime();
    long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
    // dump heap,並生成相應的 hprof 文件
    File heapDumpFile = heapDumper.dumpHeap();
    
    if (heapDumpFile == RETRY_LATER) {// dump the heap 失敗的時候
      // Could not dump the heap.
      return RETRY;
    }
    long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
    // 分析 hprof 文件
    heapdumpListener.analyze(
        new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
            gcDurationMs, heapDumpDurationMs));
  }
  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;
  // 遍歷 queue ,並從 retainedKeys set 集合中移除
  while ((ref = (KeyedWeakReference) queue.poll()) != null) {
    retainedKeys.remove(ref.key);
  }
}


複製代碼

gone(reference) 方法,判斷 retainedKeys set 集合,是否還含有 reference,若沒有,證實已經被回收了;若含有,可能已經發生內存泄露。由於咱們知道 refrence 被回收的時候,會被加進 queue 裏面,值調用 gone 方法判斷的時候,咱們已經遍歷 queue 移除掉 retainedKeys 裏面的 refrence,若含有,證實 refrence 沒有被回收,之因此說可能發生內存泄露,是由於 gc 回收器可能尚未回收。

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


複製代碼

gcTrigger.runGc() 的主要做用是促發 gc,進行回收。

GcTrigger DEFAULT = new GcTrigger() {
    @Override public void runGc() {
      // Code taken from AOSP FinalizationTest:
      // https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
      // java/lang/ref/FinalizationTester.java
      // System.gc() does not garbage collect every time. Runtime.gc() is
      // more likely to perfom a gc.
      Runtime.getRuntime().gc();
      enqueueReferences();
      System.runFinalization();
    }

    private void enqueueReferences() {
      // Hack. We don't have a programmatic way to wait for the reference queue daemon to move // references to the appropriate queues. try { Thread.sleep(100); } catch (InterruptedException e) { throw new AssertionError(); } } }; 複製代碼

ok,咱們在回到 ensureGoneAsync 方法,整理一下它的流程

  • Activity onDestroy 5s 以後,檢測 activity 的弱引用 refrence 有沒有被回收,若被回收,證實沒有發生內存泄露,若沒有被回收,繼續下面流程
  • 調用 gcTrigger.runGc() 促發垃圾回收機器進行回收
  • 再次檢測 activity 的弱引用 refrence 有沒有被回收,若被回收,證實沒有發生內存泄露,若沒有被回收,則認爲發生內存泄露
  • dump heap,生成 hprof。
  • 分析 hprof 文件,找到泄露路徑,發送到通知欄

關於如何 dump 和 如何解析hprof

關於如何 dump

這裏主要是調用 AndroidHeapDumper 的 dumpHeap 方法,而裏面比較重要的是調用 Debug.dumpHprofData 生成 hprof 文件。

AndroidHeapDumper#dumpHeap

@SuppressWarnings("ReferenceEquality") // Explicitly checking for named null.
@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;
  }

  Toast toast = waitingForToast.get();
  try {
    Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
    cancelToast(toast);
    return heapDumpFile;
  } catch (Exception e) {
    CanaryLog.d(e, "Could not dump heap");
    // Abort heap dump
    return RETRY_LATER;
  }
}
複製代碼

如何解析hprof

當發生了泄漏就會生成 HeapDump 對象而後就會進入下面這個方法去啓動 HeapAnalyzerServiceService 來進行分析

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

關於如解析 hprof,請自行了解 haha 庫的用法即原理

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

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

    try {
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      HprofParser parser = new HprofParser(buffer);
      Snapshot snapshot = parser.parse();
      deduplicateGcRoots(snapshot);

      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);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }
複製代碼

通過解析以後機會把數據傳遞到 DisplayLeakService ,Service 會根據傳入進來的數據發送通知欄通知,當你點擊對應的通知進入DisplayLeakActivity界面就能顯示泄漏日誌了。


總結:

LeakCanary 的原理總結以下

  • 監聽 Activity 的生命週期
  • 在 onDestroy 的時候,建立相應的 Refrence 和 RefrenceQueue,並啓動後臺進程去檢測
  • 一段時間以後,從 RefrenceQueue 讀取,若讀取不到相應 activity 的 Refrence,有可能發生泄露了,這個時候,再促發 gc,一段時間以後,再去讀取,若在從 RefrenceQueue 仍是讀取不到相應 activity 的 refrence,能夠判定是發生內存泄露了
  • 發生內存泄露以後,dump,分析 hprof 文件,找到泄露路徑(使用 haha 庫分析)

其中,比較重要的是如何肯定是否發生內存泄露,而如何肯定發生內存泄露最主要的原理是經過 Refrence 和 RefrenceQueue。悄悄地提醒你一下,面試必備。

最後,用一張圖片來表示 leakCannary 的執行流程,該圖片來自 深刻理解 Android 之 LeakCanary 源碼解析

image

java 源碼系列 - 帶你讀懂 Reference 和 ReferenceQueue

blog.csdn.net/gdutxiaoxu/…

一步步拆解 LeakCanary

blog.csdn.net/gdutxiaoxu/…

最後的最後

賣一下廣告,歡迎你們關注個人微信公衆號,掃一掃下方二維碼或搜索微信號 stormjun,便可關注。 目前專一於 Android 開發,主要分享 Android開發相關知識和一些相關的優秀文章,包括我的總結,職場經驗等。

image
相關文章
相關標籤/搜索