Android主流三方庫源碼分析(6、深刻理解Leakcanary源碼)

前言

成爲一名優秀的Android開發,須要一份完備的知識體系,在這裏,讓咱們一塊兒成長爲本身所想的那樣~。

在Android主流三方庫源碼分析系列的前幾篇文章中,筆者已經對網絡、圖片、數據庫、響應式編程中最熱門的第三方開源框架進行了較爲深刻地講解,若是有朋友對這四塊感興趣的話,能夠去了解下。本篇,我將會對Android中的內存泄露檢測框架Leakcanary的源碼流程進行詳細地講解。java

1、原理概述

首先,筆者仔細查看了Leakcanary官方的github倉庫,最重要的即是對Leakcanary是如何起做用的(即原理)這一問題進行了闡述,我本身把它翻譯成了易於理解的文字,主要分爲以下7個步驟:android

  • 一、RefWatcher.watch()建立了一個KeyedWeakReference用於去觀察對象。
  • 二、而後,在後臺線程中,它會檢測引用是否被清除了,而且是否沒有觸發GC。
  • 三、若是引用仍然沒有被清除,那麼它將會把堆棧信息保存在文件系統中的.hprof文件裏。
  • 四、HeapAnalyzerService被開啓在一個獨立的進程中,而且HeapAnalyzer使用了HAHA開源庫解析了指定時刻的堆棧快照文件heap dump。
  • 五、從heap dump中,HeapAnalyzer根據一個獨特的引用key找到了KeyedWeakReference,而且定位了泄露的引用。
  • 六、HeapAnalyzer爲了肯定是否有泄露,計算了到GC Roots的最短強引用路徑,而後創建了致使泄露的鏈式引用。
  • 七、這個結果被傳回到app進程中的DisplayLeakService,而後一個泄露通知便展示出來了。

官方的原理簡單來解釋就是這樣的:在一個Activity執行完onDestroy()以後,將它放入WeakReference中,而後將這個WeakReference類型的Activity對象與ReferenceQueque關聯。這時再從ReferenceQueque中查看是否有沒有該對象,若是沒有,執行gc,再次查看,仍是沒有的話則判斷髮生內存泄露了。最後用HAHA這個開源庫去分析dump以後的heap內存。git

2、簡單示例

下面這段是Leakcanary官方倉庫的示例代碼:github

首先在你項目app下的build.gradle中配置:數據庫

dependencies {
  debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.2'
  releaseImplementation   'com.squareup.leakcanary:leakcanary-android-no-op:1.6.2'
  // 可選,若是你使用支持庫的fragments的話
  debugImplementation   'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'
}
複製代碼

而後在你的Application中配置:編程

public class WanAndroidApp extends Application {

    private RefWatcher refWatcher;
    
    public static RefWatcher getRefWatcher(Context context) {
        WanAndroidApp application = (WanAndroidApp)     context.getApplicationContext();
        return application.refWatcher;
    }

    @Override public void onCreate() {
      super.onCreate();
      if (LeakCanary.isInAnalyzerProcess(this)) {
        // 1
        return;
      }
      // 2
      refWatcher = LeakCanary.install(this);
    }
}
複製代碼

在註釋1處,會首先判斷當前進程是不是Leakcanary專門用於分析heap內存的而建立的那個進程,即HeapAnalyzerService所在的進程,若是是的話,則不進行Application中的初始化功能。若是是當前應用所處的主進程的話,則會執行註釋2處的LeakCanary.install(this)進行LeakCanary的安裝。只需這樣簡單的幾行代碼,咱們就能夠在應用中檢測是否產生了內存泄露了。固然,這樣使用只會檢測Activity和標準Fragment是否發生內存泄漏,若是要檢測V4包的Fragment在執行完onDestroy()以後是否發生內存泄露的話,則須要在Fragment的onDestroy()方法中加上以下兩行代碼去監視當前的Fragment:json

RefWatcher refWatcher = WanAndroidApp.getRefWatcher(_mActivity);
refWatcher.watch(this);
複製代碼

