主流開源框架之LeakCanary深刻了解

主流開源框架源碼深刻了解第4篇——Leakcanary源碼分析。(源碼以1.6.1版爲準)html

簡單說兩句,又有兩個多月沒寫文章啦,這中間雖然沒有繼續看源碼,不過卻是學了一些性能優化的知識,因爲基本都是經過視頻、博客等學習,並且本身的筆記也都是學習過程當中跟隨視頻和博客記下的,所以就沒有寫成文章發佈出來。感興趣的小夥伴能夠看一看:github.com/Endless5F/J…java

LeakCanary的使用

private static void initLeakCanary(Application sApplication) {
        // LeakCanary 初始化(內存泄漏檢測)
        if (LeakCanary.isInAnalyzerProcess(sApplication)) {
            // This process is dedicated to LeakCanary for heap analysis.
            // You should not init your app in this process.
            return;
        }
        LeakCanary.install(sApplication);
    }
複製代碼

相關知識點以及關聯類簡析

  1. ReferenceQueue:在適當的時候檢測到對象的可達性發生改變後,垃圾回收器就將已註冊的引用對象添加到此隊列中。後面代碼裏使用一個弱引用鏈接到你須要檢測的對象,而後使用ReferenceQueue來監測這個弱引用可達性的改變android

  2. 四大引用類型:git

    • StrongReference強引用:強引用的對象絕對不會被gc回收
    • SoftReference軟引用:若是物理內存充足則不會被gc回收,若是物理內存不充足則會被gc回收。
    • WeakReference弱引用:一旦被gc掃描到則會被回收
    • PhantomReference虛引用:不會影響對象的生命週期,形同於無,任什麼時候候均可能被gc回收
    • FinalReference:用於收尾機制(finalization)

    GC線程掃描它所管轄的內存區域時,一旦發現了只具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。因爲垃圾回收器是一個優先級很低的線程,所以不必定會很快發現那些只具備弱引用的對象。github

    除了強引用,其他3種引用均繼承自Reference類。Reference其內部提供2個構造函數,一個帶queue,一個不帶queue。其中queue的意義在於,咱們能夠在外部對這個queue進行監控。即若是有對象即將被回收,那麼相應的reference對象就會被放到這個queue裏。咱們拿到reference,就能夠再做一些事務。而若是不帶的話,就只有不斷地輪訓reference對象,經過判斷裏面的get是否返回null(phantomReference對象不能這樣做,其get始終返回null,所以它只有帶queue的構造函數)。這兩種方法均有相應的使用場景,取決於實際的應用。如WeakHashMap中就選擇去查詢queue的數據,來斷定是否有對象將被回收。緩存

    Reference 把內存分爲 4 種狀態,Active 、 Pending 、 Enqueued 、 Inactive。性能優化

    • Active 通常說來內存一開始被分配的狀態都是 Active
    • Pending 快要放入隊列(ReferenceQueue)的對象,也就是立刻要回收的對象
    • Enqueued 對象已經進入隊列,已經被回收的對象。方便咱們查詢某個對象是否被回收
    • Inactive 最終的狀態,沒法變成其餘的狀態。

    LeakCanary種就是WeakReference和ReferenceQueue聯合使用,若是弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。bash

  3. ActivityRefWatcher:用於監控Activity,但只能用於Android 4.0及其之上 它經過watchActivities方法將全局的Activity生命週期回調接口 Application.ActivityLifecycleCallbacks註冊到application服務器

  4. RefWatcher:做用 LeakCanary核心中的核心。RefWatcher的工做就是觸發GC,若是對象被回收,那麼WeakReference將被放入 ReferenceQueue中,不然就懷疑有泄漏(僅僅是懷疑),而後將內存dump出來,爲接下來的深刻分析作準備。app

  5. ExcludedRef:LeakCanary提供了ExcludedRefs來靈活控制是否須要將一些對象排除在考慮以外,由於在Android Framework,手機廠商rom自身也存在一些內存泄漏,對於開發者來講這些泄漏是咱們無能爲力的,因此在AndroidExcludedRefs中定義了不少排除考慮的類

  6. HeapDump.Listener與ServiceHeapDumpListener:ServiceHeapDumpListener實現了HeapDump.Listener接口。當RefWatcher發現可疑引用的以後,它將dump出來的Hprof文件經過 listener傳遞到HeapAnalyzerService。

  7. HeapAnalyzerService:主要是經過HeapAnalyzer.checkForLeak分析對象的引用,計算出到GC root的最短強引用路徑。而後將分析結果傳遞給DisplayLeakService。

  8. AbstractAnalysisResultService與DisplayLeakService:DisplayLeakService繼承了AbstractAnalysisResultService。它主要是用來處理分析結果,將結果寫入文件,而後在通知欄報警。

  9. Heap Dump:Heap Dump也叫堆轉儲文件,是一個Java進程在某個時間點上的內存快照。

