Android源碼系列-解密LeakCanary

leakcanary是什麼?

leakcanary是Square開源的一個內存泄露自動探測神器,它是一個Android和Java的內存泄露檢測庫,能夠大幅度減小了開發中遇到的OOM問題。java

下圖爲官方應用圖標:android

image

簡介

Github地址:leakcanarygit

特色

  • 使用簡單
  • 實時檢測
  • 及時的消息提醒及log日誌

內存泄漏

本文主要介紹leakcanary的實現及原理。關於內存泄漏的定義及Android常見的內存泄漏及解決方法,推薦參考以下文章:github

Android性能優化-內存泄漏(上)api

Android性能優化-內存泄漏(下)性能優化

leakcanary怎麼用?

一、gradle引入庫bash

debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
 releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
 // Optional, if you use support library fragments:
 debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.1'
複製代碼

二、自定義Application而且在onCreate中進行初始化微信

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核心執行流程是怎樣?

leakcanary主要是經過弱引用持有監聽的實例對象,每一個弱引用生成惟一的id,內部維護一個retainedKeys。經過觸發gc,循環獲取ReferenceQueue中的對象,若已被gc回收,則把id從keys中移除,最後keys維護了一個內存泄漏的實例集合。接着經過dump堆內存信息,用Square的haha庫對hprof文件進行解析,最終獲取內存泄漏實例的引用鏈,推送到前臺通知。app

關鍵類功能說明

說明
LeakCanary 外觀模式,統一的註冊類,install會返回RefWatcher對象,用於監聽內存泄漏
RefWatcher 用於總體調度監聽對象的內存泄漏,是核心的類。內部維護了watchExecutor(異步分析線程池)、debuggerControl(用於判斷是否debug模式)、gcTrigger(觸發gc)、heapDumper(實現爲AndroidHeapDumper,用於dump內存堆快照信息)、retainedKeys(用於記錄內存泄漏的對象)、queue(弱引用被gc回收會加入到該隊列);提供watch()方法對對象進行內存泄漏的判斷分析。
ActivityRefWatcher 用於監聽Activity的生命週期、執行watch()
FragmentRefWatcher 用於監聽Fragment的生命週期、執行watch()
KeyedWeakReference 將檢測的對象轉換爲虛引用持有,便於監聽GC的回收,內部維護了一個key屬性,用做惟一的標識(UUID.randomUUID()生成)
DebuggerControl 實現爲AndroidDebuggerControl,用於判斷是否屬於調試模式(Debug.isDebuggerConnected())
GcTrigger 用於觸發GC操做,(Runtime.getRuntime().gc())
HeapDump 堆內存信息的包裝類,包含heapDumpFile、excludedRefs、referenceKey等對象
ExcludedRefs 用於排除一些已知的系統內存泄漏問題,避免再次提醒
HeapAnalyzerService IntentService,用於開始執行內存泄漏的堆信息分析
HeapAnalyzer 堆內存分析器,用於堆信息分析的總體調度
HprofParser haha庫中用於解析hprof文件
Snapshot haha庫中,堆信息內存快照
AnalysisResult 分析結果包裝類,包含className、leakTrace(泄漏的引用鏈)、retainedHeapSize(內存泄漏的大小)等

代碼執行流程

leakcanary的核心流程主要包含5個步驟。框架

一、install-註冊

二、bind-設置監聽

三、watch-觸發監聽,分析泄漏

四、dump-堆內存信息

五、analyze-分析,獲取引用鏈

這裏先上一下總體的流程圖,建議結合源碼進行查看。

image

下面咱們經過上訴5個步驟相關的源碼來進行分析。

install

咱們知道,使用leakcanary主要在application中進行註冊,調用LeakCanary.install(this)。相關的實現代碼以下:

public static RefWatcher install(Application application) {
    //建立了RefWatcher(也就是檢測內存泄漏的類)
    return refWatcher(application)
        //設置了內存泄漏的通知Service(通知)
        .listenerServiceClass(DisplayLeakService.class)
        //設置了須要忽略的已知的系統級別的內存泄漏(能夠自定義)
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        //開始監聽
        .buildAndInstall();
  }