上面的RefWatcher其實就是一個引用觀察者對象,是用於監測當前實例對象的引用狀態的。從以上的分析能夠了解到,核心代碼就是LeakCanary.install(this)這行代碼,接下來,就從這裏出發將LeakCanary一步一步進行拆解。緩存

3、源碼分析

一、LeakCanary#install()

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

在install()方法中的處理,能夠分解爲以下四步:性能優化

  • 一、refWatcher(application)
  • 二、鏈式調用listenerServiceClass(DisplayLeakService.class)
  • 三、鏈式調用excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
  • 四、鏈式調用buildAndInstall()

首先,咱們來看下第一步,這裏調用了LeakCanary類的refWatcher方法,以下所示:微信

public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
  return new AndroidRefWatcherBuilder(context);
}
複製代碼

而後新建了一個AndroidRefWatcherBuilder對象,再看看AndroidRefWatcherBuilder這個類。

二、AndroidRefWatcherBuilder

/** A {@link RefWatcherBuilder} with appropriate Android defaults. */
public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {

...

    AndroidRefWatcherBuilder(@NonNull Context context) {
        this.context = context.getApplicationContext();
    }

...
}
複製代碼

在AndroidRefWatcherBuilder的構造方法中僅僅是將外部傳入的applicationContext對象保存起來了。AndroidRefWatcherBuilder是一個適配Android平臺的引用觀察者構造器對象,它繼承了RefWatcherBuilder,RefWatcherBuilder是一個負責創建引用觀察者RefWatcher實例的基類構造器。繼續看看RefWatcherBuilder這個類。

三、RefWatcherBuilder

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

    ...
    
    public RefWatcherBuilder() {
        heapDumpBuilder = new HeapDump.Builder();
    }

    ...
}
複製代碼

在RefWatcher的基類構造器RefWatcherBuilder的構造方法中新建了一個HeapDump的構造器對象。其中HeapDump就是一個保存heap dump信息的數據結構

接着來分析下install()方法中的鏈式調用的listenerServiceClass(DisplayLeakService.class)這部分邏輯。

四、AndroidRefWatcherBuilder#listenerServiceClass()

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

在這裏,傳入了一個DisplayLeakService的Class對象,它的做用是展現泄露分析的結果日誌,而後會展現一個用於跳轉到顯示泄露界面DisplayLeakActivity的通知。在listenerServiceClass()這個方法中新建了一個ServiceHeapDumpListener對象,下面看看它內部的操做。

五、ServiceHeapDumpListener

public final class ServiceHeapDumpListener implements HeapDump.Listener {

    ...
    
    public ServiceHeapDumpListener(@NonNull final Context context,
        @NonNull final Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
      this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
      this.context = checkNotNull(context, "context").getApplicationContext();
    }
    
    ...
}
複製代碼

能夠看到這裏僅僅是在ServiceHeapDumpListener中保存了DisplayLeakService的Class對象和application對象。它的做用就是接收一個heap dump去分析。

而後咱們繼續看install()方法鏈式調用.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())的這部分代碼。先看AndroidExcludedRefs.createAppDefaults()。

六、AndroidExcludedRefs#createAppDefaults()

public enum AndroidExcludedRefs {

    ...

    public static @NonNull ExcludedRefs.Builder createAppDefaults() {
      return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
    }
    
    public static @NonNull 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;
    }
    
    ...
}
複製代碼

先來講下AndroidExcludedRefs這個類,它是一個enum類,它聲明瞭Android SDK和廠商定製的SDK中存在的內存泄露的case,根據AndroidExcludedRefs這個類的類名就可看出這些case都會被Leakcanary的監測過濾掉。目前這個版本是有46種這樣的case被包含在內,後續可能會一直增長。而後EnumSet.allOf(AndroidExcludedRefs.class)這個方法將會返回一個包含AndroidExcludedRefs元素類型的EnumSet。Enum是一個抽象類,在這裏具體的實現類是通用正規型的RegularEnumSet,若是Enum裏面的元素個數大於64,則會使用存儲大數據量的JumboEnumSet。最後,在createBuilder這個方法裏面構建了一個排除引用的建造器excluded,將各式各樣的case分門別類地保存起來再返回出去。

