一、 什么是LeakCanary
LeakCanary 是大名鼎鼎的 square 公司開源的內存泄漏檢測工具。目前上大部分App在開發測試階段都會接入此工具用于檢測潛在的內存泄漏問題,做的好一點的可能會搭建一個服務器用于保存各個設備上的內存泄漏問題再集中處理。
本文首發于我的微信公眾號:Android開發實驗室,歡迎大家關注和我一起學Android,掉節操。
二、 為什么要使用LeakCanary
我們知道內存泄漏問題的排查有很多種方法, 比如說,Android Studio 自帶的 Profile 工具、MAT(Memory Analyzer Tool)、以及LeakCanary。 選擇 LeakCanary 作為首選的內存泄漏檢測工具主要是因為它能實時檢測泄漏并以非常直觀的調用鏈方式展示內存泄漏的原因。
三、 LeakCanary 做不到的(待定)
雖然 LeakCanary 有諸多優點,但是它也有做不到的地方,比如說檢測申請大容量內存導致的OOM問題、Bitmap內存未釋放問題,Service 中的內存泄漏可能無法檢測等。
四、 LeakCanary 源碼解析
本章內容前后依賴關系強烈,建議順序閱讀。
4.1 ActivityLifecycleCallbacks 與 FragmentLifeCycleCallbacks
在開始 LeakCanary 原理解析之前,有必要簡單說下 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
方法用于注冊和反注冊 Activity 的生命周期監聽類,這樣我們就能在 Application 中對所有的 Activity 生命周期回調中做一些統一處理。
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
方法用戶注冊和反注冊 Fragment 的生命周期監聽類,這樣我們對每一個 Activity 進行注冊,就能獲取所有的 Fragment 生命周期回調。
4.2 LeakCanary 的使用
4.2.1 使用方法
我們直接在 Application 類中,添加一下代碼即可。
public class ExampleApplication extends Application {
@Override public void onCreate() {
super.onCreate();
setupLeakCanary();
}
protected void setupLeakCanary() {
// 啟用嚴格模式
enabledStrictMode();
// 判斷是否是 HeapAnalyzerService 所屬進程
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
// 注冊 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 文件解析服務 HeapAnalyzerService 所屬進程是與主進程獨立的一個進程,所以在 setupLeakCanary
中,我們需要排除其他進程,只對 leakcanary
進程注冊 LeakCanary 監聽處理。
android:enabled="false" 這是什么?
這里簡單說下,AndroidManifest文件中的 enabled 屬性,可以看到 HeapAnalyzerService 這個組件默認是不可用的,所以如果在代碼中動態啟用這個組件,可以使用以下方法:
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 方法實際上構造了一個 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();
}
我們一個個來看這個注冊方法。首先是 refWatcher 方法構造了一個 AndroidRefWatcherBuilder, 傳入參數是當前Application 的 Context.
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
listenerServiceClass 和 excludedRefs 方法是基于建造者模式傳入分析Service 和 排除已知的泄漏問題 AndroidExcludedRefs,這里我就不貼代碼了。
重點看下 buildAndInstall 方法,這個方法很形象的表示將要進行建造者模式的最后一步 build 和 注冊一些監聽器,下面我們來看具體代碼:
public @NonNull RefWatcher buildAndInstall() {
// 只允許 install 一次
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
// 建造者模式的最后一步,構造對象
RefWatcher refWatcher = build();
// 判斷是否開啟了 LeakCanary,沒有開啟默認會返回 DISABLED 對象
if (refWatcher != DISABLED) {
// 手動開啟 DisplayLeakActivity 組件,會在桌面上顯示一個查看內存泄漏結果的入口
LeakCanaryInternals.setEnabledAsync(context, DisplayLeakActivity.class, true);
// 是否檢測 Activity 的 內存泄漏,默認開啟
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
// 是否檢測 Fragment 的 內存泄漏,默認開啟
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
// 復制給全局靜態變量,防止二次調用
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
以上代碼作用大部分都在代碼中注釋了,剩下 ActivityRefWatcher.install 和 FragmentRefWatcher.Helper.install 方法沒有注釋。下面我們就來具體看看這兩個方法究竟干了什么。
(1). ActivityRefWatcher.install
ActivityRefWatcher 的靜態方法 install 獲取到了當前 Application,然后添加了一個生命周期監聽器 ActivityLifecycleCallbacks,這里的 lifecycleCallbacks 僅僅關注了 Activity 銷毀的回調 onActivityDestroyed,在這里將傳入的對象 activity 監聽起來, 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 的靜態方法 install 里同樣會注冊一個 ActivityLifecycleCallbacks 用于監聽 Activity 生命周期中的 onActivityCreated
的創建完成的回調,在 Activity 創建完成后,會對這個 Activity 注冊 Fragment 的生命周期監聽器。install 方法首先會判斷系統是否大于等于 Android O, 如果是那么會使用 android.app.FragmentManager
進行注冊,如果需要兼容 Android O 以下需要自行在依賴中添加對 leakcanary-support-fragment
組件的依賴,然后通過反射構造出SupportFragmentRefWatcher
; 然后將fragmentRefWatchers所有監聽器取出,在 Activity 創建完成后,添加 Fragment 的生命監聽,主要關注 Fragment 的 onFragmentViewDestroyed
和 onFragmentDestroyed
方法。具體代碼如下:
public static void install(Context context, RefWatcher refWatcher) {
List<FragmentRefWatcher> fragmentRefWatchers = new ArrayList<>();
// 系統是否大于等于 Android O,如果是,添加 AndroidOFragmentRefWatcher
if (SDK_INT >= O) {
fragmentRefWatchers.add(new AndroidOFragmentRefWatcher(refWatcher));
}
// 如果添加了leakcanary-support-fragment的依賴,通過反射可以構造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);
// 先監聽 Activity 的創建完成回調
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 創建完成后,對Activity中的Fragment注冊生命周期監聽
for (FragmentRefWatcher watcher : fragmentRefWatchers) {
watcher.watchFragments(activity);
}
}
};
// AndroidOFragmentRefWatcher.java
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
// Fragment 中的View 視圖銷毀時
@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
View view = fragment.getView();
if (view != null) {
refWatcher.watch(view);
}
}
// Fragment 銷毀時
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
refWatcher.watch(fragment);
}
};
@Override public void watchFragments(Activity activity) {
// 通過FragmentManager 注冊 FragmentLifecycleCallbacks
FragmentManager fragmentManager = activity.getFragmentManager();
fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
上述流程我們已經完全搞清楚了,用一個流程圖可以表示為:
[圖片上傳失敗...(image-f5fe25-1543852247130)]
4.4 LeakCanary 內存泄漏檢測原理
從行文結構上,本小節應屬于上一節后半部分內容,但是RefWatcher 的 watch 方法足夠重要和復雜,所以有必要單獨列一節仔細講解內部原理。
4.4.1 基礎知識——弱引用 WeakReference 和 引用隊列 ReferenceQueue
關于引用類型和引用隊列相關知識,讀者可以參考白話 JVM——深入對象引用,這篇文章我認為講解的比較清晰。
這里,我簡單舉個例子,弱引用在定義的時候可以指定引用對象和一個 ReferenceQueue,弱引用對象在垃圾回收器執行回收方法時,如果原對象只有這個弱引用對象引用著,那么會回收原對象,并將弱引用對象加入到 ReferenceQueue,通過 ReferenceQueue 的 poll 方法,可以取出這個弱引用對象,獲取弱引用對象本身的一些信息。看下面這個例子。
mReferenceQueue = new ReferenceQueue<>();
// 定義一個對象
o = new Object();
// 定義一個弱引用對象引用 o,并指定引用隊列為 mReferenceQueue
weakReference = new WeakReference<Object>(o, mReferenceQueue);
// 去掉強引用
o = null;
// 觸發應用進行垃圾回收
Runtime.getRuntime().gc();
// hack: 延時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");
}
打印結果為:
============
ref in queue
4.4.2 基礎知識——hprof文件
hprof 文件可以展示某一時刻java堆的使用情況,根據這個文件我們可以分析出哪些對象占用大量內存和未在合適時機釋放,從而定位內存泄漏問題。
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 工具轉換成J2SE HPROF格式才能在MAT等標準 hprof 工具中查看。
hprof-conv dump.hprof converted-dump.hprof
至于hprof內部格式如何,本文不做具體介紹,以后有機會再單獨寫一篇文章來仔細講解。LeakCanary 解析 .hprof 文件用的是 square 公司開源的另一項目:haha.
4.4.3 watch方法
終于到了 LeakCanary 關鍵部分了。我們從 watch 方法入手,前面的代碼都是為了增強魯棒性,我們直接從生成唯一id開始,LeakCanary 構造了一個帶有 key 的弱引用對象,并且將 queue 設置為弱引用對象的引用隊列。
這里解釋一下,為什么需要創建一個帶有 key 的弱引用對象,不能直接使用 WeakReference 么?
舉個例子,假設 OneActivity 發生了內存泄漏,那么執行 GC 操作時,肯定不會回收 Activity 對象,這樣 WeakReference 對象也不會被回收。假設當前啟動了 N 個 OneActivity,Dump內存時我們可以獲取到內存中的所有 OneActivity,但是當我們準備去檢測其中某一個 Activity 的泄漏問題時,我們就無法匹配。但是如果使用了帶有 key 的 WeakReference 對象,發生泄露時泄漏時,key 的值也會 dump 保存下來,這樣我們根據 key 的一一對應關系就能映射到某一個 Activity。
然后,LeakCanary 調用了 ensureGoneAsync 方法去檢測內存泄漏。
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
// 對當前監視對象設置一個唯一 id
String key = UUID.randomUUID().toString();
// 添加到 Set<String> 中
retainedKeys.add(key);
// 構造一個帶有id 的 WeakReference 對象
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
// 檢測對象是否被回收了
ensureGoneAsync(watchStartNanoTime, reference);
}
4.4.4 ensureGoneAsync 方法
ensureGoneAsync 方法構造了一個 Retryable 對象,并將它傳給 watchExecutor 的 execute 方法。
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
// watchExecutor 是 AndroidWatchExecutor的一個實例
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
watchExecutor
是 AndroidWatchExecutor 的一個實例, AndroidWatchExecutor 的 execute 方法的作用就是判斷當前線程是否是主線程,如果是主線程,那么直接執行 waitForIdle 方法,否則通過 Handler 的 post 方法切換到主線程再執行 waitForIdle 方法。
@Override public void execute(@NonNull Retryable retryable) {
// 判斷當前線程是否是主線程
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}
waitForIdle
方法通過調用 addIdleHandler
方法,指定當主進程中沒有需要處理的事件時,在這個空閑期間執行 postToBackgroundWithDelay
方法。
private void waitForIdle(final Retryable retryable, final int failedAttempts) {
// 由于上面的 execute 方法,已經保證了此方法在主線程中執行,所以Looper.myQueue()獲取的主線程的消息隊列
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
// return false 表示執行完之后,就立即移除這個事件
return false;
}
});
}
postToBackgroundWithDelay
方法首先會計算延遲時間 delayMillis
,這個延時是有 exponentialBackoffFactor(指數因子) 乘以初始延時時間得到的, exponentialBackoffFactor(指數因子)會在2^n 和 Long.MAX_VALUE / initialDelayMillis 中取較小值,也就說延遲時間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);
// 計算延遲時間
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
// 切換到子線程中執行
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
// 執行 retryable 里的 run 方法
Retryable.Result result = retryable.run();
// 如果需要重試,那么再添加到主線程的空閑期間執行
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
postToBackgroundWithDelay
方法每次執行會指數級增加延時時間,延時時間到了后,會執行 Retryable 里的方法,如果返回為重試,那么會增加延時時間并執行下一次。
retryable.run()
的run 方法又執行了什么呢?別忘了我們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 方法
我現在講ensureGone
方法的完整代碼貼出來,我們逐行分析:
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
// 前面不是有一個重試的機制么,這里會計下這次重試距離第一次執行花了多長時間
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
// 移除所有弱引用可達對象,后面細講
removeWeaklyReachableReferences();
// 判斷當前是否正在開啟USB調試,LeakCanary 的解釋是調試時可能會觸發不正確的內存泄漏
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
// 上面執行 removeWeaklyReachableReferences 方法,判斷是不是監視對象已經被回收了,如果被回收了,那么說明沒有發生內存泄漏,直接結束
if (gone(reference)) {
return DONE;
}
// 手動觸發一次 GC 垃圾回收
gcTrigger.runGc();
// 再次移除所有弱引用可達對象
removeWeaklyReachableReferences();
// 如果對象沒有被回收
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
// 使用 Debug 類 dump 當前堆內存中對象使用情況
File heapDumpFile = heapDumper.dumpHeap();
// dumpHeap 失敗的話,會走重試機制
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
// 將hprof文件、key等屬性構造一個 HeapDump 對象
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile).referenceKey(reference.key)
.referenceName(reference.name)
.watchDurationMs(watchDurationMs)
.gcDurationMs(gcDurationMs)
.heapDumpDurationMs(heapDumpDurationMs)
.build();
// heapdumpListener 分析 heapDump 對象
heapdumpListener.analyze(heapDump);
}
return DONE;
}
看完上述代碼,基本把檢測泄漏的大致過程走了一遍,下面我們來看一些具體的細節。
(1). removeWeaklyReachableReferences 方法
removeWeaklyReachableReferences
移除所有弱引用可達對象是怎么工作的?
private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
還記得我們在 refWatcher.watch 方法保存了當前監視對象的 ref.key 了么,如果這個對象被回收了,那么對應的弱引用對象會在回收時被添加到queue中,通過 poll 操作就可以取出這個弱引用,這時候我們從retainedKeys
中移除這個 key, 代表這個對象已經被正常回收,不需要再被監視了。
那么現在來看,判斷這個對象是否被回收就比較簡單了?
private boolean gone(KeyedWeakReference reference) {
// retainedKeys 中不包含 reference.key 的話,就代表這個對象已經被回收了
return !retainedKeys.contains(reference.key);
}
(2). dumpHeap 方法
heapDumper.dumpHeap()
是執行生成hprof的方法,heapDumper 是 AndroidHeapDumper 的一個對象,我們來具體看看它的 dump 方法。
public File dumpHeap() {
// 生成一個存儲 hprof 的文件
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
// 文件創建失敗
if (heapDumpFile == RETRY_LATER) {
return RETRY_LATER;
}
// FutureResult 內部有一個 CountDownLatch,用于倒計時
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;
}
// 創建一個通知
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 內存到指定文件
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 已完成顯示(有點黑科技的感覺)。
private void showToast(final FutureResult<Toast> waitingForToast) {
mainHandler.post(new Runnable() {
@Override public void run() {
// 當前 Activity 已經 paused的話,直接返回
if (resumedActivity == null) {
waitingForToast.set(null);
return;
}
// 構建一個toast 對象
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加入顯示隊列
toast.show();
// Waiting for Idle to make sure Toast gets rendered.
// 主線程中添加空閑時操作,如果主線程是空閑的,會將CountDownLatch執行 countDown 操作
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
waitingForToast.set(toast);
return false;
}
});
}
});
}
首先我們需要知道所有的Toast對象,并不是我們一調用 show 方法就立即顯示的。NotificationServiceManager
會從mToastQueue
中輪詢去除Toast對象進行顯示。如果Toast的顯示不是實時的,那么我們如何知道Toast是否已經顯示完成了呢?我們在 Toast 調用 show 方法后調用 addIdleHandler, 在主進程空閑時執行 CountDownLatch 的減一操作。由于我們知道我們順序加入到主線程的消息隊列中的操作:先顯示Toast,再執行 CountDownLatch 減一操作。所以在 if (!waitingForToast.wait(5, SECONDS))
的判斷中,我們最多等待5秒,如果超時會走重試機制,如果我們的 CountDownLatch 已經執行了減一操作,則會正常走后續流程,同時我們也能推理出它前面 toast 肯定已經顯示完成了。
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
是系統Debug類提供的方法,我就不做具體分析了。
(3). heapdumpListener.analyze(heapDump) 方法
heapdumpListener 是 ServiceHeapDumpListener 的一個對象,最終執行了HeapAnalyzerService.runAnalysis
方法。
@Override public void analyze(@NonNull HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
// 啟動前臺服務
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會將所有并發的啟動服務操作,變成順序執行 onHandleIntent 方法。
@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
// 監聽 hprof 文件分析結果的類
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 會調用 haha 組件中的工具,分析 hprof 文件
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
// 將分析結果發送給監聽器 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 {
// 更新進度回調
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項
deduplicateGcRoots(snapshot);
listener.onProgressUpdate(FINDING_LEAKING_REF);
// 查找內存泄漏項
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));
}
// 找到泄漏處的引用關系鏈
return findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
hprof 文件的解析是由開源項目 haha 完成的,我這里不做過多展開。
findLeakingReference
方法是查找泄漏的引用處,我們看下代碼:
private Instance findLeakingReference(String key, Snapshot snapshot) {
// 從 hprof 文件保存的對象中找到所有 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);
// 找到 key 字段對應的值
Object keyFieldValue = fieldValue(values, "key");
if (keyFieldValue == null) {
keysFound.add(null);
continue;
}
// 將 keyFieldValue 轉為 String 對象
String keyCandidate = asString(keyFieldValue);
// 如果這個對象的 key 和 我們查找的 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);
}
到現在為止,我們已經把 LeakCanary 檢測內存泄漏的全部過程的源碼看完了。個人認為 LeakCanary 源碼寫的不錯,可讀性很高,查找調用關系也比較方便(這里黑一下 bilibili 的 DanmakusFlameMaster)。
五. 版權聲明
本文首發于我的微信公眾號:Android開發實驗室,歡迎大家關注和我一起學Android,掉節操。未經允許,不得轉載。