LeakCanary源碼解析
前言
對于內(nèi)存泄漏的檢測,基于MAT起點(diǎn)較高,所以一般我們都使用LeakCanary來作為我們的內(nèi)存泄漏檢測工具來使用。
基礎(chǔ)知識
四種引用
LeakCanary主要是基于弱引用來進(jìn)行對于已經(jīng)銷毀的Activity和Fragment的回收監(jiān)控來實(shí)現(xiàn)的。
強(qiáng)引用:無論如何都不會回收。
軟引用:內(nèi)存足夠不回收。內(nèi)存不夠時(shí),就會回收。
弱引用:垃圾回收時(shí)直接回收,則直接回收。
虛引用:垃圾回收時(shí)直接回收。
引用隊(duì)列(ReferenceQueue)。
軟引用和弱引用都可以關(guān)聯(lián)一個(gè)引用隊(duì)列。當(dāng)引用的對象被回收以后,會將軟引用加入到與之關(guān)聯(lián)的引用隊(duì)列中。LeakCanary的基礎(chǔ)實(shí)現(xiàn)就是將已經(jīng)銷毀的Activity和Fragment所對應(yīng)的實(shí)例放入到弱引用中,并關(guān)聯(lián)一個(gè)引用隊(duì)列。如果實(shí)例進(jìn)行了回收,那么弱引用就會放入到ReferenceQueue中,如果一段時(shí)間后,所監(jiān)控的實(shí)例還未在ReferenceQueue中出現(xiàn),那么可以證明出現(xiàn)了內(nèi)存泄漏導(dǎo)致了實(shí)例沒有被回收。
使用方法
配置:
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3'
// Optional, if you use support library fragments:
debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3'
}
使用:
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
LeakCanary.install(this);
}
}
Leakcanary原理解析
從程序的唯一入口來進(jìn)行分析。本文是基于1.6.3版本來進(jìn)行源碼解析的。對應(yīng)的解析源碼地址為leakcanary-source。
注冊實(shí)例的監(jiān)控
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application)//創(chuàng)建一個(gè)Android端使用的引用監(jiān)控的構(gòu)造者
.listenerServiceClass(DisplayLeakService.class)
//設(shè)置不進(jìn)行監(jiān)控的類引用對象
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
//創(chuàng)建對于引用的監(jiān)控
.buildAndInstall();
}
這個(gè)方法比較簡短,一個(gè)個(gè)進(jìn)行解析吧。
構(gòu)造一個(gè)AndroidRefWatcherBuilder對象
//創(chuàng)建一個(gè)AndroidRefWatcherBuilder對象
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
這里創(chuàng)建的AndroidRefWatcherBuilder對象是一個(gè)適用于Android端的引用監(jiān)控的構(gòu)造者。
設(shè)置后臺的監(jiān)聽類
//AndroidRefWatcherBuilder.java
//設(shè)置一個(gè)類用來監(jiān)聽分析的結(jié)果。
public @NonNull AndroidRefWatcherBuilder listenerServiceClass(@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
//設(shè)置一個(gè)監(jiān)聽者
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
//RefWatcherBuilder.java
//HeapDump的監(jiān)聽者
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
return self();
}
這里將DisplayLeakService類作為了我們最終內(nèi)存泄漏的分析者,并且該類能夠進(jìn)行內(nèi)存泄漏消息的通知(一般是Notification)。
不納入監(jiān)控的引用
excludedRefs方法能夠?qū)⒁恍┪覀儾魂P(guān)心的引用排除在我們的監(jiān)控范圍以外。這里這么處理,主要是因?yàn)橐恍┫到y(tǒng)級別的引用問題。我們可以具體看一下里面有哪些東西是我們不需要關(guān)注的。
//由于Android的AOSP本身可能會存在內(nèi)存泄漏的東西,所以對于這些東西默認(rèn)是不會進(jìn)行提醒的。
public static @NonNull ExcludedRefs.Builder createAppDefaults() {
//將AndroidExcludedRefs所有的枚舉類型都考慮在內(nèi)。
return createBuilder(EnumSet.allOf(AndroidExcludedRefs.class));
}
public static @NonNull ExcludedRefs.Builder createBuilder(EnumSet<AndroidExcludedRefs> refs) {
ExcludedRefs.Builder excluded = ExcludedRefs.builder();
//遍歷所有的枚舉類型
for (AndroidExcludedRefs ref : refs) {
//如果枚舉類型執(zhí)行引用的排除處理
if (ref.applies) {
//調(diào)用枚舉的add方法,這里面會將所有需要排除的引用類都放到出入的excluede中
ref.add(excluded);
((ExcludedRefs.BuilderWithParams) excluded).named(ref.name());
}
}
return excluded;
}
這個(gè)可能會有一些難以理解,我們先簡單分析一下AndroidExcludedRefs這個(gè)類。
public enum AndroidExcludedRefs {
//參數(shù),標(biāo)識是否需要執(zhí)行add方法
final boolean applies;
AndroidExcludedRefs() {
this(true);
}
AndroidExcludedRefs(boolean applies) {
this.applies = applies;
}
//枚舉類需要實(shí)現(xiàn)的方法
abstract void add(ExcludedRefs.Builder excluded);
}
AndroidExcludedRefs是一個(gè)枚舉類型。含有成員變量applies以及add()方法。
我們再分析一個(gè)具體的枚舉類型。
//AndroidExcludedRefs.java
ACTIVITY_CLIENT_RECORD__NEXT_IDLE(SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP) {
@Override
void add(ExcludedRefs.Builder excluded) {
//設(shè)置排除的類中的某個(gè)屬性
excluded.instanceField("android.app.ActivityThread$ActivityClientRecord", "nextIdle")
//設(shè)置排除的原因
.reason("Android AOSP sometimes keeps a reference to a destroyed activity as a"
+ " nextIdle client record in the android.app.ActivityThread.mActivities map."
+ " Not sure what's going on there, input welcome.");
}
},
ACTIVITY_CLIENT_RECORD__NEXT_IDLE就是一個(gè)具體的枚舉類型。applies賦值為SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP。也有add方法的具體實(shí)現(xiàn)。實(shí)現(xiàn)中將需要排除的引用類型添加到了excluded中。
所以當(dāng)我們的系統(tǒng)版本號滿足SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP這個(gè)條件的時(shí)候,就會執(zhí)行add方法。
AndroidExcludedRefs具有不同的枚舉實(shí)例,會根據(jù)不同的系統(tǒng)版本來進(jìn)行不同的處理。這里其實(shí)主要是保證對于一些系統(tǒng)級別的內(nèi)存泄漏情況不再進(jìn)行提示。
創(chuàng)建引用的監(jiān)控
我們直接看看buildAndInstall中是如何對已經(jīng)執(zhí)行onDestroy的Activity進(jìn)行監(jiān)控的。
//根據(jù)對應(yīng)的設(shè)置信息,返回一個(gè)RefWatcher對象
public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
//通過構(gòu)造者模式中的build()方法創(chuàng)建一個(gè)RefWatcher對象,這里面會有很多默認(rèn)的設(shè)置
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
//如果允許顯示內(nèi)存泄漏Activity,則進(jìn)行處理
if (enableDisplayLeakActivity) {
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
}
//如果設(shè)置了監(jiān)聽Activity,那么就為Activity注冊生命周期監(jiān)聽
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
//如果設(shè)置了監(jiān)聽Fragment,那么就為Fragment注冊生命周期監(jiān)聽
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
我們這里主要看一下如何進(jìn)行Activity以及Fragment的監(jiān)聽的。
- 對Activity的處理
//ActivityRefWatcher.java
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
//創(chuàng)建一個(gè)對于Activity的弱引用監(jiān)聽類
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
//對傳入的應(yīng)用的Application注冊一個(gè)對于Activity的生命周期監(jiān)聽函數(shù)
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
這里創(chuàng)建了一個(gè)ActivityRefWatcher對象,然后將對于應(yīng)用,通過registerActivityLifecycleCallbacks注冊了一個(gè)監(jiān)聽的回調(diào)。
//ActivityRefWatcher.java
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
//只監(jiān)聽destory方法,將調(diào)用destory的activity添加到監(jiān)聽watcher中
@Override
public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
在這個(gè)監(jiān)聽方法中,只監(jiān)聽了Activity的onDestroy方法。當(dāng)Activity銷毀的時(shí)候,使用refWatcher來監(jiān)控其實(shí)例。
- 對Fragment的處理
//FragmentRefWatcher.java
public static void install(Context context, RefWatcher refWatcher) {
List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
//將實(shí)現(xiàn)了FragmentRefWatcher接口的兩個(gè)實(shí)現(xiàn)類加入到fragmentRefWatchers中
//兩個(gè)實(shí)現(xiàn)類,一個(gè)是實(shí)現(xiàn)對于V4包下的Fragment的監(jiān)聽,一個(gè)是對于當(dāng)前包下Fragment的監(jiān)聽
if (SDK_INT >= O) {
//實(shí)現(xiàn)類AndroidOFragmentRefWatcher
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
}
try {
//實(shí)現(xiàn)類SupportFragmentRefWatcher用于監(jiān)聽V4包下面的Fragment
//這里使用反射,是因?yàn)镾upportFragmentRefWatcher這個(gè)類在support-fragment這個(gè)module中。
//所以,如果我們沒有引入V4的話,其實(shí)這個(gè)類是可以不引入的。
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) {
}
//如果沒有Fragment的監(jiān)控者,那么直接返回
if (fragmentRefWatchers.size() == 0) {
return;
}
//創(chuàng)建Helper實(shí)例
Helper helper = new Helper(fragmentRefWatchers);
Application application = (Application) context.getApplicationContext();
//注冊Activity的生命周期回調(diào)
application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
}
由于我們經(jīng)常使用的Fragment包含兩種,一種是support包中的Fragment,一種是標(biāo)準(zhǔn)的app包中的Fragment。這里對這兩種都進(jìn)行了處理。
我們看一下對于注冊的生命周期函數(shù)
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
//這里會調(diào)用具體的實(shí)現(xiàn)類的watchFragments方法。這里關(guān)心的是綁定的Activity的onCreate方法。走到這里的時(shí)候已經(jīng)創(chuàng)建了對應(yīng)FragmentManager對象
//而通過FragmentManager對象可以來registerFragmentLifecycleCallbacks來創(chuàng)建對于其管理的Fragment的生命周期監(jiān)聽
watcher.watchFragments(activity);
}
}
};
這里我們同樣是注冊了Activity的生命周期回調(diào)。但是這里監(jiān)控的是onActivityCreated方法。我們這里看一下watchFragments的實(shí)現(xiàn)。
具體的實(shí)現(xiàn)有兩個(gè)類,一個(gè)是SupportFragmentRefWatcher,一個(gè)是AndroidOFragmentRefWatcher。我們這里只分析第一個(gè)。剩下的另一個(gè)是類似的,只是因?yàn)槭褂玫腇ragment不同,而有所區(qū)別。
public void watchFragments(Activity activity) {
//V4包中的Fragment,必須使用FragmentActivity來進(jìn)行處理
if (activity instanceof FragmentActivity) {
FragmentManager supportFragmentManager = ((FragmentActivity) activity).getSupportFragmentManager();
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
}
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
@Override
public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
View view = fragment.getView();
if (view != null) {
//當(dāng)fragment的view銷毀的時(shí)候,開始監(jiān)控
refWatcher.watch(view);
}
}
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
//當(dāng)fragment銷毀的時(shí)候,開始監(jiān)控
refWatcher.watch(fragment);
}
};
所以,這里通過獲取Activity中的FragmentManager,通過registerFragmentLifecycleCallbacks來對于其管理的Fragment的生命周期進(jìn)行監(jiān)聽。當(dāng)Fragment執(zhí)行銷毀的時(shí)候,將其引用加入到監(jiān)控隊(duì)列。
到這里為止,就已經(jīng)將我們的Activity和Fragment通過refWatcher的watch進(jìn)行了監(jiān)控。
那么我們下一步分析,watch方法中又是如何監(jiān)控實(shí)例,并判斷其存在內(nèi)存泄漏的。
監(jiān)控
我們對于已經(jīng)銷毀的界面會通過refWatcher的watch方法來進(jìn)行監(jiān)控。
//RefWatcher.java
public void watch(Object watchedReference) {
//重載方法
watch(watchedReference, "");
}
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
//保證watch的對象不為空
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
//創(chuàng)建一個(gè)UUID
String key = UUID.randomUUID().toString();
//將UUID保存到set中
retainedKeys.add(key);
//創(chuàng)建一個(gè)弱引用,指向要檢測的對象。
//如果這個(gè)弱引用被回收,那么會將reference加入到queue隊(duì)列中
final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
//判斷reference是否被回收
ensureGoneAsync(watchStartNanoTime, reference);
}
這個(gè)里面主要執(zhí)行了3個(gè)操作
- 創(chuàng)建了UUID
- 將生成的UUID保存到retainedKeys隊(duì)列中。
- 創(chuàng)建一個(gè)弱引用,指定了對應(yīng)的引用隊(duì)列queue。
這里的retainedKeys隊(duì)列記錄了我們執(zhí)行了監(jiān)控的引用對象。而queue中會保存回收的引用。所以通過二者的對比,我們就可以找到內(nèi)存泄漏的引用了。
我們看一下ensureGoneAsync中是如何執(zhí)行這個(gè)操作過程的。
//RefWatcher.java
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override
public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
這里的watcheExecute使用的是AndroidWatchExecutor
//AndroidRefWatcherBuilder.java
@Override protected @NonNull WatchExecutor defaultWatchExecutor() {
return new AndroidWatchExecutor(DEFAULT_WATCH_DELAY_MILLIS);
}
我們跟蹤一下execute方法。
//AndroidWatchExecutor.java
@Override public void execute(@NonNull Retryable retryable) {
//如果當(dāng)前線程是主線程,則直接執(zhí)行waitForIdl
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
//如果不是主線程,則通過Handler機(jī)制,將waitForIdle放入到主線程去執(zhí)行
postWaitForIdle(retryable, 0);
}
}
private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
//通過Handler機(jī)制,將waitForIdle發(fā)送到主線程執(zhí)行
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}
private void waitForIdle(final Retryable retryable, final int failedAttempts) {
//當(dāng)messagequeue閑置時(shí),增加一個(gè)處理。這種方法主要是為了提升性能,不會影響我們正常的應(yīng)用流暢度
//這個(gè)方法會在主線程執(zhí)行,所以postToBackgroundWithDelay會在主線程執(zhí)行
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
所以這里最終都會在主線程中執(zhí)行postToBackgroundWithDelay方法。
private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
//計(jì)算補(bǔ)償因子。如果返回了重試的話,這個(gè)failedAttempts回增加,會使得方法的執(zhí)行時(shí)間延遲時(shí)間增加。
//比如說第一次,演示5秒執(zhí)行,但是執(zhí)行結(jié)果為RETRY,那么下一次就是延遲10秒來執(zhí)行了
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
//計(jì)算延遲時(shí)間
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
//backgroundHandler會將run方法中的代碼放在一個(gè)新的線程中去執(zhí)行。
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
Retryable.Result result = retryable.run();
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
這個(gè)方法的執(zhí)行,會根據(jù)執(zhí)行的次數(shù)進(jìn)行來延遲執(zhí)行對應(yīng)的run方法。
我們看一下retryable.run()方法的執(zhí)行。也就回到了我們的RefWatcher中的ensureGoneAsync方法。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
這里的ensureGone方法屬于我們最核心的代碼了。
//判斷reference是否被回收
@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);
//移除已經(jīng)回收的監(jiān)控對象
removeWeaklyReachableReferences();
//如果當(dāng)前是debug狀態(tài),則直接返回retry
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
//監(jiān)控對象已經(jīng)回收了,直接返回Done
if (gone(reference)) {
return DONE;
}
//執(zhí)行一次垃圾回收
gcTrigger.runGc();
//再次移除已經(jīng)回收的監(jiān)控對象
removeWeaklyReachableReferences();
if (!gone(reference)) {
//如果仍然沒有回收,證明發(fā)生了內(nèi)存泄漏
long startDumpHeap = System.nanoTime();
//gc執(zhí)行的時(shí)長
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//dump出hprof文件
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
//不能生成快照文件的話,進(jìn)行重試
return RETRY;
}
//生成hprof文件消耗的的時(shí)間
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();
//分析堆內(nèi)存,heapdumpListener默認(rèn)是ServiceHeapDumpListener
heapdumpListener.analyze(heapDump);
}
return DONE;
}
這段代碼執(zhí)行了幾個(gè)過程
- 移除已經(jīng)回收的監(jiān)控對象
- 如果當(dāng)前監(jiān)控的對象已經(jīng)回收了,直接返回DONE。
- 如果沒有回收,則強(qiáng)行執(zhí)行一次GC操作。
- 再次移除已經(jīng)回收的監(jiān)控對象。
- 如果當(dāng)前監(jiān)控對象仍然沒有回收,則dump出hprof文件,然后根據(jù)快照文件進(jìn)行內(nèi)存泄漏情況的分析。
這里我們對每個(gè)方法都一一的進(jìn)行一次分析
移除已回收的弱引用對象
private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
//循環(huán)queue
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
//在queue中的ref,說明已經(jīng)被回收了,所以直接將其對應(yīng)的key從retainedKeys移除。
retainedKeys.remove(ref.key);
}
}
這里的queue是我們提到的引用隊(duì)列,而retainedKeys中則保存著我們要監(jiān)控的對象。當(dāng)對象被回收以后,就會將對應(yīng)的弱引用信息保存到queue中,所以我們將queue中的相關(guān)弱引用信息從retainedKeys移除。剩下的就是我們在監(jiān)聽或者已經(jīng)發(fā)生內(nèi)存泄漏的對象了。
判斷監(jiān)控對象是否回收
//判斷監(jiān)控的對象是否已經(jīng)回收 true:已經(jīng)回收
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
在上一步中,我們已經(jīng)將回收的引用信息從retainedKeys中移除了,所以這里只要通過判斷這個(gè)set中是否有我們監(jiān)控的這個(gè)類即可。
導(dǎo)出.hprof文件
public File dumpHeap() {
//創(chuàng)建一個(gè).hrof文件
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
if (heapDumpFile == RETRY_LATER) {
//創(chuàng)建失敗了,等會再重試
return RETRY_LATER;
}
FutureResult<Toast> waitingForToast = new FutureResult<>();
//通過Handler機(jī)制在主線程顯示Toast,使用了CountDownLatch機(jī)制。顯示Toast的時(shí)候會將其數(shù)值修改為0,
showToast(waitingForToast);
//這里會等待主線程顯示Toast,也就是CountDownLatch變?yōu)?。然后就可以繼續(xù)后面的操作
if (!waitingForToast.wait(5, SECONDS)) {
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}
//創(chuàng)建一個(gè)Notification通知
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 {
//創(chuàng)建heap堆的快照信息,可以獲知程序的哪些部分正在使用大部分的內(nèi)存
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
//關(guān)閉Toask和Notification通知
cancelToast(toast);
notificationManager.cancel(notificationId);
return heapDumpFile;
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;
}
}
這里會創(chuàng)建一個(gè).hprof文件,然后顯示一個(gè)Toast和Notification通知,再將內(nèi)存泄漏時(shí)候的堆的快照信息保存的.hprof文件中,最后將Toast和Notification通知關(guān)閉。所以執(zhí)行完這個(gè)操作之后,我們生成的.hprof文件中就保存了對應(yīng)的內(nèi)存泄漏時(shí)的堆的相關(guān)信息了。
快照文件分析
當(dāng)生成了文件以后,會通過heapdumpListener來分析生成的快照文件。這里的listener默認(rèn)的是ServiceHeapDumpListener類
//AndroidRefWatcherBuilder.java
@Override protected @NonNull HeapDump.Listener defaultHeapDumpListener() {
return new ServiceHeapDumpListener(context, DisplayLeakService.class);
}
我們看一下它的analyze方法
//ServiceHeapDumpListener.java
public void analyze(@NonNull HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
//HeapAnalyzerService.java
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);
//這里的listenerServiceClass是DisplayLeakService
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
//啟動一個(gè)前臺的服務(wù),啟動時(shí),會調(diào)用onHandleIntent方法,該方法在父類中實(shí)現(xiàn)了。實(shí)現(xiàn)中會調(diào)用onHandleIntentInForeground()方法
ContextCompat.startForegroundService(context, intent);
}
這里啟動了一個(gè)服務(wù)來進(jìn)行對于文件的分析功能。當(dāng)啟動服務(wù)的時(shí)候會調(diào)用onHandleIntent方法。HeapAnalyzerService的onHandleIntent是在其父類中實(shí)現(xiàn)的。
//ForegroundService.java
@Override protected void onHandleIntent(@Nullable Intent intent) {
onHandleIntentInForeground(intent);
}
所以會調(diào)用onHandleIntentInForeground這個(gè)方法。
protected void onHandleIntentInForeground(@Nullable Intent intent) {
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
//創(chuàng)建一個(gè)堆分析器
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
//**重點(diǎn)分析方法***分析內(nèi)存泄漏結(jié)果
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey, heapDump.computeRetainedHeapSize);
//調(diào)用接口,將結(jié)果回調(diào)給listenerClassName所對應(yīng)的類(這里是DisplayLeakService類)來進(jìn)行處理
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
這里會創(chuàng)建一個(gè)堆分析器,對于我們的快照文件進(jìn)行分析,然后將結(jié)果通過AbstractAnalysisResultService的方法,將結(jié)果交給DisplayLeakService類來進(jìn)行處理。
檢測泄漏結(jié)果
HeapAnalyzer類的作用主要就是通過對.hprof文件的分析,檢測我們監(jiān)控的對象是否發(fā)生了內(nèi)存的泄漏
//HeapAnalyzer.java
//將hprof文件解析,解析為對應(yīng)的AnalysisResult對象
public @NonNull AnalysisResult checkForLeak(@NonNull File heapDumpFile, @NonNull 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 {
//開始讀取Dump文件
listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
//.hprof的解析器,這個(gè)是haha庫的類
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);
//根據(jù)key值,查找快照中是否有所需要的對象
Instance leakingRef = findLeakingReference(referenceKey, snapshot);
if (leakingRef == null) {
//表示對象不存在,在gc的時(shí)候,進(jìn)行了回收。表示沒有內(nèi)存泄漏
String className = leakingRef.getClassObj().getClassName();
return noLeak(className, since(analysisStartNanoTime));
}
//檢測泄漏的路徑,并將檢測的結(jié)果進(jìn)行返回
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
這個(gè)方法使用了haha三方類庫來對.hprof文件解析以及處理。里面的主要流程如下:
- 創(chuàng)建一個(gè).hprof文件的buffer來進(jìn)行文件的讀取
- 通過HprofParser解析器來解析hprof文件,生成Snapshot對象。在這一步中構(gòu)建了一顆對象的引用關(guān)系樹,我們可以在這顆樹中查詢各個(gè)Object的信息,包括Class信息、內(nèi)存地址、持有的引用以及被持有引用的關(guān)系。
- 根據(jù)傳入的監(jiān)控的對象key值,獲取其在Snapshot中所對應(yīng)的引用leakingRef。
- 分析leakingRef,獲取到內(nèi)存泄漏的路徑。這里會找到一條到泄漏對象的最短引用路徑。這個(gè)過程由findLeakTrace來完成,實(shí)際上尋找最短引用路徑的邏輯是封裝在PathsFromGCRootsComputerImpl類的getNextShortestPath和processCurrentReferrefs方法中
泄漏的通知
當(dāng)找到我們的內(nèi)存泄漏的路徑后,會調(diào)用AbstractAnalysisResultService.sendResultToListener將結(jié)果交給DisplayLeakService類來進(jìn)行處理。
//AbstractAnalysisResultService.java
public static void sendResultToListener(@NonNull Context context,
@NonNull String listenerServiceClassName,
@NonNull HeapDump heapDump,
@NonNull AnalysisResult result) {
Class<?> listenerServiceClass;
try {
//通過反射獲取到一個(gè)類信息
listenerServiceClass = Class.forName(listenerServiceClassName);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
Intent intent = new Intent(context, listenerServiceClass);
//將結(jié)果保存到文件中,然后將文件路徑傳遞給service
File analyzedHeapFile = AnalyzedHeap.save(heapDump, result);
if (analyzedHeapFile != null) {
intent.putExtra(ANALYZED_HEAP_PATH_EXTRA, analyzedHeapFile.getAbsolutePath());
}
//啟動服務(wù),然后傳遞內(nèi)存泄漏分析的結(jié)果文件所對應(yīng)的位置
ContextCompat.startForegroundService(context, intent);
}
這里會啟動一個(gè)DisplayLeakService服務(wù),傳遞了對應(yīng)的內(nèi)存泄漏分析結(jié)果的文件路徑信息。
然后通過onHandleIntent()->onHandleIntentInForeground()->onHeapAnalyzed()。最終調(diào)用了DisplayLeakService的onHeapAnalyzed方法
protected final void onHeapAnalyzed(@NonNull AnalyzedHeap analyzedHeap) {
HeapDump heapDump = analyzedHeap.heapDump;
AnalysisResult result = analyzedHeap.result;
//根據(jù)泄漏的信息,生成提示的String字符串
String leakInfo = leakInfo(this, heapDump, result, true);
CanaryLog.d("%s", leakInfo);
//重命名.hprof文件
heapDump = renameHeapdump(heapDump);
//保存分析的結(jié)果
boolean resultSaved = saveResult(heapDump, result);
//結(jié)果表頭
String contentTitle;
if (resultSaved) {
PendingIntent pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
if (result.failure != null) {
//分析失敗
contentTitle = getString(R.string.leak_canary_analysis_failed);
} else {
String className = classSimpleName(result.className);
if (result.leakFound) {//檢測到內(nèi)存泄漏
if (result.retainedHeapSize == AnalysisResult.RETAINED_HEAP_SKIPPED) {
if (result.excludedLeak) {//被排除的檢測結(jié)果
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);
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 {
//未檢測到內(nèi)存泄漏
contentTitle = getString(R.string.leak_canary_class_no_leak, className);
}
}
String contentText = getString(R.string.leak_canary_notification_message);
//***重點(diǎn)方法***顯示一個(gè)Notification通知
showNotification(pendingIntent, contentTitle, contentText);
} else {
onAnalysisResultFailure(getString(R.string.leak_canary_could_not_save_text));
}
//鉤子函數(shù),可以重寫此方法,將內(nèi)存的泄露信息和對應(yīng)的.hprof文件上傳到服務(wù)器。
// 需要注意,leakfind和excludedLeak的情況都會調(diào)用這個(gè)方法
afterDefaultHandling(heapDump, result, leakInfo);
}
這個(gè)服務(wù)的作用就是將我們分析之后的泄漏路徑的相關(guān)信息通過Notification的通知形式,告知用戶具體的內(nèi)存泄漏情況。
在程序的最后有一個(gè)afterDefaultHandling方法,這個(gè)方法是一個(gè)空實(shí)現(xiàn),用戶可以覆寫這個(gè)方法來實(shí)現(xiàn)將內(nèi)存泄漏的信息上傳到服務(wù)器的功能
到這里為止LeakCanary的整個(gè)實(shí)現(xiàn)流程解析完成了。
學(xué)習(xí)到的新知識
整篇的學(xué)習(xí),還是學(xué)到了一些之前沒有認(rèn)識到的東西的。
- 主要是通過registerActivityLifecycleCallbacks來注冊對于我們銷毀的Activity的監(jiān)聽。
- 使用了弱引用的引用隊(duì)列方式對于我們已經(jīng)銷毀的Activity的引用信息進(jìn)行監(jiān)控,檢測其是否被回收。
- 對于執(zhí)行垃圾回收需要使用Runtime.getRuntime().gc()。
- 可以使用CountDownLatch來實(shí)現(xiàn)線程之間的同步處理。比如說這套源碼里面對于showToast的處理。
- 不同的Android版本本身可能就存在一些內(nèi)存泄漏的情況。
- LeakCanary可以通過覆寫afterDefaultHandling方法來實(shí)現(xiàn)對于內(nèi)存泄漏信息的自行處理
源碼解析項(xiàng)目地址:leakcanary-source
本文由 開了肯 發(fā)布!
同步公眾號[開了肯]