LeakCanary 源碼解析

LeakCanary 是由 Square 開源的針對 AndroidJava 的內存泄漏檢測工具。java

使用

LeakCanary 的集成過程很簡單,首先在 build.gradle 文件中添加依賴:android

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.5.4'
  releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'
}
複製代碼

debugrelease 版本中使用的是不一樣的庫。LeakCanary 運行時會常常執行 GC 操做,在 release 版本中會影響效率。android-no-op 版本中基本沒有邏輯實現,用於 release 版本。git

而後實現本身的 Application 類:github

public class ExampleApplication extends Application {

  @Override public void onCreate() {
    super.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);
    // Normal app init code...
  }
}
複製代碼

這樣就集成完成了。當 LeakCanary 檢測到內存泄露時,會自動彈出 Notification 通知開發者發生內存泄漏的 Activity 和引用鏈,以便進行修復。bash

源碼分析

從入口函數 LeakCanary.install(this) 開始分析:服務器

LeakCanary.install

LeakCanary.java微信

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

LeakCanary.refWatcher

LeakCanary.java併發

/** Builder to create a customized {@link RefWatcher} with appropriate Android defaults. */
public static AndroidRefWatcherBuilder refWatcher(Context context) {
  return new AndroidRefWatcherBuilder(context);
}
複製代碼

refWatcher() 方法新建了一個 AndroidRefWatcherBuilder 對象,該對象繼承於 RefWatcherBuilder 類,配置了一些默認參數,利用建造者構建一個 RefWatcher 對象。app

AndroidRefWatcherBuilder.listenerServiceClass

AndroidRefWatcherBuilder.javadom

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

RefWatcherBuilder.java

/** @see HeapDump.Listener */
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
  this.heapDumpListener = heapDumpListener;
  return self();
}
複製代碼

DisplayLeakService.java

/** * Logs leak analysis results, and then shows a notification which will start {@link * DisplayLeakActivity}. * * You can extend this class and override {@link #afterDefaultHandling(HeapDump, AnalysisResult, * String)} to add custom behavior, e.g. uploading the heap dump. */
public class DisplayLeakService extends AbstractAnalysisResultService {}
複製代碼

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

RefWatcherBuilder.excludedRefs

RefWatcherBuilder.java

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() 方法構建 RefWatcher 實例並開始監聽 Activity 的引用:

AndroidRefWatcherBuilder.java

/** * Creates a {@link RefWatcher} instance and starts watching activity references (on ICS+). */
public RefWatcher buildAndInstall() {
  RefWatcher refWatcher = build();
  if (refWatcher != DISABLED) {
    LeakCanary.enableDisplayLeakActivity(context);
    ActivityRefWatcher.install((Application) context, refWatcher);
  }
  return refWatcher;
}
複製代碼

看一下主要的 build()install() 方法:

RefWatcherBuilder.build

RefWatcherBuilder.java

/** 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() 方法利用建造者模式構建 RefWatcher 實例,看一下其中的主要參數:

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

LeakCanary.enableDisplayLeakActivity

接下來就是最核心的 install() 方法,這裏就開始觀察 Activity 的引用了。在這以前還執行了一步操做,LeakCanary.enableDisplayLeakActivity(context);

public static void enableDisplayLeakActivity(Context context) {
  setEnabled(context, DisplayLeakActivity.class, true);
}
複製代碼

最後執行到 LeakCanaryInternals#setEnabledBlocking

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);
}
複製代碼

這裏啓用了 DisplayLeakActivity 而且顯示應用圖標。注意,這是指的不是你本身的應用圖標,是一個單獨的 LeakCanary 的應用,用於展現內存泄露歷史的,入口函數是 DisplayLeakActivity,在 AndroidManifest.xml 中能夠看到默認狀況下 android:enabled="false" :

<activity android:theme="@style/leak_canary_LeakCanary.Base" android:name=".internal.DisplayLeakActivity" android:process=":leakcanary" android:enabled="false" android:label="@string/leak_canary_display_activity_label" android:icon="@mipmap/leak_canary_icon" android:taskAffinity="com.squareup.leakcanary.${applicationId}" >
  <intent-filter>
    <action android:name="android.intent.action.MAIN"/>
    <category android:name="android.intent.category.LAUNCHER"/>
  </intent-filter>
</activity>
複製代碼

ActivityRefWatcher.install

ActivityRefWatcher.java

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

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

watchActivities() 方法中先解綁生命週期回調註冊 lifecycleCallbacks,再從新綁定,避免重複綁定。lifecycleCallbacks 監聽了 Activity 的各個生命週期,在 onDestroy() 中開始檢測當前 Activity 的引用。

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

    void onActivityDestroyed(Activity activity) {
      refWatcher.watch(activity);
    }
複製代碼

下面着重分析 RefWatcher 是如何檢測 Activity 的。

RefWatcher.watch

調用 RefWatcher#watch 檢測 ActivityRefWatcher.java

/** * Identical to {@link #watch(Object, String)} with an empty string reference name. * * @see #watch(Object, String) */
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();
  String key = UUID.randomUUID().toString();
  retainedKeys.add(key);
  final KeyedWeakReference reference =
      new KeyedWeakReference(watchedReference, key, referenceName, queue);

  ensureGoneAsync(watchStartNanoTime, reference);
}
複製代碼