LeakCanary的源碼深刻

LeakCanary使用很是方便,不過實際上全部操做都是在源碼中完成的。

1. LeakCanary.install(sApplication)

public static RefWatcher install(Application application) {
    return refWatcher(application) // 獲取AndroidRefWatcherBuilder構造者
        // 設置監聽服務的Class(注意:此處很重要)
        .listenerServiceClass(DisplayLeakService.class)
        // LeakCanary提供了ExcludedRefs來靈活控制是否須要將一些對象排除在考慮以外,
        // 由於在Android Framework,手機廠商rom自身也存在一些內存泄漏,
        // 對於開發者來講這些泄漏是咱們無能爲力的,因此在AndroidExcludedRefs中定義了不少排除考慮的類
        .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
        // 構建並裝載
        .buildAndInstall();
  }
複製代碼

咱們簡單說一下系方法中的幾點:

  1. 該靜態方法返回的RefWatcher,都是經過AndroidRefWatcherBuilder這個構造類來配置相關信息的
  2. listenerServiceClass方法和泄漏分析相關的服務綁定,綁定到DisplayLeakService.class這個類上面,這個類負責分析和通知泄漏消息給開發人員
  3. excludedRefs方法是排除一些開發能夠忽略的泄漏(通常是系統級別BUG),這些枚舉在AndroidExcludedRefs這個類當中定義
  4. buildAndInstall這個方法纔是真正構建的重點

2. buildAndInstall()

// AndroidRefWatcherBuilder類:
    // 是否觀察Activity的內存泄漏
    private boolean watchActivities = true;
    // 是否觀察Fragment的內存泄漏
    private boolean watchFragments = true;
    
    public RefWatcher buildAndInstall() {
        // LeakCanaryInternals類是LeakCanary的一些相似於工具類的邏輯處理等(都是靜態方法)
        if (LeakCanaryInternals.installedRefWatcher != null) {
            // installedRefWatcher 用於保存是否構建並install過LeakCanary
            // 若是重複構建並安裝LeakCanary,則會拋出以下異常
            throw new UnsupportedOperationException("buildAndInstall() should only be called once" +".");
        }
        // 實例化RefWatcher對象,這個對象是用來處理泄漏對象的
        RefWatcher refWatcher = build();
        if (refWatcher != DISABLED) {
            // 默認爲true
            if (watchActivities) {
                ActivityRefWatcher.install(context, refWatcher);
            }
            // 默認爲true
            if (watchFragments) {
                FragmentRefWatcher.Helper.install(context, refWatcher);
            }
        }
        LeakCanaryInternals.installedRefWatcher = refWatcher;
        return refWatcher;
    }
複製代碼

從代碼中能夠看出buildAndInstall方法將build部分交給了build()方法,install部分使用ActivityRefWatcher和FragmentRefWatcher.Helper。

1). build部分

