LeakCanary源碼解析
前言
對于內(nèi)存泄漏的檢測亮瓷,基于MAT起點較高,所以一般我們都使用LeakCanary來作為我們的內(nèi)存泄漏檢測工具來使用朴摊。
基礎(chǔ)知識
四種引用
LeakCanary主要是基于弱引用來進行對于已經(jīng)銷毀的Activity和Fragment的回收監(jiān)控來實現(xiàn)的句占。
強引用:無論如何都不會回收。
軟引用:內(nèi)存足夠不回收诱篷。內(nèi)存不夠時,就會回收雳灵。
弱引用:垃圾回收時直接回收棕所,則直接回收。
虛引用:垃圾回收時直接回收悯辙。
引用隊列(ReferenceQueue)琳省。
軟引用和弱引用都可以關(guān)聯(lián)一個引用隊列。當引用的對象被回收以后躲撰,會將軟引用加入到與之關(guān)聯(lián)的引用隊列中针贬。LeakCanary的基礎(chǔ)實現(xiàn)就是將已經(jīng)銷毀的Activity和Fragment所對應(yīng)的實例放入到弱引用中,并關(guān)聯(lián)一個引用隊列拢蛋。如果實例進行了回收桦他,那么弱引用就會放入到ReferenceQueue中,如果一段時間后谆棱,所監(jiān)控的實例還未在ReferenceQueue中出現(xiàn)快压,那么可以證明出現(xiàn)了內(nèi)存泄漏導(dǎo)致了實例沒有被回收圆仔。
使用方法
配置:
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原理解析
從程序的唯一入口來進行分析。本文是基于1.6.3版本來進行源碼解析的蔫劣。對應(yīng)的解析源碼地址為leakcanary-source坪郭。
注冊實例的監(jiān)控
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application)//創(chuàng)建一個Android端使用的引用監(jiān)控的構(gòu)造者
.listenerServiceClass(DisplayLeakService.class)
//設(shè)置不進行監(jiān)控的類引用對象
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
//創(chuàng)建對于引用的監(jiān)控
.buildAndInstall();
}
這個方法比較簡短,一個個進行解析吧脉幢。
構(gòu)造一個AndroidRefWatcherBuilder對象
//創(chuàng)建一個AndroidRefWatcherBuilder對象
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
這里創(chuàng)建的AndroidRefWatcherBuilder對象是一個適用于Android端的引用監(jiān)控的構(gòu)造者截粗。
設(shè)置后臺的監(jiān)聽類
//AndroidRefWatcherBuilder.java
//設(shè)置一個類用來監(jiān)聽分析的結(jié)果。
public @NonNull AndroidRefWatcherBuilder listenerServiceClass(@NonNull Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
enableDisplayLeakActivity = DisplayLeakService.class.isAssignableFrom(listenerServiceClass);
//設(shè)置一個監(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)存泄漏的分析者鸵隧,并且該類能夠進行內(nèi)存泄漏消息的通知(一般是Notification)绸罗。
不納入監(jiān)控的引用
excludedRefs方法能夠?qū)⒁恍┪覀儾魂P(guān)心的引用排除在我們的監(jiān)控范圍以外。這里這么處理豆瘫,主要是因為一些系統(tǒng)級別的引用問題珊蟀。我們可以具體看一下里面有哪些東西是我們不需要關(guān)注的。
//由于Android的AOSP本身可能會存在內(nèi)存泄漏的東西外驱,所以對于這些東西默認是不會進行提醒的育灸。
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;
}
這個可能會有一些難以理解磅崭,我們先簡單分析一下AndroidExcludedRefs這個類。
public enum AndroidExcludedRefs {
//參數(shù)瓦哎,標識是否需要執(zhí)行add方法
final boolean applies;
AndroidExcludedRefs() {
this(true);
}
AndroidExcludedRefs(boolean applies) {
this.applies = applies;
}
//枚舉類需要實現(xiàn)的方法
abstract void add(ExcludedRefs.Builder excluded);
}
AndroidExcludedRefs是一個枚舉類型砸喻。含有成員變量applies以及add()方法。
我們再分析一個具體的枚舉類型蒋譬。
//AndroidExcludedRefs.java
ACTIVITY_CLIENT_RECORD__NEXT_IDLE(SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP) {
@Override
void add(ExcludedRefs.Builder excluded) {
//設(shè)置排除的類中的某個屬性
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就是一個具體的枚舉類型割岛。applies賦值為SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP。也有add方法的具體實現(xiàn)犯助。實現(xiàn)中將需要排除的引用類型添加到了excluded中癣漆。
所以當我們的系統(tǒng)版本號滿足SDK_INT >= KITKAT && SDK_INT <= LOLLIPOP這個條件的時候,就會執(zhí)行add方法剂买。
AndroidExcludedRefs具有不同的枚舉實例惠爽,會根據(jù)不同的系統(tǒng)版本來進行不同的處理。這里其實主要是保證對于一些系統(tǒng)級別的內(nèi)存泄漏情況不再進行提示瞬哼。
創(chuàng)建引用的監(jiān)控
我們直接看看buildAndInstall中是如何對已經(jīng)執(zhí)行onDestroy的Activity進行監(jiān)控的婚肆。
//根據(jù)對應(yīng)的設(shè)置信息,返回一個RefWatcher對象
public @NonNull RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
//通過構(gòu)造者模式中的build()方法創(chuàng)建一個RefWatcher對象,這里面會有很多默認的設(shè)置
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
//如果允許顯示內(nèi)存泄漏Activity倒槐,則進行處理
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;
}
我們這里主要看一下如何進行Activity以及Fragment的監(jiān)聽的讨越。
- 對Activity的處理
//ActivityRefWatcher.java
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
//創(chuàng)建一個對于Activity的弱引用監(jiān)聽類
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
//對傳入的應(yīng)用的Application注冊一個對于Activity的生命周期監(jiān)聽函數(shù)
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
這里創(chuàng)建了一個ActivityRefWatcher對象两残,然后將對于應(yīng)用,通過registerActivityLifecycleCallbacks注冊了一個監(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);
}
};
在這個監(jiān)聽方法中,只監(jiān)聽了Activity的onDestroy方法着逐。當Activity銷毀的時候崔赌,使用refWatcher來監(jiān)控其實例。
- 對Fragment的處理
//FragmentRefWatcher.java
public static void install(Context context, RefWatcher refWatcher) {
List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
//將實現(xiàn)了FragmentRefWatcher接口的兩個實現(xiàn)類加入到fragmentRefWatchers中
//兩個實現(xiàn)類耸别,一個是實現(xiàn)對于V4包下的Fragment的監(jiān)聽健芭,一個是對于當前包下Fragment的監(jiān)聽
if (SDK_INT >= O) {
//實現(xiàn)類AndroidOFragmentRefWatcher
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
}
try {
//實現(xiàn)類SupportFragmentRefWatcher用于監(jiān)聽V4包下面的Fragment
//這里使用反射,是因為SupportFragmentRefWatcher這個類在support-fragment這個module中秀姐。
//所以慈迈,如果我們沒有引入V4的話,其實這個類是可以不引入的省有。
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實例
Helper helper = new Helper(fragmentRefWatchers);
Application application = (Application) context.getApplicationContext();
//注冊Activity的生命周期回調(diào)
application.registerActivityLifecycleCallbacks(helper.activityLifecycleCallbacks);
}
由于我們經(jīng)常使用的Fragment包含兩種,一種是support包中的Fragment蠢沿,一種是標準的app包中的Fragment伸头。這里對這兩種都進行了處理。
我們看一下對于注冊的生命周期函數(shù)
private final Application.ActivityLifecycleCallbacks activityLifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override
public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
//這里會調(diào)用具體的實現(xiàn)類的watchFragments方法舷蟀。這里關(guān)心的是綁定的Activity的onCreate方法恤磷。走到這里的時候已經(jīng)創(chuàng)建了對應(yīng)FragmentManager對象
//而通過FragmentManager對象可以來registerFragmentLifecycleCallbacks來創(chuàng)建對于其管理的Fragment的生命周期監(jiān)聽
watcher.watchFragments(activity);
}
}
};
這里我們同樣是注冊了Activity的生命周期回調(diào)。但是這里監(jiān)控的是onActivityCreated方法野宜。我們這里看一下watchFragments的實現(xiàn)碗殷。
具體的實現(xiàn)有兩個類,一個是SupportFragmentRefWatcher速缨,一個是AndroidOFragmentRefWatcher锌妻。我們這里只分析第一個。剩下的另一個是類似的旬牲,只是因為使用的Fragment不同仿粹,而有所區(qū)別。
public void watchFragments(Activity activity) {
//V4包中的Fragment原茅,必須使用FragmentActivity來進行處理
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) {
//當fragment的view銷毀的時候吭历,開始監(jiān)控
refWatcher.watch(view);
}
}
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
//當fragment銷毀的時候,開始監(jiān)控
refWatcher.watch(fragment);
}
};
所以擂橘,這里通過獲取Activity中的FragmentManager晌区,通過registerFragmentLifecycleCallbacks來對于其管理的Fragment的生命周期進行監(jiān)聽。當Fragment執(zhí)行銷毀的時候,將其引用加入到監(jiān)控隊列朗若。
到這里為止恼五,就已經(jīng)將我們的Activity和Fragment通過refWatcher的watch進行了監(jiān)控。
那么我們下一步分析哭懈,watch方法中又是如何監(jiān)控實例灾馒,并判斷其存在內(nèi)存泄漏的。
監(jiān)控
我們對于已經(jīng)銷毀的界面會通過refWatcher的watch方法來進行監(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)建一個UUID
String key = UUID.randomUUID().toString();
//將UUID保存到set中
retainedKeys.add(key);
//創(chuàng)建一個弱引用睬罗,指向要檢測的對象。
//如果這個弱引用被回收旭斥,那么會將reference加入到queue隊列中
final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
//判斷reference是否被回收
ensureGoneAsync(watchStartNanoTime, reference);
}
這個里面主要執(zhí)行了3個操作
- 創(chuàng)建了UUID
- 將生成的UUID保存到retainedKeys隊列中容达。
- 創(chuàng)建一個弱引用,指定了對應(yīng)的引用隊列queue垂券。
這里的retainedKeys隊列記錄了我們執(zhí)行了監(jiān)控的引用對象花盐。而queue中會保存回收的引用。所以通過二者的對比圆米,我們就可以找到內(nèi)存泄漏的引用了卒暂。
我們看一下ensureGoneAsync中是如何執(zhí)行這個操作過程的。
//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) {
//如果當前線程是主線程也祠,則直接執(zhí)行waitForIdl
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
//如果不是主線程,則通過Handler機制近速,將waitForIdle放入到主線程去執(zhí)行
postWaitForIdle(retryable, 0);
}
}
private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
//通過Handler機制诈嘿,將waitForIdle發(fā)送到主線程執(zhí)行
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}
private void waitForIdle(final Retryable retryable, final int failedAttempts) {
//當messagequeue閑置時,增加一個處理削葱。這種方法主要是為了提升性能奖亚,不會影響我們正常的應(yīng)用流暢度
//這個方法會在主線程執(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) {
//計算補償因子昔字。如果返回了重試的話,這個failedAttempts回增加首繁,會使得方法的執(zhí)行時間延遲時間增加作郭。
//比如說第一次,演示5秒執(zhí)行弦疮,但是執(zhí)行結(jié)果為RETRY夹攒,那么下一次就是延遲10秒來執(zhí)行了
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
//計算延遲時間
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
//backgroundHandler會將run方法中的代碼放在一個新的線程中去執(zhí)行。
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
Retryable.Result result = retryable.run();
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
這個方法的執(zhí)行胁塞,會根據(jù)執(zhí)行的次數(shù)進行來延遲執(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();
//如果當前是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í)行的時長
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
//dump出hprof文件
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
//不能生成快照文件的話斥季,進行重試
return RETRY;
}
//生成hprof文件消耗的的時間
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默認是ServiceHeapDumpListener
heapdumpListener.analyze(heapDump);
}
return DONE;
}
這段代碼執(zhí)行了幾個過程
- 移除已經(jīng)回收的監(jiān)控對象
- 如果當前監(jiān)控的對象已經(jīng)回收了累驮,直接返回DONE。
- 如果沒有回收舵揭,則強行執(zhí)行一次GC操作谤专。
- 再次移除已經(jīng)回收的監(jiān)控對象。
- 如果當前監(jiān)控對象仍然沒有回收午绳,則dump出hprof文件置侍,然后根據(jù)快照文件進行內(nèi)存泄漏情況的分析。
這里我們對每個方法都一一的進行一次分析
移除已回收的弱引用對象
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是我們提到的引用隊列赎败,而retainedKeys中則保存著我們要監(jiān)控的對象秕衙。當對象被回收以后,就會將對應(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中移除了勇吊,所以這里只要通過判斷這個set中是否有我們監(jiān)控的這個類即可。
導(dǎo)出.hprof文件
public File dumpHeap() {
//創(chuàng)建一個.hrof文件
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
if (heapDumpFile == RETRY_LATER) {
//創(chuàng)建失敗了窍仰,等會再重試
return RETRY_LATER;
}
FutureResult<Toast> waitingForToast = new FutureResult<>();
//通過Handler機制在主線程顯示Toast汉规,使用了CountDownLatch機制。顯示Toast的時候會將其數(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)建一個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)建一個.hprof文件悟民,然后顯示一個Toast和Notification通知,再將內(nèi)存泄漏時候的堆的快照信息保存的.hprof文件中篷就,最后將Toast和Notification通知關(guān)閉射亏。所以執(zhí)行完這個操作之后近忙,我們生成的.hprof文件中就保存了對應(yīng)的內(nèi)存泄漏時的堆的相關(guān)信息了。
快照文件分析
當生成了文件以后智润,會通過heapdumpListener來分析生成的快照文件及舍。這里的listener默認的是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);
//啟動一個前臺的服務(wù),啟動時窟绷,會調(diào)用onHandleIntent方法锯玛,該方法在父類中實現(xiàn)了。實現(xiàn)中會調(diào)用onHandleIntentInForeground()方法
ContextCompat.startForegroundService(context, intent);
}
這里啟動了一個服務(wù)來進行對于文件的分析功能兼蜈。當啟動服務(wù)的時候會調(diào)用onHandleIntent方法攘残。HeapAnalyzerService的onHandleIntent是在其父類中實現(xiàn)的。
//ForegroundService.java
@Override protected void onHandleIntent(@Nullable Intent intent) {
onHandleIntentInForeground(intent);
}
所以會調(diào)用onHandleIntentInForeground這個方法为狸。
protected void onHandleIntentInForeground(@Nullable Intent intent) {
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
//創(chuàng)建一個堆分析器
HeapAnalyzer heapAnalyzer = new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
//**重點分析方法***分析內(nèi)存泄漏結(jié)果
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey, heapDump.computeRetainedHeapSize);
//調(diào)用接口歼郭,將結(jié)果回調(diào)給listenerClassName所對應(yīng)的類(這里是DisplayLeakService類)來進行處理
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
這里會創(chuàng)建一個堆分析器,對于我們的快照文件進行分析辐棒,然后將結(jié)果通過AbstractAnalysisResultService的方法病曾,將結(jié)果交給DisplayLeakService類來進行處理。
檢測泄漏結(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的解析器,這個是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的時候,進行了回收秘蛇。表示沒有內(nèi)存泄漏
String className = leakingRef.getClassObj().getClassName();
return noLeak(className, since(analysisStartNanoTime));
}
//檢測泄漏的路徑其做,并將檢測的結(jié)果進行返回
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
這個方法使用了haha三方類庫來對.hprof文件解析以及處理。里面的主要流程如下:
- 創(chuàng)建一個.hprof文件的buffer來進行文件的讀取
- 通過HprofParser解析器來解析hprof文件赁还,生成Snapshot對象妖泄。在這一步中構(gòu)建了一顆對象的引用關(guān)系樹,我們可以在這顆樹中查詢各個Object的信息艘策,包括Class信息蹈胡、內(nèi)存地址、持有的引用以及被持有引用的關(guān)系朋蔫。
- 根據(jù)傳入的監(jiān)控的對象key值罚渐,獲取其在Snapshot中所對應(yīng)的引用leakingRef。
- 分析leakingRef驯妄,獲取到內(nèi)存泄漏的路徑荷并。這里會找到一條到泄漏對象的最短引用路徑。這個過程由findLeakTrace來完成青扔,實際上尋找最短引用路徑的邏輯是封裝在PathsFromGCRootsComputerImpl類的getNextShortestPath和processCurrentReferrefs方法中
泄漏的通知
當找到我們的內(nèi)存泄漏的路徑后源织,會調(diào)用AbstractAnalysisResultService.sendResultToListener將結(jié)果交給DisplayLeakService類來進行處理翩伪。
//AbstractAnalysisResultService.java
public static void sendResultToListener(@NonNull Context context,
@NonNull String listenerServiceClassName,
@NonNull HeapDump heapDump,
@NonNull AnalysisResult result) {
Class<?> listenerServiceClass;
try {
//通過反射獲取到一個類信息
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);
}
這里會啟動一個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);
//***重點方法***顯示一個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)用這個方法
afterDefaultHandling(heapDump, result, leakInfo);
}
這個服務(wù)的作用就是將我們分析之后的泄漏路徑的相關(guān)信息通過Notification的通知形式嗅骄,告知用戶具體的內(nèi)存泄漏情況胳挎。
在程序的最后有一個afterDefaultHandling方法饼疙,這個方法是一個空實現(xiàn)溺森,用戶可以覆寫這個方法來實現(xiàn)將內(nèi)存泄漏的信息上傳到服務(wù)器的功能
到這里為止LeakCanary的整個實現(xiàn)流程解析完成了。
學(xué)習(xí)到的新知識
整篇的學(xué)習(xí)窑眯,還是學(xué)到了一些之前沒有認識到的東西的屏积。
- 主要是通過registerActivityLifecycleCallbacks來注冊對于我們銷毀的Activity的監(jiān)聽。
- 使用了弱引用的引用隊列方式對于我們已經(jīng)銷毀的Activity的引用信息進行監(jiān)控磅甩,檢測其是否被回收炊林。
- 對于執(zhí)行垃圾回收需要使用Runtime.getRuntime().gc()。
- 可以使用CountDownLatch來實現(xiàn)線程之間的同步處理卷要。比如說這套源碼里面對于showToast的處理渣聚。
- 不同的Android版本本身可能就存在一些內(nèi)存泄漏的情況。
- LeakCanary可以通過覆寫afterDefaultHandling方法來實現(xiàn)對于內(nèi)存泄漏信息的自行處理
源碼解析項目地址:leakcanary-source
本文由 開了肯 發(fā)布僧叉!
同步公眾號[開了肯]