LeakCanary 細解初始化之一

       今天來寫一個關於LeakCanary-1.5.0的一些心得;我們移動端性能方面第一個就想到使用LeakCanary;但是很多使用LeakCanary無法解析,以及LeakCanary在不同手機上面也會有不同的一些提示;我這邊自己抽了一些時間,對LeakCanary的架構進行分析,以及代碼的實現過程進行分析;不知道一篇能不能寫完:下面先上一個圖;      

      這個是自己畫的LeakCanary的uml圖;僅僅畫了初始化的地方;這邊會從這個圖一步步解析LeakCanary的作用;首先我們先看到LeakCanary的初始化

     1)install(Application application)初始化方法

public static RefWatcher install(Application application) {
    return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        .buildAndInstall();
  }
 public static AndroidRefWatcherBuilder refWatcher(Context context) {
    return new AndroidRefWatcherBuilder(context);
  }

  其中new 一個AndroidRefWatcherBuilder對象,開始註冊listenerServiceClass並且註冊一個ExcludedRefs,最後進行組裝;

其中buildAndInstall方法裏面涉及到了Application註冊Activity的生命週期;主要在生命週期的裏面的onActivityDestroyed裏面進行觸發一個方法ActivityRefWatcher.this.onActivityDestroyed(activity);

    2)我們開始分析,第一步new AndroidRefWatcherBuilder並且設置ApplicationContext;

    3)設置listenerServiceClass;其中傳入了DisplayLeakService.class;(專門處理heapDump文件)

     DisplayLeakService的父類是AbstractAnalysisResultService,AbstractAnalysisResultService的父類是IntentService。其實走到這一步;大概能看出來做一個異步的耗時操作了;肯定是在onHandleIntent(Intent intent);並且sendResultToListener是進行發送耗時操作的方式;獲取heapDump文件,進行分析;重寫onHeapAnalyzed方法,具體在DisplayLeakService實現;(具體實現後面分析)

    4)  獲取AndroidExcludedRefs;創建ExcludedRefs對象;裏面更多包含的是配置

    先看一下createAndroidDefaults方法;其中裏面默認實現了SOFT_REFERENCES, FINALIZER_WATCHDOG_DAEMON, MAIN, LEAK_CANARY_THREAD, EVENT_RECEIVER__MMESSAGE_QUEUE;

     A) SOFT_REFERENCES:

a.註冊軟引用的類名

b.註冊弱引用的類名

c.註冊虛引用的類名

d.註冊Finalizer的類名(GC: 從一個對象變得不可達開始,到執行它的finalizer方法,時間可能任意長)

f.註冊FinalizerReference的類名(class類裏面定義finalize方法,就會創建)

     B)FINALIZER_WATCHDOG_DAEMON:

a.註冊FinalizerWatchdogDaemon線程名稱(回收的線程)

     C)MAIN

a.註冊主線程名稱

     D)LEAK_CANARY_THREAD

a.註冊LeakCanary的工作線程名稱

     E)EVENT_RECEIVER__MMESSAGE_QUEUE

a.註冊android.view.Choreographer$FrameDisplayEventReceiver以及mMessageQueue;應該是需要繼承修改;後續在看

     F)剩下的其他和機型;Api相關。是針對某一個Api遇到內存泄露作出的指定捕獲,或者刨除;這邊也是後續抽一個例子細看

這個類使用的是 EnumSet.allOf 該方法接受一個元素類型的參數elementType,並引用其元素將存儲到集合中的類對象;

   5)buildAndInstall()方法

     調用這個方法的時候;判斷是否是初始化默認的DISABLED;這個位子後續說;這邊不是;所以走下面的代碼;

 public RefWatcher buildAndInstall() {
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      LeakCanary.enableDisplayLeakActivity(context);
      ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
    }
    return refWatcher;
  }

走到enableDisplayLeakActivity方法;

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

裏面開啓類一個單核心線程

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

來處理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);
  }