最後,咱們看到鏈式調用的最後一步buildAndInstall()。

七、AndroidRefWatcherBuilder#buildAndInstall()

private boolean watchActivities = true;
private boolean watchFragments = true;

public @NonNull RefWatcher buildAndInstall() {
    // 1
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    
    // 2
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      // 3
      LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      if (watchActivities) {
        // 4
        ActivityRefWatcher.install(context, refWatcher);
      }
      if (watchFragments) {
        // 5
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    // 6
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
}
複製代碼

首先,在註釋1處,會判斷LeakCanaryInternals.installedRefWatcher是否已經被賦值,若是被賦值了,則會拋出異常,警告 buildAndInstall()這個方法應該僅僅只調用一次,在此方法結束時,即在註釋6處,該LeakCanaryInternals.installedRefWatcher纔會被賦值。再來看註釋2處,調用了AndroidRefWatcherBuilder其基類RefWatcherBuilder的build()方法,咱們它是如何建造的。

八、RefWatcherBuilder#build()

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

    return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper, heapDumpListener,
        heapDumpBuilder);
}
複製代碼

能夠看到,RefWatcherBuilder包含了如下7個組成部分:

  • 一、excludedRefs : 記錄能夠被忽略的泄漏路徑

  • 二、heapDumpListener : 轉儲堆信息到hprof文件,並在解析完 hprof 文件後進行回調,最後通知 DisplayLeakService 彈出泄漏提醒

  • 三、debuggerControl : 判斷是否處於調試模式,調試模式中不會進行內存泄漏檢測。爲何呢?由於在調試過程當中可能會保留上一個引用從而致使錯誤信息上報

  • 四、heapDumper : 堆信息轉儲者,負責dump 內存泄漏處的 heap 信息到 hprof 文件

  • 五、watchExecutor : 線程控制器,在 onDestroy() 以後而且在主線程空閒時執行內存泄漏檢測

  • 六、gcTrigger : 用於 GC,watchExecutor 首次檢測到可能的內存泄漏,會主動進行 GC,GC 以後會再檢測一次,仍然泄漏的斷定爲內存泄漏,最後根據heapDump信息生成相應的泄漏引用鏈

  • 七、reachabilityInspectorClasses : 用於要進行可達性檢測的類列表。

最後,會使用建造者模式將這些組成部分構建成一個新的RefWatcher並將其返回。

咱們繼續看回到AndroidRefWatcherBuilder的註釋3處的 LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true)這行代碼。

九、LeakCanaryInternals#setEnabledAsync()

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

在這裏直接使用了AsyncTask內部自帶的THREAD_POOL_EXECUTOR線程池進行阻塞式地顯示DisplayLeakActivity。

而後咱們再繼續看AndroidRefWatcherBuilder的註釋4處的代碼。

十、ActivityRefWatcher#install()

public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();
    // 1
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
    
    // 2
    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
複製代碼

能夠看到,在註釋1處建立一個本身的activityRefWatcher實例,並在註釋2處調用了application的registerActivityLifecycleCallbacks()方法,這樣就可以監聽activity對應的生命週期事件了。繼續看看activityRefWatcher.lifecycleCallbacks裏面的操做。

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

public abstract class ActivityLifecycleCallbacksAdapter
implements Application.ActivityLifecycleCallbacks {

}
複製代碼

很明顯,這裏實現並重寫了Application的ActivityLifecycleCallbacks的onActivityDestroyed()方法,這樣便能在全部Activity執行完onDestroyed()方法以後調用 refWatcher.watch(activity)這行代碼進行內存泄漏的檢測了

咱們再看到註釋5處的FragmentRefWatcher.Helper.install(context, refWatcher)這行代碼,

十一、FragmentRefWatcher.Helper#install()

public interface FragmentRefWatcher {

    void watchFragments(Activity activity);

    final class Helper {
    
      private static final String SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME =
          "com.squareup.leakcanary.internal.SupportFragmentRefWatcher";
    