// RefWatcherBuilder類中,此類爲AndroidRefWatcherBuilder的父類
    // 此構建方法中大部分都是獲取默認的值
    public final RefWatcher build() {
        if (isDisabled()) {
            return RefWatcher.DISABLED;
        }
        // 此處不爲null
        if (heapDumpBuilder.excludedRefs == null) {
            heapDumpBuilder.excludedRefs(defaultExcludedRefs());
        }

        // 這裏的this.heapDumpListener 不爲null
        HeapDump.Listener heapDumpListener = this.heapDumpListener;
        if (heapDumpListener == null) {
            heapDumpListener = defaultHeapDumpListener();
        }
        
        // 下面的默認爲null
        
        // 用於查詢是否在 debug 調試模式下,調試中不會執行內存泄漏檢測。
        DebuggerControl debuggerControl = this.debuggerControl;
        if (debuggerControl == null) {
            debuggerControl = defaultDebuggerControl();
        }

        // 用於產生內存泄漏分析用的 dump 文件。即 dump 內存 head。
        HeapDumper heapDumper = this.heapDumper;
        if (heapDumper == null) {
            heapDumper = defaultHeapDumper();
        }

        // 執行內存泄漏檢測的 Executor
        WatchExecutor watchExecutor = this.watchExecutor;
        if (watchExecutor == null) {
            watchExecutor = defaultWatchExecutor();
        }
        
        // GC 開關,調用系統GC。
        GcTrigger gcTrigger = this.gcTrigger;
        if (gcTrigger == null) {
            gcTrigger = defaultGcTrigger();
        }

        if (heapDumpBuilder.reachabilityInspectorClasses == null) {
            heapDumpBuilder.reachabilityInspectorClasses(defaultReachabilityInspectorClasses());
        }
        // 初始化RefWatcher對象
        return new RefWatcher(watchExecutor, debuggerControl, gcTrigger, heapDumper,
                heapDumpListener,
                heapDumpBuilder);
    }
複製代碼

咱們看一看build()方法幾個變量:

  1. heapDumpBuilder.excludedRefs:此變量其實是在LeakCanary.install方法中經過excludedRefs設置的,用於過濾系統的一些內存泄漏。

  2. this.heapDumpListener:此變量實際上同heapDumpBuilder.excludedRefs,是經過listenerServiceClass方法設置的。咱們簡單看一下其源碼:

    // 1. AndroidRefWatcherBuilder類:
     public AndroidRefWatcherBuilder listenerServiceClass(
             Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
         return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
     }
     
     // 2. ServiceHeapDumpListener類:
     public ServiceHeapDumpListener(final Context context,
                                    final Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
         this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
         this.context = checkNotNull(context, "context").getApplicationContext();
     }
     
     // 3. RefWatcherBuilder類:
     public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
         // 此處就是1中heapDumpListener方法參數初始化的2對象
         this.heapDumpListener = heapDumpListener;
         return self();
     }
    複製代碼

    所以heapDumpListener實際上就是ServiceHeapDumpListener對象。

  3. 其它:其它變量默認都是null,所以設置的都是默認值。

2). install部分

咱們已ActivityRefWatcher爲例說明:

// ActivityRefWatcher類:
    // 經過提供的靜態方法初始化ActivityRefWatcher並註冊回調
    public static void install(Context context, RefWatcher refWatcher) {
        Application application = (Application) context.getApplicationContext();
        // 初始化ActivityRefWatcher,並將build部分構建的RefWatcher傳入
        ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
        // 註冊Activity的生命週期回調
        application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
    }
    
    // 生命週期回調
    private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
            new ActivityLifecycleCallbacksAdapter() {
                @Override
                public void onActivityDestroyed(Activity activity) {
                    // Activity進入Destroy狀態時開始監控其引用變化
                    refWatcher.watch(activity);
                }
            };

    private final Application application;
    private final RefWatcher refWatcher;

    private ActivityRefWatcher(Application application, RefWatcher refWatcher) {
        this.application = application;
        // 初始化refWatcher成員
        this.refWatcher = refWatcher;
    }
複製代碼

咱們經過此部分代碼,能夠看出來,install靜態方法中初始化了ActivityRefWatcher的實例對象,而且註冊了Activity的生命週期的回調,最終在Activity的onDestroy狀態下開始使用refWatcher.watch監控當前Activity是否存在內存泄漏。