複製代碼

bind

leakcanary主要是監聽應用的Activity、Fragment的內存泄漏。那麼bind的這個環節,主要是經過監聽Activity、Fragment相關的生命週期,當對象onDestry的時候,開始執行watch操做。 首先咱們先看buildAndInstall()方法,實現以下:

一、buildAndInstall

public RefWatcher buildAndInstall() {
    if (LeakCanaryInternals.installedRefWatcher != null) {
      throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
    }
    //初始化RefWatcher對象
    RefWatcher refWatcher = build();
    if (refWatcher != DISABLED) {
      if (watchActivities) {//默認true
        //開始綁定Activity的監聽
        ActivityRefWatcher.install(context, refWatcher);
      }
      if (watchFragments) {//默認true
         //開始綁定Fragment的監聽
        FragmentRefWatcher.Helper.install(context, refWatcher);
      }
    }
    LeakCanaryInternals.installedRefWatcher = refWatcher;
    return refWatcher;
  }
複製代碼

二、ActivityRefWatcher.install(context, refWatcher)

public static void install(Context context, RefWatcher refWatcher) {
    Application application = (Application) context.getApplicationContext();
    ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
    //經過Application的ActivityLifecycleCallbacks監聽Activity的生命週期
    application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
  }

  private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
      new ActivityLifecycleCallbacksAdapter() {
        @Override public void onActivityDestroyed(Activity activity) {
          //當Activity銷燬的時候,開始觸發watch進行分析
          refWatcher.watch(activity);
        }
      };

複製代碼

三、 FragmentRefWatcher.Helper.install(context, refWatcher)

public static void install(Context context, RefWatcher refWatcher) {
        //初始化FragmentRefWatcher對象及集合
      List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
        
      if (SDK_INT >= O) {
        fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
      }

      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);
     //首先開始監聽Activity的生命週期
      Application application = (Application) context.getApplicationContext();
      application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
    }

    private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
        new ActivityLifecycleCallbacksAdapter() {
          @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
           //當Activity建立的時候,進行Fragment的綁定監聽
            for (FragmentRefWatcher watcher : fragmentRefWatchers) {
              watcher.watchFragments(activity);
            }
          }
        };
        
  @Override public void watchFragments(Activity activity) {
    if (activity instanceof FragmentActivity) {
      FragmentManager supportFragmentManager =
          ((FragmentActivity) activity).getSupportFragmentManager();
     //經過FragmentManager註冊FragmentLifecycleCallbacks,監聽Fragment的生命週期
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
    }
  } 
  
 private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
      new FragmentManager.FragmentLifecycleCallbacks() {
        @Override public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
        //當Fragment銷燬的時候,開始觸發watch進行分析
          refWatcher.watch(fragment);
        }
      };

 
複製代碼

watch

watch主要是經過弱引用持有監聽的實例對象,每一個弱引用生成惟一的id,內部維護一個retainedKeys。經過觸發gc,循環獲取ReferenceQueue中的對象,若已被gc回收,則把id從keys中移除,最後keys維護了一個內存泄漏的實例集合。觸發下一步dump操做。關鍵的實現代碼以下:

一、watch

public void watch(Object watchedReference, String referenceName) {
    if (this == DISABLED) {
      return;
    }
    checkNotNull(watchedReference, "watchedReference");
    checkNotNull(referenceName, "referenceName");
    final long watchStartNanoTime = System.nanoTime();
    //生成惟一的key
    String key = UUID.randomUUID().toString();
    retainedKeys.add(key);
    //生成虛引用
    final KeyedWeakReference reference =
        new KeyedWeakReference(watchedReference, key, referenceName, queue);
    //開始觸發線程進行分析
    ensureGoneAsync(watchStartNanoTime, reference);
  }
複製代碼