      public static void install(Context context, RefWatcher refWatcher) {
        List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
    
        // 1
        if (SDK_INT >= O) {
          fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
        }
    
        // 2
        try {
          Class<?> fragmentRefWatcherClass = Class.forName(SUPPORT_FRAGMENT_REF_WATCHER_CLASS_NAME);
          Constructor<?> constructor =
              fragmentRefWatcherClass.getDeclaredConstructor(RefWatcher.class);
          FragmentRefWatcher supportFragmentRefWatcher   =
              (FragmentRefWatcher) constructor.newInstance(refWatcher);
          fragmentRefWatchers.add(supportFragmentRefWatcher);
        } catch (Exception ignored) {
        }
    
        if (fragmentRefWatchers.size() == 0) {
          return;
        }
    
        Helper helper = new Helper(fragmentRefWatchers);
    
        // 3
        Application application = (Application) context.getApplicationContext();
        application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
      }
      
    ...
}
複製代碼

這裏面的邏輯很簡單,首先在註釋1處將Android標準的Fragment的RefWatcher類,即AndroidOfFragmentRefWatcher添加到新建立的fragmentRefWatchers中。在註釋2處使用反射將leakcanary-support-fragment包下面的SupportFragmentRefWatcher添加進來,若是你在app的build.gradle下沒有添加下面這行引用的話,則會拿不到此類,即LeakCanary只會檢測Activity和標準Fragment這兩種狀況

debugImplementation   'com.squareup.leakcanary:leakcanary-support-fragment:1.6.2'
複製代碼

繼續看到註釋3處helper.activityLifecycleCallbacks裏面的代碼。

private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
    new ActivityLifecycleCallbacksAdapter() {
      @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        for (FragmentRefWatcher watcher : fragmentRefWatchers) {
            watcher.watchFragments(activity);
        }
    }
};
複製代碼

能夠看到,在Activity執行完onActivityCreated()方法以後,會調用指定watcher的watchFragments()方法,注意,這裏的watcher可能有兩種,但不論是哪種,都會使用當前傳入的activity獲取到對應的FragmentManager/SupportFragmentManager對象,調用它的registerFragmentLifecycleCallbacks()方法,在對應的onDestroyView()和onDestoryed()方法執行完後,分別使用refWatcher.watch(view)和refWatcher.watch(fragment)進行內存泄漏的檢測,代碼以下所示。

@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
    View view = fragment.getView();
    if (view != null) {
        refWatcher.watch(view);
    }
}

@Override
public void onFragmentDestroyed(FragmentManagerfm, Fragment fragment) {
    refWatcher.watch(fragment);
}
複製代碼

注意,下面到真正關鍵的地方了,接下來分析refWatcher.watch()這行代碼。

十二、RefWatcher#watch()

public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    // 1
    String key = UUID.randomUUID().toString();
    // 2
    retainedKeys.add(key);
    // 3
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);

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

注意到在註釋1處使用隨機的UUID保證了每一個檢測對象對應 key 的惟一性。在註釋2處將生成的key添加到類型爲CopyOnWriteArraySet的Set集合中。在註釋3處新建了一個自定義的弱引用KeyedWeakReference,看看它內部的實現。

1三、KeyedWeakReference

final class KeyedWeakReference extends WeakReference<Object> {
    public final String key;
    public final String name;
    
    KeyedWeakReference(Object referent, String key, String name,
        ReferenceQueue<Object> referenceQueue) {
      // 1
      super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
      this.key = checkNotNull(key, "key");
      this.name = checkNotNull(name, "name");
    }
}
複製代碼

能夠看到,在KeyedWeakReference內部,使用了key和name標識了一個被檢測的WeakReference對象。在註釋1處,將弱引用和引用隊列 ReferenceQueue 關聯起來,若是弱引用reference持有的對象被GC回收,JVM就會把這個弱引用加入到與之關聯的引用隊列referenceQueue中。即 KeyedWeakReference 持有的 Activity 對象若是被GC回收,該對象就會加入到引用隊列 referenceQueue 中

接着咱們回到RefWatcher.watch()裏註釋4處的ensureGoneAsync()方法。

