使用
LeakCanary 的集成過程很簡單,首先在 build.gradle 文件中添加依賴:
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.1'
releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.1'
}
然後實現自己的 Application 類:
public class LeakcanaryApplication extends Application {
private static RefWatcher sRefWatcher;
@Override public void onCreate() {
super.onCreate();
Log.e("LeakcanaryApplication", "onCreate");
sRefWatcher = LeakCanary.install(this); //爲了sRefWatcher一定要install
}
public static RefWatcher getsRefWatcher(){
return sRefWatcher;
}
}
在需要監控的Activity、Fragment、Service或要監控的特定對象等,
LeakcanaryApplication.getsRefWatcher().watch(this);
這樣就集成完成了。當 LeakCanary 檢測到內存泄露時,會自動彈出 Notification 通知開發者發生內存泄漏的 Activity 和引用鏈,以便進行修復。
源碼分析
- 首先從入口函數LeakCanary.install(Application application)開始
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
- LeakCanary的refWatch方法
public static AndroidRefWatcherBuilder refWatcher(Context context) {
return new AndroidRefWatcherBuilder(context);
}
refWatcher() 方法新建了一個 AndroidRefWatcherBuilder 對象,該對象繼承於 RefWatcherBuilder 類,配置了一些默認參數,利用建造者構建一個 RefWatcher 對象。
- AndroidRefWatcherBuilder.listenerServiceClass()方法
public AndroidRefWatcherBuilder listenerServiceClass(
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
return heapDumpListener(new ServiceHeapDumpListener(context, listenerServiceClass));
}
public final T heapDumpListener(HeapDump.Listener heapDumpListener) {
this.heapDumpListener = heapDumpListener;
return self();
}
==listenerServiceClass() 方法綁定了一個後臺服務 DisplayLeakService,這個服務主要用來分析內存泄漏結果併發送通知==。可以自定義處理,繼承DisplayLeakService實現自定義的監控處理Service。
- excludedRefs()
excludedRefs() 方法定義了一些對於開發者可以忽略的路徑,意思就是即使這裏發生了內存泄漏,LeakCanary 也不會彈出通知。這大多是系統 Bug 導致的,無需用戶進行處理。
- AndroidRefWatcherBuilder.buildAndInstall()
最後調用 buildAndInstall() 方法構建 RefWatcher 實例並開始監聽 Activity 的引用
要點分析:
-
install是有返回值的—RefWatcher,這個對象非常重要,LeakCanary就是通過這個對象進行Activity的內存監控
-
RefWatcher是通過Builder模式創建的;
-
install內部一連串的鏈式調用,最後調用了buildAndInstall()方法;
public RefWatcher buildAndInstall() {
if (LeakCanaryInternals.installedRefWatcher != null) {
throw new UnsupportedOperationException("buildAndInstall() should only be called once.");
}
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
if (watchActivities) {
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) {
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
LeakCanaryInternals.installedRefWatcher = refWatcher;
return refWatcher;
}
watchActivities/watchFragments默認值都是true,所以ActivityRefWatcher和FragmentRefWatcher都將被啓用;
ActivityRefWatcher用於監控Activity的內存泄漏;
FragmentRefWatcher用於監控Fragment的內存泄漏;
- 用ActivityRefWatcher來進一步分析==LeakCanary是如何監控內存泄漏的==.
ActivityRefWatcher.install(context, refWatcher)方法
public static void install(Context context, RefWatcher refWatcher) {
Application application = (Application) context.getApplicationContext();
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
要點分析:
1、registerActivityLifecycleCallbacks是Android Application的一個方法,註冊了該方法,應用中每一個Activity的生命週期變化都會通過該方法回調回來;
2、registerActivityLifecycleCallbacks方法傳入的參數是activityRefWatcher.lifecycleCallbacks,我們到ActivityRefWatcher中看下該方法的實現
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
ActivityRefWatcher只監聽了onActivityDestroyed方法,也就是說每一個Activity調用onDestory的時候都會回調到onActivityDestroyed這個方法,通知LeakCanary該Activity應該被銷燬
到這來我們知道了爲什麼只執行LeakCanary.install(this)一條語句就可以完成對整個app的Activity內存泄漏進行監聽了.
接下來看看ActivityRefWatcher具體是如何監聽內存泄漏的,RefWatcher有兩個核心方法: watch() 和 ensureGone()
先看watch方法
public void watch(Object watchedReference) {
watch(watchedReference, "");
}
public void watch(Object watchedReference, String referenceName) {
if (this == DISABLED) {
return;
}
checkNotNull(watchedReference, "watchedReference");
checkNotNull(referenceName, "referenceName");
final long watchStartNanoTime = System.nanoTime();
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
-
==監聽提供的引用,檢查該引用是否可以被回收==。這個方法是非阻塞的,因爲檢測功能是在Executor中的異步線程執行的。
-
checkNotNull:分別檢測watchedReference、referenceName是否爲空,如果爲空則拋出異常結束;
-
==隨機生成一個key,該key用於唯一標識已產生內存泄漏的對象,或者準備檢測的對象==;
-
創建KeyedWeakReference對象,並調用另一個核心方法ensureGone;
接着看另一個ensureGone方法:
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
@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);
removeWeaklyReachableReferences();
if (debuggerControl.isDebuggerAttached()) {
// The debugger can create false leaks.
return RETRY;
}
if (gone(reference)) {
return DONE;
}
gcTrigger.runGc();
removeWeaklyReachableReferences();
if (!gone(reference)) {
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime);
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
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();
heapdumpListener.analyze(heapDump);
}
return DONE;
}
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
private void removeWeaklyReachableReferences() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
-
ensureGoneAsync() 裏面會 調用watchExecutor.execute(),watchExecutor 的真正實現是 AndroidWatchExecutor,IdleHandler會==在主線程的空閒期得到執行==,當執行的時候會調用postToBackgroundWithDelay()。這個方法爲 LeakCanary 檢測的重試機制(創建dump文件或者toast顯示超5秒等情況重試執行ensureGone)。
-
==這是一個異步任務,不會對主線程造成阻塞==。
-
removeWeaklyReachableReferences():==移除已經回收的弱引用對象的key。==
-
如果需要判斷的對象已經銷燬,則不繼續執行。
-
如果==移除之後還是存在該引用的key則手動再GC一次==:gcTrigger.runGc()。
-
然後==二次調用removeWeaklyReachableReferences()再判斷是否已經銷燬==。
-
==如果二次確認還是存在則判斷爲內存泄漏了==,開始.hprof文件的dump。
-
調用heapdumpListener.analyze(heapDump)進行泄漏分析。
階段總結一下:
-
==弱引用與ReferenceQueue聯合使用,如果弱引用關聯的對象被回收,則會把這個弱引用加入到ReferenceQueue中==;通過這個原理,可以看出removeWeaklyReachableReferences()執行後,會對應刪除已被回收的對象的KeyedWeakReference的數據。==如果這個引用繼續存在,那麼就說明沒有被回收==。
-
==爲了確保最大保險的判定是否被回收,一共執行了兩次回收判定,包括一次手動GC後的回收判定。兩次都沒有被回收,很大程度上說明了這個對象的內存被泄漏了,但並不能100%保證;因此LeakCanary是存在極小程度的誤差的。==
- 內存泄漏最短路徑分析
hprof文件已經生成好了,接下來就是內存泄漏路徑生成分析了,上一步heapdumpListener.analyze(heapDump)的調用。
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
要點分析:
- ==調用了HeapAnalyzerService,在單獨的進程中進行分析;==
public final class HeapAnalyzerService extends ForegroundService
implements AnalyzerProgressListener {
private static final String LISTENER_CLASS_EXTRA = "listener_class_extra";
private static final String HEAPDUMP_EXTRA = "heapdump_extra";
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);
}
public HeapAnalyzerService() {
super(HeapAnalyzerService.class.getSimpleName(), R.string.leak_canary_notification_analysing);
}
@Override protected void onHandleIntentInForeground(@Nullable Intent intent) {
if (intent == null) {
CanaryLog.d("HeapAnalyzerService received a null intent, ignoring.");
return;
}
String listenerClassName = intent.getStringExtra(LISTENER_CLASS_EXTRA);
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAPDUMP_EXTRA);
HeapAnalyzer heapAnalyzer =
new HeapAnalyzer(heapDump.excludedRefs, this, heapDump.reachabilityInspectorClasses);
AnalysisResult result = heapAnalyzer.checkForLeak(heapDump.heapDumpFile, heapDump.referenceKey,
heapDump.computeRetainedHeapSize);
AbstractAnalysisResultService.sendResultToListener(this, listenerClassName, heapDump, result);
}
@Override public void onProgressUpdate(Step step) {
int percent = (int) ((100f * step.ordinal()) / Step.values().length);
CanaryLog.d("Analysis in progress, working on: %s", step.name());
String lowercase = step.name().replace("_", " ").toLowerCase();
String message = lowercase.substring(0, 1).toUpperCase() + lowercase.substring(1);
showForegroundNotification(100, percent, false, message);
}
}
要點分析:
- runAnalysis方法中最後一句:ContextCompat.startForegroundService(context, intent)啓動HeapAnalyzerService
- HeapAnalyzerService繼承自ForegroundService,服務啓動時會調用onHandleIntentInForeground方法;
HeapAnalyzer中核心的方法是checkForLeak,我們來看下它的具體實現:
/**
* Searches the heap dump for a {@link KeyedWeakReference} instance with the corresponding key,
* and then computes the shortest strong reference path from that instance to the GC roots.
*/
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey,
boolean computeRetainedSize) {
long analysisStartNanoTime = System.nanoTime();
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
listener.onProgressUpdate(READING_HEAP_DUMP_FILE);
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
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);
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));
}
}
要點分析:
- 先來翻譯一下官方對該方法的註釋:
在dump的堆信息中通過key查找出內存泄漏的弱引用對象,並且計算出最短的GC路徑;
-
Snapshot snapshot = parser.parse()–生成文件的快照
-
deduplicateGcRoots(snapshot)–過濾重複的內存泄漏對象;
-
findLeakingReference(referenceKey, snapshot)–==在快照中根據referenceKey查找是否有對應的內存泄漏對象==,如果獲取到的leakingRef爲空,則說明內存已經被回收了,不存在內存泄漏的情況;如果leakingRef不爲空則進入下一步查找內存泄漏對象的GC最短路徑;
-
findLeakTrace(analysisStartNanoTime, snapshot, leakingRef, computeRetainedSize)–查找內存泄漏對象的GC最短路徑;
LeakCanary的核心源碼已經都已經分析完了,剩下的就是如果在頁面上顯示出來了。
監控原理總結
-
==LeakCanary主要是通過Application的registerActivityLifecycleCallbacks方法監控每一個Activty的Destory之後對象是否被收回.==
-
==在Activity Destory之後,RefWatch的watch方法將被調用,watch方法會通過一個隨機生成的key將這個弱引用關聯到一個ReferenceQueue,然後調用ensureGone();==
(watch也可以傳入其他需要監控的對象,將檢測的對象放入弱引用中並且關聯到引用隊列中,查看引用隊列中是否存在引用,如果發現泄露,dump出信息進行分析)
(當一個軟引用/弱引用對象被垃圾回收後,Java虛擬機就會把這個引用加入到與之關聯的引用隊列中;)
- ==RefWatch的ensureGone()方法中會先確認一次是否已經被回收,如果發現沒有被回收,則主動GC一下,然後在次確認是否被回收,如果還是沒有回收則判斷爲內存泄漏==;
(==判斷有沒被回收的依據==:弱引用與ReferenceQueue聯合使用,如果弱引用關聯的對象被回收,則會把這個弱引用加入到ReferenceQueue中,removeWeaklyReachableReferences()會清除掉retainedKeys中相應的key。在調用gone()方法時判斷retainedKeys是否還包含相應的key,沒包含說明回收了,包含說明沒有回收)
-
一旦確認是內存泄漏,則開始dump信息到hprof文件中,並調用heapdumpListener.analyze(heapDump)開始內存分析;
-
==內存分析是在HeapAnalyzerService服務中進行的,屬於一個單獨的進程==;
-
HeapAnalyzerService的runAnalysis中創建HeapAnalyzer對象並調用它的一個核心方法checkForLeak();
-
==HeapAnalyzer的checkForLeak()會先解析hprof文件並生成快照文件,然後對快照中的泄漏對象進行去重,去重後根據第2步中的key去獲取泄漏對象,如果對象爲空則說明對象已經被回收,如果不爲空則通過findLeakTrace()方法計算出最短GC路徑==,並顯示到DisplayLeakActivity頁面,提醒開發者存在內存泄漏.
默認情況只對Activity和Fragment進行監控,也可以對其他模塊的對象進行監控。
LeakcanaryApplication.getsRefWatcher().watch(this);