其中packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);來控制啓用 禁用 四大組件

由上面代碼裏面 對DisplayLeakActivity進行設置成----可用狀態,並且不殺死APP(明顯是在處理Activity的

下面在走 installOnIcsPlus 方法;

其中SDK低於14的不進行初始化觀察;

  public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
    if (SDK_INT < ICE_CREAM_SANDWICH) {
      // If you need to support Android < ICS, override onDestroy() in your base activity.
      return;
    }
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
    activityRefWatcher.watchActivities();
  }

其中使用Application進行註冊Activity生命週期,所以Fragment的泄露希望是自己去調用

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

在裏面執行了onActivityDestroyed方法;

     剛剛在上面沒有提到buildAndInstall方法裏面的build;現在我們回過來講一下;因爲我們現在已經知道了,這個LeakCanary觸發的時機;

 public final RefWatcher build() {
    if (isDisabled()) {
      return RefWatcher.DISABLED;
    }
    //1
    ExcludedRefs excludedRefs = this.excludedRefs;
    if (excludedRefs == null) {
      excludedRefs = defaultExcludedRefs();
    }
    //2
    HeapDump.Listener heapDumpListener = this.heapDumpListener;
    if (heapDumpListener == null) {
      heapDumpListener = defaultHeapDumpListener();
    }
    //3
    DebuggerControl debuggerControl = this.debuggerControl;
    if (debuggerControl == null) {
      debuggerControl = defaultDebuggerControl();
    }
    //4
    HeapDumper heapDumper = this.heapDumper;
    if (heapDumper == null) {
      heapDumper = defaultHeapDumper();
    }
    // 5
    WatchExecutor watchExecutor = this.watchExecutor;
    if (watchExecutor == null) {
      watchExecutor = defaultWatchExecutor();
    }
    // 6
    GcTrigger gcTrigger = this.gcTrigger;
    if (gcTrigger == null) {
      gcTrigger = defaultGcTrigger();
    }

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

   我們先看到 :

   1 是ExcludedRefs對象的賦值;從uml圖裏面可以看到子類AndroidRefWatcherBuilder和父類RefWatcherBuilder;在初始化的時候調用的就是對父類this.excludedRefs賦值;

   2是AndroidRefWatcherBuilder子類裏面調用父類的heapDumpListener方法賦值this.heapDumpListener

   3是AndroidRefWatcherBuilder類裏面重寫子類的defaultDebuggerControl方法,裏面獲取是否是被調試狀態:Debug.isDebuggerConnected()

  4 是AndroidRefWatcherBuilder類裏面重寫子類的defaultHeapDumper方法 (下面細講)

  5 是AndroidRefWatcherBuilder類裏面重寫子類的defaultWatchExecutor方法,初始化AndroidWatchExecutor延遲5000毫秒

(專門開了一個工作線程處理)

  6 是AndroidRefWatcherBuilder類裏面 使用默認的GcTrigger(做GC操作的)

然後組裝到RefWatcher裏面去;下面我們開始講解一下整個工作的流程;以及重點的實現部分

現在開走;實現的內存泄露檢測流程

當Activity涉及到destory時

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

調用watch方法

  public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    checkNotNull(watchedReference, "watchedReference");//校驗不爲null
    checkNotNull(referenceName, "referenceName");//校驗不爲null
    final long watchStartNanoTime = System.nanoTime();//納秒
    String key = UUID.randomUUID().toString();//唯一標識碼
    retainedKeys.add(key);//緩存起來
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);//生成weak;配置一個key和name(name看起來是空值),加到隊列裏面去,繼承WeakReference

    ensureGoneAsync(watchStartNanoTime, reference);//實現runable的方法;
  }

  private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {//watchExecutor上面說的設置一個工作線程;
      //判斷是否是主線程; 如果是的話,直接addIdleHandler(CPU空閒的時候會調用)然後執行run();(有重試機制)都是延時操作
      // 如果不是主線程,先post回主線程,addIdleHandler(CPU空閒的時候會調用)然後執行run();(有重試機制)都是延時操作

      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);//RETRY會進行重試
      }
    });
  }

  Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();//獲取納秒
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);//計算出來耗時多久

    removeWeaklyReachableReferences();//移除隊列裏面的值,以及對應的retainedKeys的name 重要1

    if (debuggerControl.isDebuggerAttached()) {//判斷是否在調試,如果是調試的返回重試
      // The debugger can create false leaks.
      return RETRY;
    }
    if (gone(reference)) {//如果retainedKeys沒有緩存的reference的話,不做操作
      return DONE;
    }
    gcTrigger.runGc();//表示有reference,進行GC操作  重要2
    //Runtime.getRuntime().gc(); 發起GC操作
    //Thread.sleep(100);//等待 100毫秒
    //System.runFinalization();//運行處於掛起終止狀態的所有對象的終止方法。
    //調用該方法說明 Java 虛擬機做了一些努力運行已被丟棄對象的 finalize 方法,但是這些對象的 finalize 方法至今尚未運行。當控制權從方法調用中返回時,Java 虛擬機已經盡最大努力去完成所有未執行的終止方法

    removeWeaklyReachableReferences();//移除隊列裏面的值,以及對應的retainedKeys的name
    if (!gone(reference)) {//如果這個時候,隊列裏面的reference被移除了,但是retainedKeys還是包含reference,進行下一步內存泄露分析,如果沒有表示被回收了;重要 3
      long startDumpHeap = System.nanoTime();//再來一個納秒
      long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);//計算耗時

      File heapDumpFile = heapDumper.dumpHeap();//生成heap dump文件,並且保存起來;看 AndroidHeapDumper類 重要4
      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));//開始分析,看ServiceHeapDumpListener 類 ->HeapAnalyzerService類 重要5
    }
    return DONE;
  }

