剖析LeakCanary—— 下篇

不要小看那些信靠上帝的人,祂能借着他們成就大事

本篇文章就是爲了說明LeakCanary是如何打造Android平臺上的內存泄漏監測過程。按照剖析LeakCanary—— 中篇的思路進行展開論述。

1. 總覽LeakCanary的Android實現過程

這裏先羅列一些涉及Android平臺的具體實現類

  1. AndroidWatchExecutor —> WatchExecutor
  2. AndroidRefWatcherBuilder —> RefWatcherBuilder
  3. LeakCanaryInternals : 可以理解爲Android平臺的工具類
  4. AndroidExcludedRefs:枚舉(過濾Android SDK中的內存泄漏)
  5. HeapAnalyzerService -->ForegroundService —> IntentService
  6. DisplayLeakService —> AbstractAnalysisResultService —> ForegroundService —> IntentService
  7. ServiceHeapDumpListener —> HeapDump.Listener 監聽分析過程及結果。(HeapAnalyzerService,DisplayLeakService就是從這裏啓動的)

HeapAnalyzerService:分析堆內容肯定是個耗時操作,故LeakCanary採用開啓IntentService的方式處理;
同理,
DisplayLeakService :在使用LeakCanary的過程中,我們會看到一些通知(Notification) 以及展示泄漏情況的DisplayLeakActivity

具體我們可以看到:
HeapAnalyzerService 在自己的runAnalysis()中啓動;
DisplayLeakServiceHeapAnalyzerServiceonHandleIntentInForeground() 需要返回分析結果時才啓動(具體可以看 :AbstractAnalysisResultService)。

在分析結果沒有出來之前,DisplayLeakService的服務是不會啓動的


2. AndroidWatchExecutor

AndroidWatchExecutorrLeakCanaryAndroid系統中的監測者。

這裏利用**IdleHandler且是主線程的MessageQueue的IdleHandler **充分了體現LeakCanary的設計之巧妙啊…

關於IdleHandler可以查看 Handler的前世今生5 —— MessageQueue

通過MessageQueue的next()中我們可以知道,IdleHandler 只有在消息隊列爲空 或者 第一個消息在隊列中但還沒有到執行時間 的情況下會被執行。即:線程阻塞時會執行IdleHandler。

public final class AndroidWatchExecutor implements WatchExecutor {
  private final Handler mainHandler;
  private final Handler backgroundHandler;

  ...
	
	@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;
    backgroundHandler.postDelayed(new Runnable() {
      @Override public void run() {
        Retryable.Result result = retryable.run();
        if (result == RETRY) {
          postWaitForIdle(retryable, failedAttempts + 1);
        }
      }
    }, delayMillis);
  }

}

3. AndroidRefWatcherBuilder

AndroidRefWatcherBuilder纔是LeakCanaryAndroid系統中真正執行者。

爲了更好滴了解 AndroidRefWatcherBuilder ,我們可以從實際出發,根據我們在Android的使用來探索其精髓。


3.1 LeakCanary在Android上的使用

LeakCanary在Android開發中,我們一般都是使用下面的代碼進行初始化的。

public class MainApp 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...
  }
}

我相信絕大多數地Android開發者對於上述代碼都不會陌生。

3.2 走進LeakCanary
  1. install()

創建一個AndroidRefWatcherBuilder對象並使其開始工作(過濾一些Android不必要的分析內容)
PS : AndroidExcludedRefs 是個枚舉,並不是ExcludedRefs的子類

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

作用:判斷監測內存泄漏的進程是否已經創建,從而避免重複創建。
實現:本質上 調用的是 LeakCanaryInternalsisInServiceProcess()方法。感興趣的可以深入瞭解一下 LeakCanaryInternals 這個類。

這裏判斷的是HeapAnalyzerService,也證明了DisplayLeakService 並不是一直存在的。

public static boolean isInAnalyzerProcess(@NonNull Context context) {
    Boolean isInAnalyzerProcess = LeakCanaryInternals.isInAnalyzerProcess;
    // This only needs to be computed once per process.
    if (isInAnalyzerProcess == null) {
      isInAnalyzerProcess = isInServiceProcess(context, HeapAnalyzerService.class);
      LeakCanaryInternals.isInAnalyzerProcess = isInAnalyzerProcess;
    }
    return isInAnalyzerProcess;
  }
3.3 AndroidRefWatcherBuilder真面目

isAssignableFrom() 是從類繼承的角度判斷從屬關係的,調用者與參數都必須是C lass對象。

用法:父類.class.isAssignableFrom(子類.class)
返回值:false —— 調用者是參數的子類;否則返回true;
具體可以參考:java中isAssignableFrom()方法

public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {
// 注意是默認觀察的延時時間 5s
private static final long DEFAULT_WATCH_DELAY_MILLIS = SECONDS.toMillis(5);

  private final Context context;
  private boolean watchActivities = true;
  private boolean watchFragments = true;
  // 這裏默認是false,意不意外?
  private boolean enableDisplayLeakActivity = false;

 // 這裏是涉及分析堆內存信息的服務
 public @NonNull AndroidRefWatcherBuilder listenerServiceClass(
      @NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
     // 注意這裏:isAssignableFrom()方法
     // 只要傳入的參數是DisplayLeakService或者其子類,就返回true.
    enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
    return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
  }
	
 // 真正的監聽操作在這裏
 public @NonNull RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
       // 是否要顯示 DisplayLeakActivity ,爲我們展示泄漏情況
      if (enableDisplayLeakActivity) {
        LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
      }
      // 觀察Activity的泄漏情況
      if (watchActivities) {
        ActivityRefWatcher.install(context, refWatcher);
      }
      // 觀察Fragment的泄漏情況
      if (watchFragments) {
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }
	
	...
	
}

相信你已經注意到ActivityRefWatcherFragmentRefWatcher

簡要看一下:

  1. ActivityRefWatcher:在Activity銷燬的時候進行監聽
 private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          refWatcher.watch(activity);
        }
      };
  1. FragmentRefWatcher是接口,其子類:
    1. SupportFragmentRefWatcher
    2. AndroidOFragmentRefWatcher (SDK >= 26)
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
      new FragmentManager.FragmentLifecycleCallbacks() {

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

        @Override public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
          refWatcher.watch(fragment);
        }
      };

4. 總結

LeakCanary 充分利用了Android系統的特點來達到監測並分析內存泄漏的目的。這裏需要我們 重點回味的技巧:

  1. 使用IdleHandler來監測;
  2. 使用服務HeapAnalyzerService進行堆內存分析,若沒有返回分析結果,則不會啓動DisplayLeakService。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章