1四、RefWatcher#ensureGoneAsync()

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

在ensureGoneAsync()方法中,在註釋1處使用 watchExecutor 執行了註釋2處的 ensureGone 方法,watchExecutor 是 AndroidWatchExecutor 的實例。

下面看看watchExecutor內部的邏輯。

1五、AndroidWatchExecutor

public final class AndroidWatchExecutor implements WatchExecutor {

    ...
    
    public AndroidWatchExecutor(long initialDelayMillis)     {
      mainHandler = new Handler(Looper.getMainLooper());
      HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
      handlerThread.start();
      // 1
      backgroundHandler = new Handler(handlerThread.getLooper());
      this.initialDelayMillis = initialDelayMillis;
      maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
    }
    
    @Override public void execute(@NonNull Retryable retryable) {
      // 2
      if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
        waitForIdle(retryable, 0);
      } else {
        postWaitForIdle(retryable, 0);
      }
    }
    
    ...
}
複製代碼

在註釋1處AndroidWatchExecutor的構造方法中,注意到這裏使用HandlerThread的looper新建了一個backgroundHandler,後面會用到。在註釋2處,會判斷當前線程是不是主線程,若是是,則直接調用waitForIdle()方法,若是不是,則調用postWaitForIdle(),來看看這個方法。

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

很清晰,這裏使用了在構造方法中用主線程looper構造的mainHandler進行post,那麼waitForIdle()最終也會在主線程執行。接着看看waitForIdle()的實現。

private void waitForIdle(final Retryable retryable,     final int failedAttempts) {
  Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
    @Override public boolean queueIdle() {
      postToBackgroundWithDelay(retryable, failedAttempts);
      return false;
    }
  });
}
複製代碼

這裏MessageQueue.IdleHandler()回調方法的做用是當 looper 空閒的時候,會回調 queueIdle 方法,利用這個機制咱們能夠實現第三方庫的延遲初始化,而後執行內部的postToBackgroundWithDelay()方法。接下來看看它的實現。

private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
  long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts),     maxBackoffFactor);
  // 1
  long delayMillis = initialDelayMillis * exponentialBackoffFactor;
  // 2
  backgroundHandler.postDelayed(new Runnable() {
    @Override public void run() {
      // 3
      Retryable.Result result = retryable.run();
      // 4
      if (result == RETRY) {
        postWaitForIdle(retryable, failedAttempts +   1);
      }
    }
  }, delayMillis);
}
複製代碼

先看到註釋4處,能夠明白,postToBackgroundWithDelay()是一個遞歸方法,若是result 一直等於RETRY的話,則會一直執行postWaitForIdle()方法。在回到註釋1處,這裏initialDelayMillis 的默認值是 5s,所以delayMillis就是5s。在註釋2處,使用了在構造方法中用HandlerThread的looper新建的backgroundHandler進行異步延時執行retryable的run()方法。這個run()方法裏執行的就是RefWatcher的ensureGoneAsync()方法中註釋2處的ensureGone()這行代碼,繼續看它內部的邏輯。

1六、RefWatcher#ensureGone()

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

    // 1
    removeWeaklyReachableReferences();

    // 2
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    
    // 3
    if (gone(reference)) {
      return DONE;
    }
    
    // 4
    gcTrigger.runGc();
    removeWeaklyReachableReferences();
    
    // 5
    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;
}
複製代碼

在註釋1處,執行了removeWeaklyReachableReferences()這個方法,接下來分析下它的含義。

private void removeWeaklyReachableReferences() {
    KeyedWeakReference ref;
    while ((ref = (KeyedWeakReference) queue.poll()) != null) {
        retainedKeys.remove(ref.key);
    }
}
複製代碼

這裏使用了while循環遍歷 ReferenceQueue ,並從 retainedKeys中移除對應的Reference。

再看到註釋2處,當Android設備處於debug狀態時,會直接返回RETRY進行延時重試檢測的操做。在註釋3處,咱們看看gone(reference)這個方法的邏輯。

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

