一驾霜、 什么是LeakCanary
LeakCanary 是大名鼎鼎的 square 公司開源的內(nèi)存泄漏檢測(cè)工具。目前上大部分App在開發(fā)測(cè)試階段都會(huì)接入此工具用于檢測(cè)潛在的內(nèi)存泄漏問題饺汹,做的好一點(diǎn)的可能會(huì)搭建一個(gè)服務(wù)器用于保存各個(gè)設(shè)備上的內(nèi)存泄漏問題再集中處理。
本文首發(fā)于我的微信公眾號(hào):Android開發(fā)實(shí)驗(yàn)室做个,歡迎大家關(guān)注和我一起學(xué)Android淤井,掉節(jié)操。
二工猜、 為什么要使用LeakCanary
我們知道內(nèi)存泄漏問題的排查有很多種方法米诉, 比如說,Android Studio 自帶的 Profile 工具篷帅、MAT(Memory Analyzer Tool)史侣、以及LeakCanary。 選擇 LeakCanary 作為首選的內(nèi)存泄漏檢測(cè)工具主要是因?yàn)樗軐?shí)時(shí)檢測(cè)泄漏并以非常直觀的調(diào)用鏈方式展示內(nèi)存泄漏的原因魏身。
三惊橱、 LeakCanary 做不到的(待定)
雖然 LeakCanary 有諸多優(yōu)點(diǎn),但是它也有做不到的地方箭昵,比如說檢測(cè)申請(qǐng)大容量?jī)?nèi)存導(dǎo)致的OOM問題税朴、Bitmap內(nèi)存未釋放問題,Service 中的內(nèi)存泄漏可能無(wú)法檢測(cè)等家制。
四正林、 LeakCanary 源碼解析
本章內(nèi)容前后依賴關(guān)系強(qiáng)烈,建議順序閱讀颤殴。
4.1 ActivityLifecycleCallbacks 與 FragmentLifeCycleCallbacks
在開始 LeakCanary 原理解析之前觅廓,有必要簡(jiǎn)單說下 ActivityLifecycleCallbacks 與 FragmentLifeCycleCallbacks。
// ActivityLifecycleCallbacks 接口
public interface ActivityLifecycleCallbacks {
void onActivityCreated(Activity var1, Bundle var2);
void onActivityStarted(Activity var1);
void onActivityResumed(Activity var1);
void onActivityPaused(Activity var1);
void onActivityStopped(Activity var1);
void onActivitySaveInstanceState(Activity var1, Bundle var2);
void onActivityDestroyed(Activity var1);
}
Application 類提供了 registerActivityLifecycleCallbacks
和 unregisterActivityLifecycleCallbacks
方法用于注冊(cè)和反注冊(cè) Activity 的生命周期監(jiān)聽類涵但,這樣我們就能在 Application 中對(duì)所有的 Activity 生命周期回調(diào)中做一些統(tǒng)一處理杈绸。
public abstract static class FragmentLifecycleCallbacks {
public void onFragmentCreated(FragmentManager fm, Fragment f, Bundle savedInstanceState) {}
public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {}
public void onFragmentDestroyed(FragmentManager fm, Fragment f) {}
// 省略其他的生命周期 ...
}
FragmentManager
類提供了 registerFragmentLifecycleCallbacks
和 unregisterFragmentLifecycleCallbacks
方法用戶注冊(cè)和反注冊(cè) Fragment 的生命周期監(jiān)聽類,這樣我們對(duì)每一個(gè) Activity 進(jìn)行注冊(cè)贤笆,就能獲取所有的 Fragment 生命周期回調(diào)蝇棉。
4.2 LeakCanary 的使用
4.2.1 使用方法
我們直接在 Application 類中讨阻,添加一下代碼即可芥永。
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
setupLeakCanary();
}
protected void setupLeakCanary() {
// 啟用嚴(yán)格模式
enabledStrictMode();
// 判斷是否是 HeapAnalyzerService 所屬進(jìn)程
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
// 注冊(cè) LeakCanary
LeakCanary.install(this);
}
private static void enabledStrictMode() {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() //
.detectAll() //
.penaltyLog() //
.penaltyDeath() //
.build());
}
}
<service
android:name=".internal.HeapAnalyzerService"
android:process=":leakcanary"
android:enabled="false"
/>
由于 LeakCanary 的核心 hropf 文件解析服務(wù) HeapAnalyzerService 所屬進(jìn)程是與主進(jìn)程獨(dú)立的一個(gè)進(jìn)程,所以在 setupLeakCanary
中钝吮,我們需要排除其他進(jìn)程埋涧,只對(duì) leakcanary
進(jìn)程注冊(cè) LeakCanary 監(jiān)聽處理板辽。
android:enabled="false" 這是什么?
這里簡(jiǎn)單說下棘催,AndroidManifest文件中的 enabled 屬性劲弦,可以看到 HeapAnalyzerService 這個(gè)組件默認(rèn)是不可用的,所以如果在代碼中動(dòng)態(tài)啟用這個(gè)組件醇坝,可以使用以下方法:
public static void setEnabledBlocking(Context appContext, Class<?> componentClass,
boolean enabled) {
ComponentName component = new ComponentName(appContext, componentClass);
PackageManager packageManager = appContext.getPackageManager();
int newState = enabled ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED;
// Blocks on IPC.
packageManager.setComponentEnabledSetting(component, newState, DONT_KILL_APP);
}
4.3 LeakCanary.install(this) 干了什么
LeakCanary 的 install 方法實(shí)際上構(gòu)造了一個(gè) RefWatcher,
/**
* Creates a {@link RefWatcher} that works out of the box, and starts watching activity
* references (on ICS+).
*/
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
我們一個(gè)個(gè)來看這個(gè)注冊(cè)方法邑跪。首先是 refWatcher 方法構(gòu)造了一個(gè) AndroidRefWatcherBuilder, 傳入?yún)?shù)是當(dāng)前Application 的 Context.
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
listenerServiceClass 和 excludedRefs 方法是基于建造者模式傳入分析Service 和 排除已知的泄漏問題 AndroidExcludedRefs,這里我就不貼代碼了呼猪。
重點(diǎn)看下 buildAndInstall 方法画畅,這個(gè)方法很形象的表示將要進(jìn)行建造者模式的最后一步 build 和 注冊(cè)一些監(jiān)聽器,下面我們來看具體代碼:
public @NonNull RefWatcher buildAndInstall() {
// 只允許 install 一次
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
// 建造者模式的最后一步宋距,構(gòu)造對(duì)象
RefWatcher refWatcher = build();
// 判斷是否開啟了 LeakCanary轴踱,沒有開啟默認(rèn)會(huì)返回 DISABLED 對(duì)象
if (refWatcher != DISABLED) {
// 手動(dòng)開啟 DisplayLeakActivity 組件,會(huì)在桌面上顯示一個(gè)查看內(nèi)存泄漏結(jié)果的入口
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
// 是否檢測(cè) Activity 的 內(nèi)存泄漏谚赎,默認(rèn)開啟
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
// 是否檢測(cè) Fragment 的 內(nèi)存泄漏淫僻,默認(rèn)開啟
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
// 復(fù)制給全局靜態(tài)變量,防止二次調(diào)用
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
以上代碼作用大部分都在代碼中注釋了壶唤,剩下 ActivityRefWatcher.install 和 FragmentRefWatcher.Helper.install 方法沒有注釋雳灵。下面我們就來具體看看這兩個(gè)方法究竟干了什么。
(1). ActivityRefWatcher.install
ActivityRefWatcher 的靜態(tài)方法 install 獲取到了當(dāng)前 Application闸盔,然后添加了一個(gè)生命周期監(jiān)聽器 ActivityLifecycleCallbacks细办,這里的 lifecycleCallbacks 僅僅關(guān)注了 Activity 銷毀的回調(diào) onActivityDestroyed,在這里將傳入的對(duì)象 activity 監(jiān)聽起來蕾殴, refWatcher.watch(activity);
的具體代碼我們稍后分析笑撞。
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
(2). FragmentRefWatcher.Helper.install
FragmentRefWatcher.Helper 的靜態(tài)方法 install 里同樣會(huì)注冊(cè)一個(gè) ActivityLifecycleCallbacks 用于監(jiān)聽 Activity 生命周期中的 onActivityCreated
的創(chuàng)建完成的回調(diào),在 Activity 創(chuàng)建完成后钓觉,會(huì)對(duì)這個(gè) Activity 注冊(cè) Fragment 的生命周期監(jiān)聽器茴肥。install 方法首先會(huì)判斷系統(tǒng)是否大于等于 Android O, 如果是那么會(huì)使用 android.app.FragmentManager
進(jìn)行注冊(cè)荡灾,如果需要兼容 Android O 以下需要自行在依賴中添加對(duì) leakcanary-support-fragment
組件的依賴瓤狐,然后通過反射構(gòu)造出SupportFragmentRefWatcher
; 然后將fragmentRefWatchers所有監(jiān)聽器取出,在 Activity 創(chuàng)建完成后批幌,添加 Fragment 的生命監(jiān)聽础锐,主要關(guān)注 Fragment 的 onFragmentViewDestroyed
和 onFragmentDestroyed
方法。具體代碼如下:
public static void install(Context context, RefWatcher refWatcher) {
List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
// 系統(tǒng)是否大于等于 Android O荧缘,如果是皆警,添加 AndroidOFragmentRefWatcher
if (SDK_INT >= O) {
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
}
// 如果添加了leakcanary-support-fragment的依賴,通過反射可以構(gòu)造SupportFragmentRefWatcher
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);
// 先監(jiān)聽 Activity 的創(chuàng)建完成回調(diào)
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 創(chuàng)建完成后截粗,對(duì)Activity中的Fragment注冊(cè)生命周期監(jiān)聽
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
watcher.watchFragments(activity);
}
}
};
// AndroidOFragmentRefWatcher.java
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
// Fragment 中的View 視圖銷毀時(shí)
@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
View view = fragment.getView();
if (view != null) {
refWatcher.watch(view);
}
}
// Fragment 銷毀時(shí)
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
refWatcher.watch(fragment);
}
};
@Override public void watchFragments(Activity activity) {
// 通過FragmentManager 注冊(cè) FragmentLifecycleCallbacks
FragmentManager fragmentManager = activity.getFragmentManager();
fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
上述流程我們已經(jīng)完全搞清楚了信姓,用一個(gè)流程圖可以表示為:
[圖片上傳失敗...(image-f5fe25-1543852247130)]
4.4 LeakCanary 內(nèi)存泄漏檢測(cè)原理
從行文結(jié)構(gòu)上鸵隧,本小節(jié)應(yīng)屬于上一節(jié)后半部分內(nèi)容,但是RefWatcher 的 watch 方法足夠重要和復(fù)雜意推,所以有必要單獨(dú)列一節(jié)仔細(xì)講解內(nèi)部原理豆瘫。
4.4.1 基礎(chǔ)知識(shí)——弱引用 WeakReference 和 引用隊(duì)列 ReferenceQueue
關(guān)于引用類型和引用隊(duì)列相關(guān)知識(shí),讀者可以參考白話 JVM——深入對(duì)象引用菊值,這篇文章我認(rèn)為講解的比較清晰外驱。
這里,我簡(jiǎn)單舉個(gè)例子腻窒,弱引用在定義的時(shí)候可以指定引用對(duì)象和一個(gè) ReferenceQueue略步,弱引用對(duì)象在垃圾回收器執(zhí)行回收方法時(shí),如果原對(duì)象只有這個(gè)弱引用對(duì)象引用著定页,那么會(huì)回收原對(duì)象趟薄,并將弱引用對(duì)象加入到 ReferenceQueue,通過 ReferenceQueue 的 poll 方法典徊,可以取出這個(gè)弱引用對(duì)象杭煎,獲取弱引用對(duì)象本身的一些信息∽渎洌看下面這個(gè)例子羡铲。
mReferenceQueue = new ReferenceQueue<>();
// 定義一個(gè)對(duì)象
o = new Object();
// 定義一個(gè)弱引用對(duì)象引用 o,并指定引用隊(duì)列為 mReferenceQueue
weakReference = new WeakReference<Object>(o, mReferenceQueue);
// 去掉強(qiáng)引用
o = null;
// 觸發(fā)應(yīng)用進(jìn)行垃圾回收
Runtime.getRuntime().gc();
// hack: 延時(shí)100ms,等待gc完成
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Reference ref = null;
// 遍歷 mReferenceQueue,取出所有弱引用
while ((ref = mReferenceQueue.poll()) != null) {
System.out.println("============ \n ref in queue");
}
打印結(jié)果為:
============
ref in queue
4.4.2 基礎(chǔ)知識(shí)——hprof文件
hprof 文件可以展示某一時(shí)刻java堆的使用情況儡毕,根據(jù)這個(gè)文件我們可以分析出哪些對(duì)象占用大量?jī)?nèi)存和未在合適時(shí)機(jī)釋放也切,從而定位內(nèi)存泄漏問題。
Android 生成 hprof 文件整體上有兩種方式:
- 使用 adb 命令
adb shell am dumpheap <processname> <FileName>
- 使用 android.os.Debug.dumpHprofData 方法
直接使用 Debug 類提供的 dumpHprofData 方法即可腰湾。
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
Android Studio 自帶 Android Profiler 的 Memory 模塊的 dump 操作使用的是方法一雷恃。這兩種方法生成的 .hprof 文件都是 Dalvik 格式,需要使用 AndroidSDK 提供的 hprof-conv 工具轉(zhuǎn)換成J2SE HPROF格式才能在MAT等標(biāo)準(zhǔn) hprof 工具中查看费坊。
hprof-conv dump.hprof converted-dump.hprof
至于hprof內(nèi)部格式如何倒槐,本文不做具體介紹,以后有機(jī)會(huì)再單獨(dú)寫一篇文章來仔細(xì)講解附井。LeakCanary 解析 .hprof 文件用的是 square 公司開源的另一項(xiàng)目:haha.
4.4.3 watch方法
終于到了 LeakCanary 關(guān)鍵部分了讨越。我們從 watch 方法入手,前面的代碼都是為了增強(qiáng)魯棒性永毅,我們直接從生成唯一id開始把跨,LeakCanary 構(gòu)造了一個(gè)帶有 key 的弱引用對(duì)象,并且將 queue 設(shè)置為弱引用對(duì)象的引用隊(duì)列沼死。
這里解釋一下着逐,為什么需要?jiǎng)?chuàng)建一個(gè)帶有 key 的弱引用對(duì)象,不能直接使用 WeakReference 么?
舉個(gè)例子滨嘱,假設(shè) OneActivity 發(fā)生了內(nèi)存泄漏峰鄙,那么執(zhí)行 GC 操作時(shí)浸间,肯定不會(huì)回收 Activity 對(duì)象太雨,這樣 WeakReference 對(duì)象也不會(huì)被回收。假設(shè)當(dāng)前啟動(dòng)了 N 個(gè) OneActivity魁蒜,Dump內(nèi)存時(shí)我們可以獲取到內(nèi)存中的所有 OneActivity囊扳,但是當(dāng)我們準(zhǔn)備去檢測(cè)其中某一個(gè) Activity 的泄漏問題時(shí),我們就無(wú)法匹配兜看。但是如果使用了帶有 key 的 WeakReference 對(duì)象锥咸,發(fā)生泄露時(shí)泄漏時(shí),key 的值也會(huì) dump 保存下來细移,這樣我們根據(jù) key 的一一對(duì)應(yīng)關(guān)系就能映射到某一個(gè) Activity搏予。
然后,LeakCanary 調(diào)用了 ensureGoneAsync 方法去檢測(cè)內(nèi)存泄漏弧轧。
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
// 對(duì)當(dāng)前監(jiān)視對(duì)象設(shè)置一個(gè)唯一 id
String key = UUID.randomUUID().toString();
// 添加到 Set<String> 中
retainedKeys.add(key);
// 構(gòu)造一個(gè)帶有id 的 WeakReference 對(duì)象
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
// 檢測(cè)對(duì)象是否被回收了
ensureGoneAsync(watchStartNanoTime, reference);
}
4.4.4 ensureGoneAsync 方法
ensureGoneAsync 方法構(gòu)造了一個(gè) Retryable 對(duì)象雪侥,并將它傳給 watchExecutor 的 execute 方法。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
// watchExecutor 是 AndroidWatchExecutor的一個(gè)實(shí)例
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
watchExecutor
是 AndroidWatchExecutor 的一個(gè)實(shí)例精绎, AndroidWatchExecutor 的 execute 方法的作用就是判斷當(dāng)前線程是否是主線程速缨,如果是主線程,那么直接執(zhí)行 waitForIdle 方法代乃,否則通過 Handler 的 post 方法切換到主線程再執(zhí)行 waitForIdle 方法旬牲。
@Override public void execute(@NonNull Retryable retryable) {
// 判斷當(dāng)前線程是否是主線程
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}
waitForIdle
方法通過調(diào)用 addIdleHandler
方法,指定當(dāng)主進(jìn)程中沒有需要處理的事件時(shí)搁吓,在這個(gè)空閑期間執(zhí)行 postToBackgroundWithDelay
方法原茅。
private void waitForIdle(final Retryable retryable, final int failedAttempts) {
// 由于上面的 execute 方法,已經(jīng)保證了此方法在主線程中執(zhí)行堕仔,所以Looper.myQueue()獲取的主線程的消息隊(duì)列
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
// return false 表示執(zhí)行完之后员咽,就立即移除這個(gè)事件
return false;
}
});
}
postToBackgroundWithDelay
方法首先會(huì)計(jì)算延遲時(shí)間 delayMillis
,這個(gè)延時(shí)是有 exponentialBackoffFactor(指數(shù)因子) 乘以初始延時(shí)時(shí)間得到的贮预, exponentialBackoffFactor(指數(shù)因子)會(huì)在2^n 和 Long.MAX_VALUE / initialDelayMillis 中取較小值贝室,也就說延遲時(shí)間delayMillis = initialDelayMillis * 2^n
,且不能超過 Long.MAX_VALUE仿吞。
private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
// 計(jì)算延遲時(shí)間
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
// 切換到子線程中執(zhí)行
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
// 執(zhí)行 retryable 里的 run 方法
Retryable.Result result = retryable.run();
// 如果需要重試滑频,那么再添加到主線程的空閑期間執(zhí)行
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
postToBackgroundWithDelay
方法每次執(zhí)行會(huì)指數(shù)級(jí)增加延時(shí)時(shí)間,延時(shí)時(shí)間到了后唤冈,會(huì)執(zhí)行 Retryable 里的方法峡迷,如果返回為重試,那么會(huì)增加延時(shí)時(shí)間并執(zhí)行下一次。
retryable.run()
的run 方法又執(zhí)行了什么呢绘搞?別忘了我們ensureGoneAsync
中的代碼彤避,一直在重試的代碼正式 ensureGone
方法。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
4.4.5 ensureGone 方法
我現(xiàn)在講ensureGone
方法的完整代碼貼出來夯辖,我們逐行分析:
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
// 前面不是有一個(gè)重試的機(jī)制么琉预,這里會(huì)計(jì)下這次重試距離第一次執(zhí)行花了多長(zhǎng)時(shí)間
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// 移除所有弱引用可達(dá)對(duì)象,后面細(xì)講
removeWeaklyReachableReferences();
// 判斷當(dāng)前是否正在開啟USB調(diào)試蒿褂,LeakCanary 的解釋是調(diào)試時(shí)可能會(huì)觸發(fā)不正確的內(nèi)存泄漏
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// 上面執(zhí)行 removeWeaklyReachableReferences 方法圆米,判斷是不是監(jiān)視對(duì)象已經(jīng)被回收了,如果被回收了啄栓,那么說明沒有發(fā)生內(nèi)存泄漏娄帖,直接結(jié)束
if (gone(reference)) {
return DONE;
}
// 手動(dòng)觸發(fā)一次 GC 垃圾回收
gcTrigger.runGc();
// 再次移除所有弱引用可達(dá)對(duì)象
removeWeaklyReachableReferences();
// 如果對(duì)象沒有被回收
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// 使用 Debug 類 dump 當(dāng)前堆內(nèi)存中對(duì)象使用情況
File heapDumpFile = heapDumper.dumpHeap();
// dumpHeap 失敗的話,會(huì)走重試機(jī)制
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
// 將hprof文件昙楚、key等屬性構(gòu)造一個(gè) HeapDump 對(duì)象
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
// heapdumpListener 分析 heapDump 對(duì)象
heapdumpListener.analyze(heapDump);
}
return DONE;
}
看完上述代碼近速,基本把檢測(cè)泄漏的大致過程走了一遍,下面我們來看一些具體的細(xì)節(jié)堪旧。
(1). removeWeaklyReachableReferences 方法
removeWeaklyReachableReferences
移除所有弱引用可達(dá)對(duì)象是怎么工作的削葱?
private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
還記得我們?cè)?refWatcher.watch 方法保存了當(dāng)前監(jiān)視對(duì)象的 ref.key 了么,如果這個(gè)對(duì)象被回收了崎场,那么對(duì)應(yīng)的弱引用對(duì)象會(huì)在回收時(shí)被添加到queue中佩耳,通過 poll 操作就可以取出這個(gè)弱引用,這時(shí)候我們從retainedKeys
中移除這個(gè) key谭跨, 代表這個(gè)對(duì)象已經(jīng)被正掣珊瘢回收,不需要再被監(jiān)視了螃宙。
那么現(xiàn)在來看蛮瞄,判斷這個(gè)對(duì)象是否被回收就比較簡(jiǎn)單了?
private boolean gone(KeyedWeakReference reference) {
// retainedKeys 中不包含 reference.key 的話谆扎,就代表這個(gè)對(duì)象已經(jīng)被回收了
return !retainedKeys.contains(reference.key);
}
(2). dumpHeap 方法
heapDumper.dumpHeap()
是執(zhí)行生成hprof的方法挂捅,heapDumper 是 AndroidHeapDumper 的一個(gè)對(duì)象,我們來具體看看它的 dump 方法堂湖。
public File dumpHeap() {
// 生成一個(gè)存儲(chǔ) hprof 的文件
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
// 文件創(chuàng)建失敗
if (heapDumpFile == RETRY_LATER) {
return RETRY_LATER;
}
// FutureResult 內(nèi)部有一個(gè) CountDownLatch闲先,用于倒計(jì)時(shí)
FutureResult<Toast> waitingForToast = new FutureResult<>();
// 切換到主線程顯示 toast
showToast(waitingForToast);
// 等待5秒,確保 toast 已完成顯示
if (!waitingForToast.wait(5, SECONDS)) {
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}
// 創(chuàng)建一個(gè)通知
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 內(nèi)存到指定文件
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;
}
}
這段代碼里我們需要看看 showToast()
方法无蜂,以及它是如何確保 toast 已完成顯示(有點(diǎn)黑科技的感覺)伺糠。
private void showToast(final FutureResult<Toast> waitingForToast) {
mainHandler.post(new Runnable() {
@Override public void run() {
// 當(dāng)前 Activity 已經(jīng) paused的話,直接返回
if (resumedActivity == null) {
waitingForToast.set(null);
return;
}
// 構(gòu)建一個(gè)toast 對(duì)象
final Toast toast = new Toast(resumedActivity);
toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
toast.setDuration(Toast.LENGTH_LONG);
LayoutInflater inflater = LayoutInflater.from(resumedActivity);
toast.setView(inflater.inflate(R.layout.leak_canary_heap_dump_toast, null));
// 將toast加入顯示隊(duì)列
toast.show();
// Waiting for Idle to make sure Toast gets rendered.
// 主線程中添加空閑時(shí)操作斥季,如果主線程是空閑的训桶,會(huì)將CountDownLatch執(zhí)行 countDown 操作
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
waitingForToast.set(toast);
return false;
}
});
}
});
}
首先我們需要知道所有的Toast對(duì)象累驮,并不是我們一調(diào)用 show 方法就立即顯示的。NotificationServiceManager
會(huì)從mToastQueue
中輪詢?nèi)コ齌oast對(duì)象進(jìn)行顯示舵揭。如果Toast的顯示不是實(shí)時(shí)的谤专,那么我們?nèi)绾沃繲oast是否已經(jīng)顯示完成了呢?我們?cè)?Toast 調(diào)用 show 方法后調(diào)用 addIdleHandler午绳, 在主進(jìn)程空閑時(shí)執(zhí)行 CountDownLatch 的減一操作置侍。由于我們知道我們順序加入到主線程的消息隊(duì)列中的操作:先顯示Toast,再執(zhí)行 CountDownLatch 減一操作箱叁。所以在 if (!waitingForToast.wait(5, SECONDS))
的判斷中墅垮,我們最多等待5秒惕医,如果超時(shí)會(huì)走重試機(jī)制耕漱,如果我們的 CountDownLatch 已經(jīng)執(zhí)行了減一操作,則會(huì)正常走后續(xù)流程抬伺,同時(shí)我們也能推理出它前面 toast 肯定已經(jīng)顯示完成了螟够。
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
是系統(tǒng)Debug類提供的方法,我就不做具體分析了峡钓。
(3). heapdumpListener.analyze(heapDump) 方法
heapdumpListener 是 ServiceHeapDumpListener 的一個(gè)對(duì)象妓笙,最終執(zhí)行了HeapAnalyzerService.runAnalysis
方法。
@Override public void analyze(@NonNull HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
// 啟動(dòng)前臺(tái)服務(wù)
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);
}
HeapAnalyzerService 繼承自 IntentService能岩,IntentService的具體原理我就不多做解釋了寞宫。IntentService會(huì)將所有并發(fā)的啟動(dòng)服務(wù)操作,變成順序執(zhí)行 onHandleIntent 方法拉鹃。
@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
// 監(jiān)聽 hprof 文件分析結(jié)果的類
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
// hprof 文件類
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
// checkForLeak 會(huì)調(diào)用 haha 組件中的工具辈赋,分析 hprof 文件
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
// 將分析結(jié)果發(fā)送給監(jiān)聽器 listenerClassName
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
我們來看下 checkForLeak
方法,我們一起來看下吧膏燕。
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 {
// 更新進(jìn)度回調(diào)
listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
// 將 hprof 文件解析成 Snapshot
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
listener.onProgressUpdate(PARSING_HEAP_DUMP);
Snapshot snapshot = parser.parse();
listener.onProgressUpdate(DEDUPLICATING_GC_ROOTS);
// 移除相同 GC root項(xiàng)
deduplicateGcRoots(snapshot);
listener.onProgressUpdate(FINDING_LEAKING_REF);
// 查找內(nèi)存泄漏項(xiàng)
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));
}
// 找到泄漏處的引用關(guān)系鏈
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
hprof 文件的解析是由開源項(xiàng)目 haha 完成的坝辫,我這里不做過多展開篷就。
findLeakingReference
方法是查找泄漏的引用處,我們看下代碼:
private Instance findLeakingReference(String key, Snapshot snapshot) {
// 從 hprof 文件保存的對(duì)象中找到所有 KeyedWeakReference 的實(shí)例
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<>();
// 對(duì) KeyedWeakReference 實(shí)例列表進(jìn)行遍歷
for (Instance instance : refClass.getInstancesList()) {
// 獲取每個(gè)實(shí)例里的所有字段
List<ClassInstance.FieldValue> values = classInstanceValues(instance);
// 找到 key 字段對(duì)應(yīng)的值
Object keyFieldValue = fieldValue(values, "key");
if (keyFieldValue == null) {
keysFound.add(null);
continue;
}
// 將 keyFieldValue 轉(zhuǎn)為 String 對(duì)象
String keyCandidate = asString(keyFieldValue);
// 如果這個(gè)對(duì)象的 key 和 我們查找的 key 相同近忙,那么返回這個(gè)弱對(duì)象持有的原對(duì)象
if (keyCandidate.equals(key)) {
return fieldValue(values, "referent");
}
keysFound.add(keyCandidate);
}
throw new IllegalStateException(
"Could not find weak reference with key " + key + " in " + keysFound);
}
到現(xiàn)在為止竭业,我們已經(jīng)把 LeakCanary 檢測(cè)內(nèi)存泄漏的全部過程的源碼看完了。個(gè)人認(rèn)為 LeakCanary 源碼寫的不錯(cuò)及舍,可讀性很高未辆,查找調(diào)用關(guān)系也比較方便(這里黑一下 bilibili 的 DanmakusFlameMaster)。
五. 版權(quán)聲明
本文首發(fā)于我的微信公眾號(hào):Android開發(fā)實(shí)驗(yàn)室击纬,歡迎大家關(guān)注和我一起學(xué)Android鼎姐,掉節(jié)操。未經(jīng)允許,不得轉(zhuǎn)載炕桨。