3. RefWatcher.watch

1. RefWatcher簡介

private final WatchExecutor watchExecutor;
    private final DebuggerControl debuggerControl;
    private final GcTrigger gcTrigger;
    private final HeapDumper heapDumper;
    private final HeapDump.Listener heapdumpListener;
    private final HeapDump.Builder heapDumpBuilder;
    private final Set<String> retainedKeys;
    private final ReferenceQueue<Object> queue;
複製代碼
  • watchExecutor(實際爲AndroidWatchExecutor,內部使用Handler):執行內存泄漏檢測的Executor。
  • debuggerControl:用於查詢是否在 debug 調試模式下,調試中不會執行內存泄漏檢測。
  • gcTrigger:GC 開關,調用系統GC。
  • heapDumper(實際爲AndroidHeapDumper對象):用於產生內存泄漏分析用的dump文件。
  • retainedKeys:保存待檢測和產生內存泄漏的引用的 key。
  • queue(RefWatcher構造方法中初始化queue):用於保存被gc的弱引用。
  • heapdumpListener:用於分析 dump 文件,生成內存泄漏分析報告。
  • heapDumpBuilder:經過heapDumper產生的dump文件以及其它信息,來構建分析內存泄漏的HeapDump對象。

2. watch(activity)

// RefWatcher類:
    public void watch(Object watchedReference) {
        // watchedReference爲Activity或者Fragment
        watch(watchedReference, "");
    }

    public void watch(Object watchedReference, String referenceName) {
        if (this == DISABLED) {
            return;
        }
        checkNotNull(watchedReference, "watchedReference");
        checkNotNull(referenceName, "referenceName");
        // 返回的是JVM運行的納秒數
        final long watchStartNanoTime = System.nanoTime();
        // key值是用來最終定位泄漏對象用的,用來標識當前Activity或者Fragment的惟一值
        String key = UUID.randomUUID().toString();
        retainedKeys.add(key);
        // 建立一個弱引用,並傳入key和queue(引用隊列)
        // KeyedWeakReference這個弱引用對象使用的是帶ReferenceQueue的構造方法
        final KeyedWeakReference reference =
                new KeyedWeakReference(watchedReference, key, referenceName, queue);
        // 繼續執行
        ensureGoneAsync(watchStartNanoTime, reference);
    }

    private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
        // 實際上使用的是Handler的post方法
        // // 在異步線程上開始分析這個弱引用
        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);
        // 移除弱引用
        removeWeaklyReachableReferences();

        if (debuggerControl.isDebuggerAttached()) {
            // 若是VM正鏈接到Debuger,忽略此次檢測,由於Debugger可能會持有一些在當前上下文中不可見的對象,致使誤判
            return RETRY;
        }
        if (gone(reference)) {
            // 若是引用已經不存在了則返回
            return DONE;
        }
        gcTrigger.runGc(); // 觸發GC
        removeWeaklyReachableReferences(); ;// 再次移除弱引用,二次確認
        // 若是GC以後引用仍是存在,那麼就進行深刻分析
        if (!gone(reference)) {
            long startDumpHeap = System.nanoTime();
            long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
            // dump出內存快照到*.hprof文件
            File heapDumpFile = heapDumper.dumpHeap();
            if (heapDumpFile == RETRY_LATER) {
                // Could not dump the heap.
                return RETRY;
            }
            long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
            
            // 構建HeapDump對象
            HeapDump heapDump =
                    heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
                    .referenceName(reference.name)
                    .watchDurationMs(watchDurationMs)
                    .gcDurationMs(gcDurationMs)
                    .heapDumpDurationMs(heapDumpDurationMs)
                    .build();
            // 對.hprof文件進行分析
            heapdumpListener.analyze(heapDump);
        }
        return DONE;
    }
複製代碼