這裏會判斷 retainedKeys 集合中是否還含有 reference,若沒有,證實已經被回收了,若含有,可能已經發生內存泄露(或Gc尚未執行回收)。前面的分析中咱們知道了 reference 被回收的時候,會被加進 referenceQueue 裏面,而後咱們會調用removeWeaklyReachableReferences()遍歷 referenceQueue 移除掉 retainedKeys 裏面的 refrence

接着咱們看到註釋4處,執行了gcTrigger的runGc()方法進行垃圾回收,而後使用了removeWeaklyReachableReferences()方法移除已經被回收的引用。這裏咱們再深刻地分析下runGc()的實現。

GcTrigger DEFAULT = new GcTrigger() {
    @Override public void runGc() {
      // Code taken from AOSP FinalizationTest:
      // https://android.googlesource.com/platform/libc  ore/+/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 perform 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();
      }
    }
};
複製代碼

這裏並無使用System.gc()方法進行回收,由於system.gc()並不會每次都執行。而是從AOSP中拷貝一段GC回收的代碼,從而相比System.gc()更可以保證垃圾回收的工做

最後咱們分析下注釋5處的代碼處理。首先會判斷activity是否被回收,若是尚未被回收,則證實發生內存泄露,進行if判斷裏面的操做。在裏面先調用堆信息轉儲者heapDumper的dumpHeap()生成相應的 hprof 文件。這裏的heapDumper是一個HeapDumper接口,具體的實現是AndroidHeapDumper。咱們分析下AndroidHeapDumper的dumpHeap()方法是如何生成hprof文件的。

public File dumpHeap() {
    File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();

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

    ...
    
    try {
      Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
      ...
      
      return heapDumpFile;
    } catch (Exception e) {
      ...
      // Abort heap dump
      return RETRY_LATER;
    }
}
複製代碼

這裏的核心操做就是調用了Android SDK的API Debug.dumpHprofData() 來生成 hprof 文件

若是這個文件等於RETRY_LATER則表示生成失敗,直接返回RETRY進行延時重試檢測的操做。若是不等於的話,則表示生成成功,最後會執行heapdumpListener的analyze()對新建立的HeapDump對象進行泄漏分析。由前面對AndroidRefWatcherBuilder的listenerServiceClass()的分析可知,heapdumpListener的實現 就是ServiceHeapDumpListener,接着看到ServiceHeapDumpListener的analyze方法。

1七、ServiceHeapDumpListener#analyze()

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

能夠看到,這裏執行了HeapAnalyzerService的runAnalysis()方法,爲了不下降app進程的性能或佔用內存,這裏將HeapAnalyzerService設置在了一個獨立的進程中。接着繼續分析runAnalysis()方法裏面的處理。

public final class HeapAnalyzerService extends ForegroundService
implements AnalyzerProgressListener {

    ...

    public static void runAnalysis(Context context, HeapDump heapDump,
    Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
        ...
        
        ContextCompat.startForegroundService(context, intent);
    }

    ...
    
    @Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
        ...

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

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

這裏的HeapAnalyzerService實質是一個類型爲IntentService的ForegroundService,執行startForegroundService()以後,會回調onHandleIntentInForeground()方法。註釋1處,首先會新建一個HeapAnalyzer對象,顧名思義,它就是根據RefWatcher生成的heap dumps信息來分析被懷疑的泄漏是不是真的。在註釋2處,而後會調用它的checkForLeak()方法去使用haha庫解析 hprof文件,以下所示:

public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile,
  @NonNull String referenceKey,
  boolean computeRetainedSize) {
    ...
    
    try {
    listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
    // 1
    HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
  
    // 2
    HprofParser parser = new HprofParser(buffer);
    listener.onProgressUpdate(PARSING_HEAP_DUMP);
    Snapshot snapshot = parser.parse();
  
    listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
    // 3
    deduplicateGcRoots(snapshot);
    listener.onProgressUpdate(FINDING_LEAKING_REF);
  
    // 4
    Instance leakingRef = findLeakingReference(referenceKey, snapshot);

    // 5
    if (leakingRef == null) {
        return noLeak(since(analysisStartNanoTime));
    }
  
    // 6
    return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
    } catch (Throwable e) {
    return failure(e, since(analysisStartNanoTime));
    }
}
複製代碼