watch() 方法的參數是 ObjectLeakCanary 並不只僅是針對 Android 的,它能夠檢測任何對象的內存泄漏,原理都是一致的。

這裏出現了幾個新面孔,先來了解一下各自是什麼:

  • retainedKeys : 一個 Set<String> 集合,每一個檢測的對象都對應着一個惟一的 key,存儲在 retainedKeys
  • KeyedWeakReference : 自定義的弱引用,持有檢測對象和對用的 key
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");
  }
}
複製代碼
  • queue : ReferenceQueue 對象,和 KeyedWeakReference 配合使用

這裏有個小知識點,弱引用和引用隊列 ReferenceQueue 聯合使用時,若是弱引用持有的對象被垃圾回收,Java 虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。即 KeyedWeakReference 持有的 Activity 對象若是被垃圾回收,該對象就會加入到引用隊列 queue 中。

接着看看具體的內存泄漏判斷過程:

RefWatcher.ensureGoneAsync

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

經過 watchExecutor 執行檢測操做,這裏的 watchExecutorAndroidWatchExecutor 對象。

@Override protected WatchExecutor defaultWatchExecutor() {
  return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}
複製代碼

DEFAULT_WATCH_DELAY_MILLIS 爲 5 s。

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;
}
複製代碼

看看其中用到的幾個對象:

  • mainHandler : 主線程消息隊列
  • handlerThread : 後臺線程,HandlerThread 對象,線程名爲 LeakCanary-Heap-Dump
  • backgroundHandler : 上面的後臺線程的消息隊列
  • initialDelayMillis : 5 s,即以前的 DEFAULT_WATCH_DELAY_MILLIS
@Override public void execute(Retryable retryable) {
  if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
    waitForIdle(retryable, 0);
  } else {
    postWaitForIdle(retryable, 0);
  }
}

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

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;
    }
  });
}
複製代碼

在具體的 execute() 過程當中,不論是 waitForIdle 仍是 postWaitForIdle,最終仍是要切換到主線程中執行。要注意的是,這裏的 IdleHandler 究竟是何時去執行?

咱們都知道 Handler 是循環處理 MessageQueue 中的消息的,當消息隊列中沒有更多消息須要處理的時候,且聲明瞭 IdleHandler 接口,這是就會去處理這裏的操做。即指定一些操做,當線程空閒的時候來處理。當主線程空閒時,就會通知後臺線程延時 5 秒執行內存泄漏檢測工做。

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);
}
複製代碼

下面是真正的檢測過程,AndroidWatchExecutor 在執行時調用 ensureGone() 方法:

RefWatcher.ensureGone

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);
    heapdumpListener.analyze(
        new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
            gcDurationMs, heapDumpDurationMs));
  }
  return DONE;
}
複製代碼

再重複一次幾個變量的含義,retainedKeys 是一個 Set集合,存儲檢測對象對應的惟一 key 值,queue是一個引用隊列,存儲被垃圾回收的對象。

主要過程有一下幾步:

RefWatcher.emoveWeaklyReachableReferences()

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);
  }
}
複製代碼

遍歷引用隊列 queue,判斷隊列中是否存在當前 Activity 的弱引用,存在則刪除 retainedKeys 中對應的引用的 key值。

RefWatcher.gone()

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

判斷 retainedKeys 中是否包含當前 Activity 引用的 key 值。

若是不包含,說明上一步操做中 retainedKeys 移除了該引用的 key 值,也就說上一步操做以前引用隊列 queue 中包含該引用,GC 處理了該引用,未發生內存泄漏,返回 DONE,再也不往下執行。

若是包含,並不會當即斷定發生內存泄漏,可能存在某個對象已經不可達,可是還沒有進入引用隊列 queue。這時會主動執行一次 GC 操做以後再次進行判斷。

gcTrigger.runGc()

/** * Called when a watched reference is expected to be weakly reachable, but hasn't been enqueued * in the reference queue yet. This gives the application a hook to run the GC before the {@link * RefWatcher} checks the reference queue again, to avoid taking a heap dump if possible. */
public interface GcTrigger {
  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();
      }
    }
  };

  void runGc();
}
複製代碼

注意這裏調用 GC 的寫法,並非使用 System.gcSystem.gc 僅僅只是通知系統在合適的時間進行一次垃圾回收操做,實際上並不能保證必定執行。

主動進行 GC 以後會再次進行斷定,過程同上。首先調用 removeWeaklyReachableReferences() 清除 retainedKeys 中弱引用的 key 值,再判斷是否移除。若是仍然沒有移除,斷定爲內存泄漏。