這一大段代碼咱們分別來分析一下:

  1. 觸發watch動做過程分析:當Activity的onDestroy調用的時候,Application會收到通知,而後調用 lifecycleCallback.onActivityDestroyed()方法,最終RefWatcher的watch方法被觸發,也就實現 了Activity內存泄漏自動分析。

  2. 建立一個弱引用KeyedWeakReference,而當JVM觸發GC時,弱引用就會被回收,那麼此弱引用添加到引用隊列(ReferenceQueue)當中去。(WeakReference構造方法傳入ReferenceQueue隊列的時候,若引用的對象被回收,則將其加入該隊列。)

    具體可參考:Java中各類引用(Reference)解析詳解java中Reference的實現與相應的執行過程

  3. 經過兩次移除弱引用,第二次以前手動調用gc操做。若還引用還存在,則生成堆內存dump文件,並初始化HeapDump對象,最後調用heapdumpListener.analyze分析並通知內存泄漏。

再來看兩個上段代碼中的兩個方法的具體源碼:

private boolean gone(KeyedWeakReference reference) {
        // 判斷retainedKeys容器中有沒有key。
        // 若回收了,就不存在key了,那麼就沒有泄漏,不然就懷疑有泄漏。
        return !retainedKeys.contains(reference.key);
    }

    private void removeWeaklyReachableReferences() {
        KeyedWeakReference ref;
        // 這個對象做爲弱引用,若回收了,那麼添加到引用隊列(ReferenceQueue)當中去,因此這個函數.poll是出棧的意思,
        // 若是成功出棧了,那麼說明你加入了引用隊列,而後能夠認爲是已經被回收了
        // 而後retainedKeys這個是一個Set容器,在以前會加入生成的惟一key做爲標識,這裏若是這個對象回收了,那麼就移除這個key值。
        while ((ref = (KeyedWeakReference) queue.poll()) != null) {
            retainedKeys.remove(ref.key);
        }
    }
複製代碼

4. heapdumpListener.analyze內存快照分析

說明:此處的heapdumpListener就是上面【1). build部分】中的ServiceHeapDumpListener

此部分核心內容使用了另外一個庫:HaHa

// ServiceHeapDumpListener類:
    @Override
    public void analyze(HeapDump heapDump) {
        checkNotNull(heapDump, "heapDump");
        HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
    }

// HeapAnalyzerService類:
    public static void runAnalysis(Context context, HeapDump heapDump
        ,Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
        // Android 禁用與啓用 APP 或四大組件
        // 請參考:https://blog.csdn.net/ShawnXiaFei/article/details/82020386
        setEnabledBlocking(context, HeapAnalyzerService.class, true);
        setEnabledBlocking(context, listenerServiceClass, true);
        Intent intent = new Intent(context, HeapAnalyzerService.class);
        // 此處listenerServiceClass,
        // 即【LeakCanary.install(sApplication)】中提到的DisplayLeakService.class
        intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
        intent.putExtra(HEAPDUMP_EXTRA, heapDump);
        // 開啓前臺服務
        ContextCompat.startForegroundService(context, intent);
    }
    
    @Override
    protected void onHandleIntentInForeground(@Nullable Intent intent) {
        if (intent == null) {
            CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
            return;
        }
        // 獲取DisplayLeakService.class
        String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
        HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);

        // 初始化堆分析儀
        HeapAnalyzer heapAnalyzer =
                new HeapAnalyzer(heapDump.excludedRefs, this,
                        heapDump.reachabilityInspectorClasses);
        // checkForLeak就是最爲關鍵的方法
        AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile,
                heapDump.referenceKey,
                heapDump.computeRetainedHeapSize);
        // 將結果發送給偵聽器
        AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump,result);
    }
    
    @Override
    public void onProgressUpdate(Step step) {
        // 更新分析內存泄漏過程當中的進度狀態
        int percent = (int) ((100f * step.ordinal()) / Step.values().length);
        CanaryLog.d("Analysis in progress, working on: %s", step.name());
        String lowercase = step.name().replace("_", " ").toLowerCase();
        String message = lowercase.substring(0, 1).toUpperCase() + lowercase.substring(1);
        // 開啓通知顯示
        showForegroundNotification(100, percent, false, message);
    }
