日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

LeakCanary 源码分析

發布時間:2024/1/17 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 LeakCanary 源码分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. 前言

LeakCanary 是由 Square 開發的一款內存泄露檢測工具。相比與用 IDE dump memory 的繁瑣,它以輕便的日志被廣大開發者所喜愛。讓我們看看它是如何實現的吧。

ps: Square 以著名框架 Okhttp 被廣大開發者所熟知。

2. 源碼分析

2.1 設計架構

分析一個框架,我們可以嘗試先分層。好的框架層次清晰,像TCP/IP那樣,一層一層的封裝起來。這里,我按照主流程大致分了一下。

一圖流,大家可以參考這個圖,來跟源碼。

2.2 業務層

按照教程,我們通常會有如下初始化代碼:

  • Applicaion 中:mRefWatcher = LeakCanary.install(this);
  • 基類 Activity/Fragment onDestory() 中: mRefWatcher.watch(this);
  • 雖然是用戶端的代碼,不過作為分析框架的入口,不妨稱為業務層。

    這一層我們考慮的是檢測我們的業務對象 Activity。當然你也可以用來檢測 Service。

    2.3 Api層

    從業務層切入,我們引出了兩個類LeakCanary、RefWatcher,組成了我們的 api 層。

    這一層我們要考慮如何對外提供接口,并隱藏內部實現。通常會使用 Builder、單例、適當的包私有權限。

    2.3.1 主線1 install()

    public final class LeakCanary {public static @NonNull RefWatcher install(@NonNull Application application) {return refWatcher(application).listenerServiceClass(DisplayLeakService.class).excludedRefs(AndroidExcludedRefs.createAppDefaults().build()).buildAndInstall();}public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {return new AndroidRefWatcherBuilder(context);} } 復制代碼

    我們先看install(),先拿到一個RefWatcherBuilder,轉而使用Builder模式構造一個RefWatcher作為返回值。 大概可以知道是框架的一些初始配置。忽略其他,直接看buildAndInstall()。

    public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {...private boolean watchActivities = true;private boolean watchFragments = true;public @NonNull RefWatcher buildAndInstall() {RefWatcher refWatcher = build();if (refWatcher != DISABLED) {...if (watchActivities) { // 1ActivityRefWatcher.install(context, refWatcher);}if (watchFragments) { // 2FragmentRefWatcher.Helper.install(context, refWatcher);}}return refWatcher;} } 復制代碼

    可以看到 1, 2 兩處,默認行為是,監控 Activity 和 Fragment。 以 Activity為例:

    public final class ActivityRefWatcher {public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {...application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);}private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =new ActivityLifecycleCallbacksAdapter() {@Override public void onActivityDestroyed(Activity activity) {refWatcher.watch(activity);}}; } 復制代碼

    使用了Application.ActivityLifecycleCallbacks,看來我們基類里的watch()是多余的。Fragment 也是類似的,就不分析了,使用了FragmentManager.FragmentLifecycleCallbacks。

    PS: 老版本默認只監控 Activity,watchFragments 這個字段是 2018/6 新增的。

    2.3.2 主線2 watch()

    之前的分析,引出了RefWatcher.watch(),它可以檢測任意對象是否正常銷毀,不單單是 Activity。我們來分析看看:

    public final class RefWatcher {private final WatchExecutor watchExecutor;public void watch(Object watchedReference, String referenceName) {...String key = UUID.randomUUID().toString();retainedKeys.add(key);final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);ensureGoneAsync(watchStartNanoTime, reference);}private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {watchExecutor.execute(new Retryable() {@Override public Retryable.Result run() {return ensureGone(reference, watchStartNanoTime);}});} } 復制代碼

    通過這個 watch(),我們可以注意到這幾點:

  • 為了不阻塞我們的onDestory(),特意設計成異步調用——WatchExecutor。
  • 有一個弱引用 KeyedWeakReference,干嘛用的呢?
  • 我們該怎么設計 WatchExecutor 呢?AsyncTask?線程池?我們接著往下看

    2.4 日志產生層

    現在我們來到了非常關鍵的一層,這一層主要是分析是否泄露,產物是.hprof文件。 我們平常用 IDE dump memory 的時候,生成的也是這種格式的文件。

    2.4.1 WatchExecutor 異步任務

    接之前的分析,WatchExecutor主要是用于異步任務,同時提供了失敗重試的機制。

    public final class AndroidWatchExecutor implements WatchExecutor {private final Handler mainHandler;private final Handler backgroundHandler;public AndroidWatchExecutor(long initialDelayMillis) {mainHandler = new Handler(Looper.getMainLooper());HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);handlerThread.start();backgroundHandler = new Handler(handlerThread.getLooper());...}@Override public void execute(@NonNull Retryable retryable) {if (Looper.getMainLooper().getThread() == Thread.currentThread()) {waitForIdle(retryable, 0);} else {postWaitForIdle(retryable, 0);}}... } 復制代碼

    看來是使用了HandlerThread。沒啥說的,要注意一下子線程Handler的使用方式。之后便會回調ensureGone(),注意此時執行環境已經切到子線程了。

    2.4.2 ReferenceQueue 檢測泄露

    分析下一步之前,我們先介紹一下 ReferenceQueue。

  • 引用隊列 ReferenceQueue 作為參數傳入 WeakReference.
  • WeakReference 中的 value 變得不可達,被 JVM 回收之前,WeakReference 會被加到該隊列中,等待回收。
  • 說白了,ReferenceQueue 提供了一種通知機制,以便在 GC 發生前,我們能做一些處理。

    詳見 Reference 、ReferenceQueue 詳解

    好了,讓我們回到 RefWatcher。

    final class KeyedWeakReference extends WeakReference<Object> {public final String key; // 由于真正的 value 正等待回收,我們追加一個 key 來識別目標。public final String name;KeyedWeakReference(Object referent, String key, String name,ReferenceQueue<Object> referenceQueue) {super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));this.key = checkNotNull(key, "key");this.name = checkNotNull(name, "name");} }public final class RefWatcher {private final Set<String> retainedKeys; // 保存未回收的引用的 key。 watch()時 add, 在 queue 中找到則 remove。private final ReferenceQueue<Object> queue; // 收集所有變得不可達的對象。public void watch(Object watchedReference, String referenceName) {...String key = UUID.randomUUID().toString();retainedKeys.add(key); // 1final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);ensureGoneAsync(watchStartNanoTime, reference);}Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {removeWeaklyReachableReferences(); // 2...if (gone(reference)) { // 3return DONE;}gcTrigger.runGc(); // 4removeWeaklyReachableReferences(); // 5if (!gone(reference)) { // 6// 發現泄漏...}return DONE;}private boolean gone(KeyedWeakReference reference) {return !retainedKeys.contains(reference.key);}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);}} } 復制代碼

    我們有這樣的策略:用retainedKeys保存未回收的引用的 key。

  • 主線程 onDestroy() -> watch() -> retainedKeys.add(ref.key)。WatchExecutor 啟動,主線程 Activity 銷毀。
  • WatchExecutor.execute() -> ensureGone() -> removeWeaklyReachableReferences() -> 遍歷 ReferenceQueue,從 retainedKeys.remove(ref.key)
  • 判斷 gone(ref), 如果 Activity 已經不可達,那么直接返回,否則可能有內存泄漏。
  • 4-6. 引用還在,然而這里沒有立即判定為泄漏,而是很謹慎的手動觸發 gc,再次校驗。

    2.4.3 GcTrigger 手動觸發 Gc

    這里注意一點 Android 下邊的 jdk 和 oracle 公司的 jdk 在一些方法的實現上有區別。比如這個 System.gc()就被改了,不再保證必定觸發 gc。作者使用Runtime.getRuntime().gc()作為代替。

    了解更多:System.gc() 源碼解讀

    2.4.4 HeapDumper 生成堆快照 .hprof

    public final class RefWatcher {private final HeapDumper heapDumper;private final HeapDump.Listener heapdumpListener;Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {...gcTrigger.runGc(); removeWeaklyReachableReferences(); if (!gone(reference)) { // 發現泄漏File heapDumpFile = heapDumper.dumpHeap();HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile)....build();heapdumpListener.analyze(heapDump);}return DONE;} } 復制代碼

    我們跟進 heapDumper.dumpHeap(),略去一些 UI 相關代碼:

    public final class AndroidHeapDumper implements HeapDumper {@Override @Nullable public File dumpHeap() {File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();...try {Debug.dumpHprofData(heapDumpFile.getAbsolutePath());...return heapDumpFile;} catch (Exception e) { ... }} } 復制代碼

    最后用了 Android 原生的 api —— Debug.dumpHprofData(),生成了堆快照。

    2.5 日志分析層 && 日志展示層

    生成 .hprof 之后,之后由 heapdumpListener.analyze(heapDump) 把數據轉到下一層。其實這兩層沒啥好分析的,.hprof 已經是標準的堆快照格式,平時用 AS 分析內存生成的也是這個格式。

    所以,LeakCanary 在這一層只是幫我們讀取了堆中的引用鏈。然后,日志展示層也沒啥說的,就一個 ListView。

    3. 總結

    最后,我們可以看到一個優秀的框架需要那些東西:

    分層

    • 分層的意義在于邏輯清晰,每一層的任務都很明確,盡量避免跨層的依賴,這符合單一職責的設計原則。
    • 對于使用者來說,只用關心api層有哪些接口以及業務層怎么使用;而對于維護者來說,很多時候只需要關心核心邏輯日志產生層,UI層不怎么改動丑一點也沒關系。方便使用也方便維護。

    ReferenceQueue 的使用

    • 學到了如何檢測內存回收情況,并且做一些處理。以前只會傻傻的new WeakReference()。

    手動觸發 gc

    • Runtime.getRuntime().gc() 是否能立即觸發 gc,這點感覺也比較含糊。這是一個 native 方法,依賴于 JVM 的實現,深究起來需要去看 Dalvik 的源碼,先打一個問號。
    • 框架中 gc 的這段代碼是從 AOSP 里拷貝出來的。。。所以說,多看源碼是個好習慣。

    leakcanary-no-op

    • release 版提供一個空實現,可以學習一下。

    總結

    以上是生活随笔為你收集整理的LeakCanary 源码分析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。