二、ensureGone

private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
    watchExecutor.execute(new Retryable() {
      @Override public Retryable.Result run() {
        return ensureGone(reference, watchStartNanoTime);
      }
    });
  }
  
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
    long gcStartNanoTime = System.nanoTime();
    long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
    //刷新keys
    removeWeaklyReachableReferences();
    //判斷是否在debug模式
    if (debuggerControl.isDebuggerAttached()) {
      // The debugger can create false leaks.
      return RETRY;
    }
    //若是已回收不用處理
    if (gone(reference)) {
      return DONE;
    }
    //沒回收,觸發gc,提升準確性
    gcTrigger.runGc();
    //再次刷新keys
    removeWeaklyReachableReferences();
    //若是keys中仍是存在該虛引用,則斷定爲內存泄漏
    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;
  }

 //判斷對象是否已被gc回收
 private boolean gone(KeyedWeakReference reference) {
    return !retainedKeys.contains(reference.key);
  }
 //循環獲取ReferenceQueue中的對象,若被gc回收,從keys中移除
 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);
    }
  }
複製代碼

dump

在watch中的ensureGone方法中,當監聽到內存泄漏的方法時,會開始執行dump,下載堆內存快照文件。具體的實現以下:

//若是keys中仍是存在該虛引用,則斷定爲內存泄漏
    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);
    }
複製代碼

關鍵的heapDumper.dumpHeap()實現以下:

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

    Notification.Builder builder = new Notification.Builder(context)
        .setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
    Notification notification = LeakCanaryInternals.buildNotification(context, builder);
    NotificationManager notificationManager =
        (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    int notificationId = (int) SystemClock.uptimeMillis();
    notificationManager.notify(notificationId, notification);

    Toast toast = waitingForToast.get();
    try {
        //關鍵的實現,dump堆內存信息
      Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
      //隱藏凍結彈層
      cancelToast(toast);
      notificationManager.cancel(notificationId);
      return heapDumpFile;
    } catch (Exception e) {
      CanaryLog.d(e, "Could not dump heap");
      // Abort heap dump
      return RETRY_LATER;
    }
  }
複製代碼

analyze

當dump堆內存信息成功後,會執行以下方法heapdumpListener.analyze(heapDump),開始執行分析,具體的實現以下:

@Override public void analyze(HeapDump heapDump) {
    checkNotNull(heapDump, "heapDump");
    HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
  }
  
    public static void runAnalysis(Context context, HeapDump heapDump,
      Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
    setEnabledBlocking(context, HeapAnalyzerService.class, true);
    setEnabledBlocking(context, listenerServiceClass, true);
    Intent intent = new Intent(context, HeapAnalyzerService.class);
    intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
    intent.putExtra(HEAPDUMP_EXTRA, heapDump);
    ContextCompat.startForegroundService(context, intent);
  }
複製代碼

經過源碼可知,會開啓一個IntentService進行分析的操做。接下來看,HeapAnalyzerService的onHandleIntentInForeground實現,以下:

@Override protected void onHandleIntentInForeground(@Nullable 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, this, heapDump.reachabilityInspectorClasses);
   //開始執行分析
    AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
        heapDump.computeRetainedHeapSize);
    AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
  }
複製代碼

接下來是使用了Square的開源庫haha去分析hprof文件,獲取泄漏對象的引用鏈,相關的實現以下:

public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey,
      boolean computeRetainedSize) {
    long analysisStartNanoTime = System.nanoTime();

    if (!heapDumpFile.exists()) {
      Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
      return failure(exception, since(analysisStartNanoTime));
    }

    try {
      listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
      HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
      HprofParser parser = new HprofParser(buffer);
      listener.onProgressUpdate(PARSING_HEAP_DUMP);
      Snapshot snapshot = parser.parse();
      listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
      deduplicateGcRoots(snapshot);
      listener.onProgressUpdate(FINDING_LEAKING_REF);
      Instance leakingRef = findLeakingReference(referenceKey, snapshot);

      // 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, computeRetainedSize);
    } catch (Throwable e) {
      return failure(e, since(analysisStartNanoTime));
    }
  }
 private AnalysisResult findLeakTrace(long analysisStartNanoTime, Snapshot snapshot,
      Instance leakingRef, boolean computeRetainedSize) {

    listener.onProgressUpdate(FINDING_SHORTEST_PATH);
    ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
    ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);

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

    listener.onProgressUpdate(BUILDING_LEAK_TRACE);
    LeakTrace leakTrace = buildLeakTrace(result.leakingNode);

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

    long retainedSize;
    if (computeRetainedSize) {

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

      Instance leakingInstance = result.leakingNode.instance;

      retainedSize = leakingInstance.getTotalRetainedSize();

      // TODO: check O sources and see what happened to android.graphics.Bitmap.mBuffer
      if (SDK_INT <= N_MR1) {
        listener.onProgressUpdate(COMPUTING_BITMAP_SIZE);
        retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
      }
    } else {
      retainedSize = AnalysisResult.RETAINED_HEAP_SKIPPED;
    }

    return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
        since(analysisStartNanoTime));
  }
複製代碼

關於haha庫的具體實現,可參考以下連接。

haha github

最後在onHandleIntentInForeground的方法最後,會執行AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);將最終分析的結果推送到前臺的通知。這就完成了leakcanary對於內存泄漏的關鍵步驟分析。

leakcanary是如何進行內存泄漏的監聽?

leakcanary的內存泄漏監聽包含2個方面,一個是對Activity的監聽,一個是對Fragment的監聽。

一、 Activity內存泄漏監聽

經過上述的流程分析,咱們可知。leakcanary經過對 application註冊ActivityLifecycleCallbacks的監聽,當回調onDestroy的時候,開始對Activity進行watch操做

二、Fragment內存泄漏監聽

經過上述的流程分析,咱們可知。leakcanary經過對 application註冊ActivityLifecycleCallbacks的監聽,當回調onActivityCreate的時候,經過FragmentManager註冊FragmentLifecycleCallbacks的監聽,當回調onDestroy的時候,開始對Fragment進行watch操做

leakcanary是如何進行內存泄漏的分析?

leakcanary主要是經過監聽Activity及Fragment的生命週期,當對象onDestroy的時候,使用弱引用持有該實例對象,每一個弱引用生成惟一的id,內部維護一個retainedKeys。經過觸發gc,循環獲取ReferenceQueue中的對象,若已被gc回收,則把id從keys中移除,表示已正常被gc回收,最後keys維護了一個內存泄漏的集合。主要是經過弱引用和ReferenceQueue機制來進行內存泄漏的分析。

leakcanary是如何dump內存信息?

經過上述的dump流程,leakcanary主要是經過Android系統提供的api進行堆內存信息的保存。具體的api以下:

Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
  
複製代碼

leakcanary是如何進行堆信息的分析?

leakcanary主要是使用haha庫對hprof的文件進行解析,最後分析出內存泄漏對象的引用鏈,及泄漏的內存大小。關於haha庫的詳細使用,可參考:

haha github

總結

思考

Square真是一家牛逼的公司,並且熱衷於對開源社區的貢獻。在Android的生態中,貢獻了諸如okhttp、picasso、leakcanary、javapoet、retrofit等主流的框架。

Squaregithub

參考資料

Android性能優化-內存泄漏(上)

Android性能優化-內存泄漏(下)

LeakCanary源碼解析-很值得咱們學習的一款框架

推薦

Android源碼系列-解密OkHttp

Android源碼系列-解密Retrofit

Android源碼系列-解密Glide

Android源碼系列-解密EventBus

Android源碼系列-解密RxJava

Android源碼系列-解密LeakCanary

Android源碼系列-解密BlockCanary

關於

歡迎關注個人我的公衆號

微信搜索:一碼一浮生,或者搜索公衆號ID:life2code

image

相關文章
相關標籤/搜索