複製代碼

analyze 方法開啓了HeapAnalyzerService堆分析服務,HeapAnalyzerService繼承自ForegroundService,而ForegroundService類繼承自IntentService。ForegroundService類重寫了onHandleIntent方法,並在該方法中調用了本身聲明的抽象方法onHandleIntentInForeground。而HeapAnalyzerService類又實現了AnalyzerProgressListener接口,該接口中只有一個方法onProgressUpdate(分析進度狀態更新)和分析過程當中的狀態枚舉。所以HeapAnalyzerService服務開啓後會直接執行onHandleIntentInForeground方法,最後執行到了一個最重要的方法checkForLeak:

// HeapAnalyzer類:
    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);
            // 利用HAHA(基於MAT的堆棧解析庫)將以前dump出來的內存文件解析成Snapshot對象
            // 根據堆轉儲文件生成HprofBuffer緩存
            HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
            // Hprof文件解析對象
            HprofParser parser = new HprofParser(buffer);
            listener.onProgressUpdate(PARSING_HEAP_DUMP);
            // 解析過程,是基於google的perflib庫,根據hprof的格式進行解析
            Snapshot snapshot = parser.parse();
            listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
            // 分析結果進行去重,可減小內存壓力
            deduplicateGcRoots(snapshot);
            listener.onProgressUpdate(FINDING_LEAKING_REF);
            // 此方法就是根據咱們須要檢測的類的key,查詢解析結果中是否有咱們的對象,獲取解析結果中咱們檢測的對象  
            Instance leakingRef = findLeakingReference(referenceKey, snapshot);

            // 此對象不存在表示已經被gc清除了,不存在泄露所以返回無泄漏
            if (leakingRef == null) {
                return noLeak(since(analysisStartNanoTime));
            }
            // 此對象存在也不能確認它內存泄漏了,要檢測此對象的gc root  
            return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
        } catch (Throwable e) {
            return failure(e, since(analysisStartNanoTime));
        }
    }

    private Instance findLeakingReference(String key, Snapshot snapshot) {
        // 由於須要監控的類,都構造了一個KeyedWeakReference
        // 所以先找到KeyedWeakReference,就能夠找到咱們的對象  
        ClassObj refClass = snapshot.findClass(KeyedWeakReference.class.getName());
        if (refClass == null) {
            throw new IllegalStateException(
                    "Could not find the " + KeyedWeakReference.class.getName() + " class in the " +
                            "heap dump.");
        }
        List<String> keysFound = new ArrayList<>();
        // 循環全部KeyedWeakReference實例  
        for (Instance instance : refClass.getInstancesList()) {
            List<ClassInstance.FieldValue> values = classInstanceValues(instance);
            // 找到KeyedWeakReference裏面的key值,此值在咱們前面傳入的對象惟一標示
            Object keyFieldValue = fieldValue(values, "key");
            if (keyFieldValue == null) {
                keysFound.add(null);
                continue;
            }
            String keyCandidate = asString(keyFieldValue);
            // 當key值相等時就表示是咱們的檢測對象
            if (keyCandidate.equals(key)) {
                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, boolean computeRetainedSize) {

        listener.onProgressUpdate(FINDING_SHORTEST_PATH);
        /**
         * 這兩行代碼是判斷內存泄露的關鍵,咱們在上面中分析hprof文件,判斷內存泄漏  
         * 判斷的依據是展開調用到gc root,所謂gc root,就是不能被gc回收的對象,
         * 查找泄露的最短引用鏈,gc root有不少類型,咱們只要關注兩種類型:
         *  1.此對象是靜態 2.此對象被其餘線程使用,而且其餘線程正在運行,沒有結束  
         * pathFinder.findPath方法中也就是判斷這兩種狀況  
         */
        ShortestPathFinder pathFinder = new ShortestPathFinder(excludedRefs);
        ShortestPathFinder.Result result = pathFinder.findPath(snapshot, leakingRef);

        // 找不到引發內存泄漏的gc root,就表示此對象未泄漏  
        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);
            // 反作用:計算保留的大小。
            snapshot.computeDominators();

            Instance leakingInstance = result.leakingNode.instance;
            // 計算泄漏的空間大小  
            retainedSize = leakingInstance.getTotalRetainedSize();

            // 檢查Android O以上,並查看android.graphics.Bitmap.mBuffer發生了什麼
            if (SDK_INT <= N_MR1) {
                listener.onProgressUpdate(COMPUTING_BITMAP_SIZE);
                // 計算忽略的位圖保留大小
                retainedSize += computeIgnoredBitmapRetainedSize(snapshot, leakingInstance);
            }
        } else {
            retainedSize = AnalysisResult.RETAINED_HEAP_SKIPPED;
        }
        // 檢測到泄漏,構建AnalysisResult分析結果對象返回
        return leakDetected(result.excludingKnownLeaks, className, leakTrace, retainedSize,
                since(analysisStartNanoTime));
    }
