Android進階7:內存優化——LeakCanary原理分析

好的項目離不開健壯的代碼,對於想要寫出健壯的代碼,解決內存泄漏是必須的。app

對於LeakCanary,對於大多人是不陌生的,也就是檢測內存泄漏的工具。可能在代碼中咱們會這樣引入LeakCanary:dom

        //檢查leakCanary和APP是否在同一個進程,若是是同一個進程就返回,不在同一個進程,就註冊。
        //由於再也不同一個進程,不會對APP進程形成消極影響(如:APP進程變慢或者out of memory)
        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);ide


簡簡單單的兩句話,背後的原理倒是一大堆。
在開始源碼以前,先說幾個知識點:工具

    強引用,軟引用,弱引用,GC線程掃描它所管轄的內存區域時,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。
    垃圾回收器是一個優先級很低的線程,即便有弱引用的存在,也不必定會執行。
    咱們手動調用GC,不必定成功調用垃圾回收器,由於咱們僅僅是建議JVM執行GC,最終執不執行,仍是得看JVM的最終決策。
    LeakCanary是在主進程進行內存檢測,而且是在主進程空閒的時候進行的。 這就是爲何會出現咱們關閉Activity了,過了一段時間,才顯示內存泄漏了。
    Android4.0以後,新加了ActivityLifecycleCallbacks,做用就是,同一進程下,每一個Activity的的生命週期都會進入到ActivityLifecycleCallbacks回調的相應方法中。ui

以上知識點是咱們須要知道的。接下來看源碼:this

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


方法返回的是RefWatcher ,RefWatcher 是分析內存泄露的核心類,裏面提供了相應的方法用戶分析內存泄漏。其中refWatcher(application)返回的是AndroidRefWatcherBuilder對象,listenerServiceClass方法裏面的DisplayLeakService參數是:通知給開發者顯示內存泄漏的詳細信息,其繼承自IntentService, 不理解的能夠看這個:
Android進階2:線程和線程池(3)—— IntentService原理解析
而後就是鏈式調用了excludedRefs方法,做用是忽略系統底層的內存泄漏,也就是說不是咱們開發者形成的內存泄漏。最後調用了buildAndInstall方法,這個方法最終返回的是RefWatcher對象:線程

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


執行ActivityRefWatcher.install,看下:對象

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


調用了watchActivities(), 繼續深究:

  public void watchActivities() {
    // Make sure you don't get installed twice.
    stopWatchingActivities();
    //給Application註冊監聽事件;
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
  }

  public void stopWatchingActivities() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks);
  }


到這裏咱們就明白了原來就是LeakCanary內部實際上用的也就是ActivityLifecycleCallbacks,那麼,想一下lifecycleCallbacks對象的實現,既然咱們須要檢測內存泄漏,那麼確定是在Activity ‘銷燬’ 以後,纔可以檢測的,那麼名義上的銷燬,調用的方法也就是onActivityDestroyed,因此咱們猜測LeakCanary的檢測內存泄漏的邏輯是寫在onActivityDestroyed中的。
接下來看下源碼,驗證一下:

  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方法中的。
繼續,看下onActivityDestroyed方法,注意傳遞的參數是當前銷燬的Activity對象。其最終調用的仍是RefWatcher的watch方法,

   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中
    retainedKeys.add(key);
    //將Activity和惟一標識以及ReferenceQueue做爲參數,生成弱引用KeyedWeakReference
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    //開始內存泄漏檢測
    ensureGoneAsync(watchStartNanoTime, reference);
  }


watchedReference 也就是咱們傳入的Activity對象,上面源碼主要是先生成了惟一標識key,而後將Activity和惟一標識以及ReferenceQueue做爲參數生成弱引用對象reference 。ReferenceQueue的做用是:在適當的時候檢測到對象的可達性發生改變後,垃圾回收器就將已註冊的引用對象添加到此隊列中。後面代碼裏使用一個弱引用鏈接到你須要檢測的對象,而後使用ReferenceQueue來監測這個弱引用可達性的改變;說白了ReferenceQueue就是一個弱引用隊列。而後垃圾回收器會自動的回收此隊列中的對象。KeyedWeakReference 本質也就是將咱們的Activity封裝了一層,變成了惟一的一個弱引用對象。

  private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    //開啓子線程進行內存分析
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }


能夠看出,實際上LeakCanary分析內存是在子線程中的。


  @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();
    //是否處於debugger狀態,若是是就返回
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    // 看是否此弱引用對象還存在
    if (gone(reference)) {
        //若是不存在,就返回,代表沒有內存泄漏
      return DONE;
    }
    //若是還存在,就手動調用GC,
    gcTrigger.runGc();
    //再次刪除弱引用
    removeWeaklyReachableReferences();
    //再次判斷此弱引用對象是否存在
    if (!gone(reference)) {
        //若是還存在,就分析Dump內存快照。
      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);
      //分析analyze方法;
      heapdumpListener.analyze(
          new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
              gcDurationMs, heapDumpDurationMs));
    }
    return DONE;
  }


從上述代碼能夠看出,LeakCanary檢測是否內存泄漏的步驟是:

    先刪除弱引用
    判斷弱引用是否還存在,若是不存在,說明已經沒有內存泄漏;若是存在,就執行3步驟。
    手動調用GC ,而後再次刪除弱引用
    再次判斷弱引用是否還存在,若是不存在,就代表沒有內存泄漏;若是存在,就開始深層次的分析,這裏仍是疑似內存泄漏,由於手動觸發GC,僅僅是建議JVM觸發垃圾回收,真正執不執行仍是得看JVM。

注意這裏判斷弱引用是否存在的標識就是:上述源碼生成的惟一標識key。

最後若是進行到第四步驟,也就是深度分析,此時升讀分析的也就是Hprof文件。 這裏的heapdumpListener也就是ServiceHeapDumpListener對象。關於爲何是ServiceHeapDumpListener對象,能夠看下上面源碼的install方法內的listenerServiceClass方法:

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


繼續深刻:

  @Override public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    //單獨開一個進程,開始深度分析
    //HeapAnalyzerService類的註釋:此服務在單獨的進程中運行,以免放慢應用程序進程或使其運行
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }

也就是說heapdumpListener.analyze最終調用的也就是ServiceHeapDumpListener的analyze方法。HeapAnalyzerService繼承自IntentService, runAnalysis方法的內部作的也就是開啓Service(也就是它本身),那麼咱們知道對於IntentService,暴露了onHandleIntent抽象方法,共開發者處理相關的業務邏輯,因此看下onHandleIntent內部的代碼:

 @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);
   //而後調用checkForLeak方法分析,並返回分析結果
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey);
    //
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }


上述代碼流程是先建立了一個堆分析對象,而後開始分析,最後返回分析結果,並調用AbstractAnalysisResultService.sendResultToListener發送結果,展現給開發者。值得注意的是listenerClassName參數也就是上面源碼的DisplayLeakService對象,而後在AbstractAnalysisResultService的sendResultToListener方法內,經過反射獲取DisplayLeakService對象:

  public static void sendResultToListener(Context context, String listenerServiceClassName,
      HeapDump heapDump, AnalysisResult result) {
    Class<?> listenerServiceClass;
    try {
    //經過反射獲取ServiceHeapDumpListener對象
      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);
  }


經過反射獲取到DisplayLeakService對象,而後開啓本身,

 @Override protected final void onHandleIntent(Intent intent) {
    HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
    AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
    try {
    //最終調用的仍是DisplayLeakService的方法。
      onHeapAnalyzed(heapDump, result);
    } finally {
      //noinspection ResultOfMethodCallIgnored
      heapDump.heapDumpFile.delete();
    }
  }

   

看下DisplayLeakService的onHeapAnalyzed方法:

 @Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
    .......
    //展現通知
    showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
    afterDefaultHandling(heapDump, result, leakInfo);
  }

 

至此LeakCanary流程分析大體完成了。

接下來講下開發中對於ActivityLifecycleCallbacks的使用,在實際開發中,經常會須要埋點,咱們項目中用的是友盟,後來公司運營須要一套本身的經營分析系統,也就是說友盟一套埋點數據,咱們本身一套埋點數據,埋點需求:點擊,頁面停留時長等,這時候使用ActivityLifecycleCallbacks監聽Activity的狀態,就很是方便了。

相關文章
相關標籤/搜索