重點說明:(這個操作是線程空餘狀態進行)

重點1:

清空不存在的誤差

重點2:

進行GC的操作,並且還是延遲了

重點3:

是利用緩存,以及隊列的刪除進行判斷是否存在內存泄漏

重點4:

AndroidHeapDumper類:

 @Override public File dumpHeap() {
    File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();//生成路徑
    // 看下面的DefaultLeakDirectoryProvider類newHeapDumpFile();生成路徑不多說明

    if (heapDumpFile == RETRY_LATER) {//如果稍後在進行重試;會返回一個標識
      return RETRY_LATER;
    }

    FutureResult<Toast> waitingForToast = new FutureResult<>();
    showToast(waitingForToast);//先進行彈框出現內存泄露標誌,這個也是cpu空閒時,緩存該toast

    if (!waitingForToast.wait(5, SECONDS)) {//併發
      CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
      return RETRY_LATER;
    }

    Toast toast = waitingForToast.get();//獲取toat
    try {
      Debug.dumpHprofData(heapDumpFile.getAbsolutePath());//Debug.dumpHprofData 生成heap dump文件,後面是路徑;//目標找到了,這個是生成文件
      cancelToast(toast);//取消彈窗
      return heapDumpFile;//返回寫入的路徑
    } catch (Exception e) {
      CanaryLog.d(e, "Could not dump heap");
      // Abort heap dump
      return RETRY_LATER;//失敗了重試
    }
  }

裏面生成了真正的dump的對象,以及解析的類

重點5:

ServiceHeapDumpListener 類 ->HeapAnalyzerService類

  @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);//TODO 1 檢測內存泄漏,以及生成result
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
    //TODO 2 DisplayLeakService 也是一樣開啓一個IntentService處理 onHeapAnalyzed方法裏面
  }

這個類操作之後,到DisplayLeakService 這個進行最後的數據小處理,以及彈通知操作

下面細講一下TODO:

TODO 1:

HeapAnalyzer類:這個是我們非常關鍵的類。因爲會涉及到我們判斷哪一些不需要寫入;不需要生成泄漏文件;所以上面的配置類很重要

  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);//MemoryMappedFileBuffer 將heapDumpFile傳入,生成HprofBuffer,這個到haha的那個庫,後續分析
      HprofParser parser = new HprofParser(buffer);//轉碼,也是haha
      Snapshot snapshot = parser.parse();//這幾個轉化後續在看,上面就是將文件轉化成可以識別的對象
      deduplicateGcRoots(snapshot);//生成GC root快照

      Instance leakingRef = findLeakingReference(referenceKey, snapshot);//這邊開始查找泄漏Instance

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

  /**
   * Pruning duplicates reduces memory pressure from hprof bloat added in Marshmallow.
   */
  void deduplicateGcRoots(Snapshot snapshot) {
    // THashMap has a smaller memory footprint than HashMap.
    final THashMap<String, RootObj> uniqueRootMap = new THashMap<>();

    final List<RootObj> gcRoots = (ArrayList) snapshot.getGCRoots();//獲取根的gc root(可達性分析法)
    for (RootObj root : gcRoots) {//開始遍歷
      String key = generateRootKey(root);//轉化成字符串
      if (!uniqueRootMap.containsKey(key)) {//排重操作,如果沒有的話,就加進去
        uniqueRootMap.put(key, root);
      }
    }

    // Repopulate snapshot with unique GC roots.
    gcRoots.clear();//清空
    uniqueRootMap.forEach(new TObjectProcedure<String>() {//haha裏面的,這邊應該是轉化成字符串之類的,方便後續識別
      @Override public boolean execute(String key) {
        return gcRoots.add(uniqueRootMap.get(key));
      }
    });
  }

  private String generateRootKey(RootObj root) {
    return String.format("%s@0x%08x", root.getRootType().getName(), root.getId());
  }

  private Instance findLeakingReference(String key, Snapshot snapshot) {//返回找到的referenceKey的KeyedWeakReference泄漏
    ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());//看是否包含 KeyedWeakReference這個對象名字(弱引用);對象無法被銷燬導致的
    List<String> keysFound = new ArrayList<>();
    for (Instance instance : refClass.getInstancesList()) {//haha裏面,進行遍歷
      List<ClassInstance.FieldValue> values = classInstanceValues(instance);//獲取 List<FieldValue>
      String keyCandidate = asString(fieldValue(values, "key"));
      if (keyCandidate.equals(key)) {//看一下是否包含該key名稱的,即referenceKey,有的話就是找到了該泄漏的
        return fieldValue(values, "referent");
      }
      keysFound.add(keyCandidate);
    }
    throw new IllegalStateException(
        "Could not find weak reference with key " + key + " in " + keysFound);
  }

  private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
      Instance leakingRef) {

    ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);//之前的配置類;
    ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);//ShortestPathFinder類

    // False alarm, no strong reference path to GC Roots.
    if (result.leakingNode == null) {
      return noLeak(since(analysisStartNanoTime));
    }

    LeakTrace leakTrace = buildLeakTrace(result.leakingNode);

    String className = leakingRef.getClassObj().getClassName();

    // Side effect: computes retained size.
    snapshot.computeDominators();

    Instance leakingInstance = result.leakingNode.instance;

    long retainedSize = leakingInstance.getTotalRetainedSize();

    retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);

    return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
        since(analysisStartNanoTime));
  }

TODO 2:

DisplayLeakService類,進行發送通知

  @Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
    String leakInfo = leakInfo(this, heapDump, result, true);
    CanaryLog.d(leakInfo);

    boolean resultSaved = false;
    boolean shouldSaveResult = result.leakFound || result.failure != null;
    if (shouldSaveResult) {
      heapDump = renameHeapdump(heapDump);//生成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);//發送Notification通知
    afterDefaultHandling(heapDump, result, leakInfo);
  }

以上是我們使用LeakCanary的基本操作流程;所以這些是我需要了解;後續我們再去分析haha這個庫

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章