複製代碼

第三個分析步驟,解析hprof文件中,是先把這個文件封裝成snapshot,而後根據弱引用和前面定義的key值,肯定泄漏的對象,最後找到最短泄漏路徑,做爲結果反饋出來, 那麼若是在快照中找不到這個懷疑泄漏的對象,那麼就認爲這個對象其實並無泄漏。

最後,當內存泄漏分析完成,則調用AbstractAnalysisResultService.sendResultToListener:

// AbstractAnalysisResultService類:
    public static void sendResultToListener(Context context
        , String listenerServiceClassName,HeapDump heapDump, AnalysisResult result) {
        Class<?> listenerServiceClass;
        try {
            listenerServiceClass = Class.forName(listenerServiceClassName);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        // 開啓DisplayLeakService服務
        Intent intent = new Intent(context, listenerServiceClass);
        intent.putExtra(HEAP_DUMP_EXTRA, heapDump);
        intent.putExtra(RESULT_EXTRA, result);
        ContextCompat.startForegroundService(context, intent);
    }
複製代碼

內存泄漏分析完成時,會開啓DisplayLeakService服務,該服務繼承自AbstractAnalysisResultService,而AbstractAnalysisResultService服務又繼承自ForegroundService,而AbstractAnalysisResultService重寫了ForegroundService服務的onHandleIntentInForeground(該方法是在onHandleIntent中調用),並在onHandleIntentInForeground方法中調用了onHeapAnalyzed方法,最終刪除了heapDumpFile文件。

// ForegroundService類:
    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        onHandleIntentInForeground(intent);
    }
// AbstractAnalysisResultService類:
    @Override
    protected final void onHandleIntentInForeground(Intent intent) {
        HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
        AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
        try {
            onHeapAnalyzed(heapDump, result);
        } finally {
            //noinspection ResultOfMethodCallIgnored
            heapDump.heapDumpFile.delete();
        }
    }
    
