Android記憶體洩漏框架LeakCanary原始碼分析
LeakCanary原始碼分析
LeakCanary是一個記憶體洩漏檢測的框架,預設只會檢測Activity的洩漏,如果需要檢測其他類,可以使用LeakCanary.install返回的RefWatcher,呼叫RefWatcher.watch(obj)就可以觀測obj物件是否出現洩漏。
從install方法開始:
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class )
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
最終返回了一個RefWatcher物件:
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
DisplayLeakService
繼承自AbstractAnalysisResultService,它是一個IntentService,用來對洩漏資料進行分析處理。
public abstract class AbstractAnalysisResultService extends IntentService {
...
@Override protected final void onHandleIntent(Intent intent) {
HeapDump heapDump = (HeapDump) intent.getSerializableExtra(HEAP_DUMP_EXTRA);
AnalysisResult result = (AnalysisResult) intent.getSerializableExtra(RESULT_EXTRA);
try {
onHeapAnalyzed(heapDump, result);
} finally {
//noinspection ResultOfMethodCallIgnored
heapDump.heapDumpFile.delete();
}
}
}
在AbstractAnalysisResultService的實現DisplayLeakService中,會開啟一個DisplayLeakActivity活動進行洩漏的顯示:
public class DisplayLeakService extends AbstractAnalysisResultService {
@Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = leakInfo(this, heapDump, result, true);
CanaryLog.d("%s", leakInfo);
boolean resultSaved = false;
boolean shouldSaveResult = result.leakFound || result.failure != null;
if (shouldSaveResult) {
heapDump = renameHeapdump(heapDump);
resultSaved = saveResult(heapDump, result); //儲存heapdump到本地
}
PendingIntent pendingIntent;
String contentTitle;
String contentText;
if (!shouldSaveResult) {
contentTitle = getString(R.string.leak_canary_no_leak_title);
contentText = getString(R.string.leak_canary_no_leak_text);
pendingIntent = null;
} else if (resultSaved) { //heapdump檔案儲存成功才開啟活動
pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey);
if (result.failure == null) {
String size = formatShortFileSize(this, result.retainedHeapSize);
String className = classSimpleName(result.className);
if (result.excludedLeak) {
contentTitle = getString(R.string.leak_canary_leak_excluded, className, size);
} else {
contentTitle = getString(R.string.leak_canary_class_has_leaked, className, size);
}
} else {
contentTitle = getString(R.string.leak_canary_analysis_failed);
}
contentText = getString(R.string.leak_canary_notification_message);
} else {
contentTitle = getString(R.string.leak_canary_could_not_save_title);
contentText = getString(R.string.leak_canary_could_not_save_text);
pendingIntent = null;
}
// New notification id every second.
int notificationId = (int) (SystemClock.uptimeMillis() / 1000);
showNotification(this, contentTitle, contentText, pendingIntent, notificationId);
afterDefaultHandling(heapDump, result, leakInfo); //最後呼叫一個擴充套件的介面,我們可以重寫這個方法來實現heapdump上傳到伺服器的功能
}
}
RefWatch
RefWatch是最主要的類:
我們看一下watch方法:
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); //retainedKeys存放未被GC回收的被觀察物件
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue); //給被觀測的物件建立一個弱引用
ensureGoneAsync(watchStartNanoTime, reference); //開始非同步對目標進行觀察
}
加入一個任何到執行器中:
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
檢查引用是否有效;GC;再次檢查引用是否有效。
弱引用的關聯引用佇列:當弱引用的物件,弱引用會放入關聯的引用佇列queue中。
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime(); //gc開始時間
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(); //執行GC,並且等待100ms
removeWeaklyReachableReferences(); //再次移出有效引用
if (!gone(reference)) { //如果這時候被觀測物件的引用不等於null,也就是它仍然被執行緒可達,說明已經發生了記憶體洩漏
long startDumpHeap = System.nanoTime();
long gcDurationMs = NANOSECONDS.toMillis(startDumpHeap - gcStartNanoTime); //計算gc所用時間
File heapDumpFile = heapDumper.dumpHeap(); //把堆疊資訊存到本地
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
long heapDumpDurationMs = NANOSECONDS.toMillis(System.nanoTime() - startDumpHeap);
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs)); //最後交給heapdump監聽器來處理
}
return DONE;
}
heapdump資訊最終會傳遞給繼承自IntentService的listenerServiceClass方法處理:
public static void runAnalysis(Context context, HeapDump heapDump,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
Intent intent = new Intent(context, HeapAnalyzerService.class);
intent.putExtra(LISTENER_CLASS_EXTRA, listenerServiceClass.getName());
intent.putExtra(HEAPDUMP_EXTRA, heapDump);
context.startService(intent);
}
AndroidWatchExecutor
我們來看一下記憶體洩漏檢查的執行器:
public AndroidWatchExecutor(long initialDelayMillis) {
mainHandler = new Handler(Looper.getMainLooper()); //主執行緒Handler
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME); //新建一個帶訊息迴圈Looper的子執行緒
handlerThread.start();
backgroundHandler = new Handler(handlerThread.getLooper()); //後臺Handler
this.initialDelayMillis = initialDelayMillis;
maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
}
執行檢查任務:
@Override public void execute(Retryable retryable) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0); //主執行緒
} else {
postWaitForIdle(retryable, 0); //非主執行緒
}
最終是在主執行緒呼叫waitForIdle:
void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { //新增一個閒置處理器(在主執行緒訊息佇列閒置的時候會執行queueIdle)
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
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.Result result = retryable.run(); //這時候才會執行鍼對物件的記憶體洩漏檢查
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
ActivityRefWatcher
這個類是用來監聽一個應用內的所有活動的生命週期,當活動執行了onDestory的時候,開始對Activity記憶體洩漏的檢查。
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
總結
整個Activity記憶體洩漏的檢測從Activity執行了onDestory後開始:
1. Activity執行onDestory
2. refWatcher把該Activity新增到弱引用列表裡面
3. 然後使用IdelHandler機制,在主執行緒空閒的時候延時 5 秒傳送一個Runnable到後臺執行緒HandlerThread的訊息佇列裡
4. HandlerThread取出訊息,移除被回收物件的弱引用,然後查詢活動是否還存在於弱引用列表中,如果不在,則說明沒有發生記憶體洩漏
5. 如果在,則呼叫一次gc,等待100ms,再次移除被回收物件的弱引用,查詢活動是否還存在弱引用列表裡面,如果在,則說明發生了記憶體洩漏
6. 然後就執行dumpheap,把堆疊資訊用Intent的方式傳送給IntentService
7. IntentService拿到dumheap之後,儲存heapdump到本地,在通知欄上顯示記憶體洩漏的通知,然後執行afterDefaultHandling(我們可以重寫)
8. 最後刪除本地的heapdump檔案
幾個問題
1. 為什麼要使用idlehandler? 在主執行緒空閒的時候才把任務放到子執行緒中執行
2. HandlerThread會退出嗎?
改進,用HandlerThread設定為守護執行緒,當主執行緒退出時,該執行緒也就死亡了:
handlerThread.setDaemon(true);
3. 執行緒安全嗎?安全retainedKeys = new CopyOnWriteArraySet<>();