內存泄露結果處理

AndroidHeapDumper.dumpHeap

斷定內存泄漏以後,調用 heapDumper.dumpHeap() 進行處理:

AndroidHeapDumper.java

@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;
  }
}
複製代碼

leakDirectoryProvider.newHeapDumpFile() 新建了 hprof 文件,而後調用 Debug.dumpHprofData() 方法 dump 當前堆內存並寫入剛纔建立的文件。

回到 RefWatcher.ensureGone() 方法中,生成 heapDumpFile 文件以後,經過 heapdumpListener 分析。

ServiceHeapDumpListener.analyze

heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
複製代碼

這裏的 heapdumpListenerServiceHeapDumpListener 對象,接着進入 ServiceHeapDumpListener.runAnalysis() 方法。

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

這裏的 listenerServiceClass 指的是 DisplayLeakService.class,文章開頭提到的 AndroidRefWatcherBuilder 中進行了配置。

@Override protected HeapDump.Listener defaultHeapDumpListener() {
  return new ServiceHeapDumpListener(context, DisplayLeakService.class);
}
複製代碼

HeapAnalyzerService.runAnalysis

HeapAnalyzerService.runAnalysis() 方法中啓動了它本身,傳遞了兩個參數,DisplayLeakService 類名和要分析的 heapDump。啓動本身後,在 onHandleIntent 中進行處理。

/** * This service runs in a separate process to avoid slowing down the app process or making it run * out of memory. */
public final class HeapAnalyzerService extends IntentService {

  private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
  private static final String HEAPDUMP_EXTRA = "heapdump_extra";

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

  public HeapAnalyzerService() {
    super(HeapAnalyzerService.class.getSimpleName());
  }

  @Override protected void onHandleIntent(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);

    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }
}
複製代碼

heapAnalyzer.checkForLeak

checkForLeak 方法中主要使用了 Square 公司的另外一個庫 haha 來分析 Android heap dump,獲得結果後回調給 DisplayLeakService

AbstractAnalysisResultService.sendResultToListener

public static void sendResultToListener(Context context, String listenerServiceClassName, HeapDump heapDump, AnalysisResult result) {
  Class<?> listenerServiceClass;
  try {
    listenerServiceClass = Class.forName(listenerServiceClassName);
  } catch (ClassNotFoundException e) {
    throw new RuntimeException(e);
  }
  Intent intent = new Intent(context, listenerServiceClass);
  intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
  intent.putExtra(RESULT_EXTRA, result);
  context.startService(intent);
}
複製代碼

一樣在 onHandleIntent 中進行處理。

DisplayLeakService.onHandleIntent

@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();
  }
}
複製代碼

DisplayLeakService.onHeapAnalyzed

調用 onHeapAnalyzed() 以後,會將 hprof 文件刪除。

DisplayLeakService.java

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

  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) {
    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);
}
複製代碼

根據分析結果,調用 showNotification() 方法構建了一個 Notification 向開發者通知內存泄漏。

public static void showNotification(Context context, CharSequence contentTitle, CharSequence contentText, PendingIntent pendingIntent, int notificationId) {
  NotificationManager notificationManager =
      (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

  Notification notification;
  Notification.Builder builder = new Notification.Builder(context) //
      .setSmallIcon(R.drawable.leak_canary_notification)
      .setWhen(System.currentTimeMillis())
      .setContentTitle(contentTitle)
      .setContentText(contentText)
      .setAutoCancel(true)
      .setContentIntent(pendingIntent);
  if (SDK_INT >= O) {
    String channelName = context.getString(R.string.leak_canary_notification_channel);
    setupNotificationChannel(channelName, notificationManager, builder);
  }
  if (SDK_INT < JELLY_BEAN) {
    notification = builder.getNotification();
  } else {
    notification = builder.build();
  }
  notificationManager.notify(notificationId, notification);
}
複製代碼

DisplayLeakService.afterDefaultHandling

最後還會執行一個空實現的方法 afterDefaultHandling

/** * You can override this method and do a blocking call to a server to upload the leak trace and * the heap dump. Don't forget to check {@link AnalysisResult#leakFound} and {@link * AnalysisResult#excludedLeak} first. */
protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
}
複製代碼

你能夠重寫這個方法進行一些自定義的操做,好比向服務器上傳泄漏的堆棧信息等。

這樣,LeakCanary 就完成了整個內存泄漏檢測的過程。能夠看到,LeakCanary 的設計思路十分巧妙,同時也很清晰,有不少有意思的知識點,像對於弱引用和 ReferenceQueue 的使用, IdleHandler 的使用,四大組件的開啓和關閉等等,都很值的你們去深究。

文章同步更新於微信公衆號: 秉心說 , 專一 Java 、 Android 原創知識分享,LeetCode 題解,歡迎關注!

相關文章
相關標籤/搜索