// DisplayLeakService類:
    @Override
    protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
        String leakInfo = leakInfo(this, heapDump, result, true);
        CanaryLog.d("%s", leakInfo);

        boolean resultSaved = false;
        boolean shouldSaveResult = result.leakFound || result.failure != null;
        if (shouldSaveResult) {
            heapDump = renameHeapdump(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) {
                if (result.retainedHeapSize == AnalysisResult.RETAINED_HEAP_SKIPPED) {
                    String className = classSimpleName(result.className);
                    if (result.excludedLeak) {
                        contentTitle = getString(R.string.leak_canary_leak_excluded, className);
                    } else {
                        contentTitle = getString(R.string.leak_canary_class_has_leaked, className);
                    }
                } else {
                    String size = formatShortFileSize(this, result.retainedHeapSize);
                    String className = classSimpleName(result.className);
                    if (result.excludedLeak) {
                        contentTitle = getString(R.string.leak_canary_leak_excluded_retaining, className, size);
                    } else {
                        contentTitle = getString(R.string.leak_canary_class_has_leaked_retaining, 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);
        afterDefaultHandling(heapDump, result, leakInfo);
    }
    
// LeakCanaryInternals類:
    public static void showNotification(Context context
            , CharSequence contentTitle, CharSequence contentText, PendingIntent pendingIntent, int notificationId) {
        Notification.Builder builder = new Notification.Builder(context)
                .setContentText(contentText)
                .setContentTitle(contentTitle)
                .setAutoCancel(true)
                .setContentIntent(pendingIntent);

        Notification notification = buildNotification(context, builder);
        NotificationManager notificationManager =
                (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.notify(notificationId, notification);
    }
複製代碼

內存泄漏分析完成後,開啓DisplayLeakService服務,並調用onHeapAnalyzed方法,最終彈出通知告知 開發者內存泄漏的引用了,堆引用路徑。

LeakCanary的自定義保存泄漏信息

對於Android開發來講,用leakcanary來檢測內存泄漏非常方便與快捷的。不過若LeakCanary沒法知足需求,能夠自定義將內存泄漏結果保存本地。

在LeakCanary中的DisplayLeakService.java類中有一個空方法,以下:

/**
     * 您能夠重寫此方法,並對服務器進行阻塞調用以上傳泄漏跟蹤和堆轉儲。
     * 不要忘記先檢查{@link AnalysisResult#leakFound and AnalysisResult#excludedLeak }
     */
    protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
    }
複製代碼
  1. 繼承DisplayLeakService類,重寫afterDefaultHandling()方法,實現本身的泄漏信息處理

    public class LeadCanaryService extends DisplayLeakService {
         @Override
         protected void afterDefaultHandling(HeapDump heapDump, AnalysisResult result, String leakInfo) {
             super.afterDefaultHandling(heapDump, result, leakInfo);
             // 泄漏信息上傳雲端或者保存本地
             saveLocal(result, leakInfo);
         }
     
         private void saveLocal(AnalysisResult result, String leakInfo) {
             if (result != null) {
                 String leakPath = getApplication().getCacheDir().getAbsolutePath() + "/LeakCanary" +
                         "/LeakCanary.log";
                 File file = new File(leakPath);
                 FileUtils.createFileDir(file);
     
                 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                 String leadMessage = "Time" + simpleDateFormat.toString() +
                         "\\n AnalysisResult{" +
                         "leakFound=" + result.leakFound +
                         ", excludedLeak=" + result.excludedLeak +
                         ", className='" + result.className + '\'' +
                         ", leakTrace=" + result.leakTrace +
                         ", failure=" + result.failure +
                         ", retainedHeapSize=" + result.retainedHeapSize +
                         ", analysisDurationMs=" + result.analysisDurationMs +
                         "} \\r\\n";
     
                 ByteArrayInputStream byteArrayInputStream =
                         new ByteArrayInputStream(leadMessage.getBytes());
                 try {
                     FileUtils.writeFile(byteArrayInputStream, file);
                 } catch (IOException e) {
                     e.printStackTrace();
                 }
             }
     
         }
     }
    複製代碼
  2. AndroidManifest.xml中註冊自定義服務LeadCanaryService

    <service android:name=".service.LeadCanaryService"/>
    複製代碼
  3. Application中引用自定義服務LeadCanaryService

    LeakCanary.refWatcher(this).listenerServiceClass(LeadCanaryService.class)
             .excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
             .buildAndInstall();
    複製代碼

到此,LeakCanary分析就結束啦,對性能優化的小夥伴能夠看一下個人Github:github.com/Endless5F/J… ,有詳細的文檔和代碼參考。

參考連接

www.cnblogs.com/cord/p/1154…

www.cnblogs.com/huanyi0723/…

www.jianshu.com/p/9a7c0e6e6…

注:如有什麼地方闡述有誤,敬請指正。期待您的點贊哦!!!

相關文章
相關標籤/搜索