在註釋1處,會新建一個內存映射緩存文件buffer。在註釋2處,會使用buffer新建一個HprofParser解析器去解析出對應的引用內存快照文件snapshot。在註釋3處,爲了減小在Android 6.0版本中重複GCRoots帶來的內存壓力的影響,使用deduplicateGcRoots()刪除了gcRoots中重複的根對象RootObj。在註釋4處,調用了findLeakingReference()方法將傳入的referenceKey和snapshot對象裏面全部類實例的字段值對應的keyCandidate進行比較,若是沒有相等的,則表示沒有發生內存泄漏,直接調用註釋5處的代碼返回一個沒有泄漏的分析結果AnalysisResult對象。若是找到了相等的,則表示發生了內存泄漏,執行註釋6處的代碼findLeakTrace()方法返回一個有泄漏分析結果的AnalysisResult對象。

最後,咱們來分析下HeapAnalyzerService中註釋3處的AbstractAnalysisResultService.sendResultToListener()方法,很明顯,這裏AbstractAnalysisResultService的實現類就是咱們剛開始分析的用於展現泄漏路徑信息的DisplayLeakService對象。在裏面直接建立一個由PendingIntent構建的泄漏通知用於供用戶點擊去展現詳細的泄漏界面DisplayLeakActivity。核心代碼以下所示:

public class DisplayLeakService extends AbstractAnalysisResultService {

    @Override
    protected final void onHeapAnalyzed(@NonNull AnalyzedHeap analyzedHeap) {
    
        ...
        
        boolean resultSaved = false;
        boolean shouldSaveResult = result.leakFound || result.failure != null;
        if (shouldSaveResult) {
            heapDump = renameHeapdump(heapDump);
            // 1
            resultSaved = saveResult(heapDump, result);
        }
        
        if (!shouldSaveResult) {
            ...
            showNotification(null, contentTitle, contentText);
        } else if (resultSaved) {
            ...
            // 2
            PendingIntent pendingIntent =
                DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);

            ...
            
            showNotification(pendingIntent, contentTitle, contentText);
        } else {
             onAnalysisResultFailure(getString(R.string.leak_canary_could_not_save_text));
        }
    
    ...
}

@Override protected final void onAnalysisResultFailure(String failureMessage) {
    super.onAnalysisResultFailure(failureMessage);
    String failureTitle = getString(R.string.leak_canary_result_failure_title);
    showNotification(null, failureTitle, failureMessage);
}
複製代碼

能夠看到,只要當分析的堆信息文件保存成功以後,即在註釋1處返回的resultSaved爲true時,纔會執行註釋2處的邏輯,即建立一個供用戶點擊跳轉到DisplayLeakActivity的延時通知。最後給出一張源碼流程圖用於回顧本篇文章中LeakCanary的運做流程:

image

4、總結

性能優化一直是Android中進階和深刻的方向之一,而內存泄漏一直是性能優化中比較重要的一部分,Android Studio自身提供了MAT等工具去分析內存泄漏,可是分析起來比較耗時耗力,於是才誕生了LeakCanary,它的使用很是簡單,可是通過對它的深刻分析以後,才發現,簡單的API後面每每藏着許多複雜的邏輯處理,嘗試去領悟它們,你可能會發現不同的世界

參考連接:

一、LeakCanary V1.6.2 源碼

二、一步步拆解 LeakCanary

三、深刻理解 Android 之 LeakCanary 源碼解析

Contanct Me

● 微信:

歡迎關注個人微信:bcce5360

● 微信羣:

微信羣若是不能掃碼加入,麻煩你們想進微信羣的朋友們,加我微信拉你進羣。

● QQ羣:

2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎你們加入~

About me

很感謝您閱讀這篇文章,但願您能將它分享給您的朋友或技術羣,這對我意義重大。

但願咱們能成爲朋友,在 Github掘金上一塊兒分享知識。

相關文章
相關標籤/搜索