LeakCanary簡易解析
本篇基於1.6.1
版本原始碼閱讀, 本篇內容就是搞懂LeakCanary
如何做到記憶體洩漏定位的主要流程, 不摳具體細節.
正文
老樣子, 我們直接從從LeakCanary.install(this)
作為入口開始看
public static RefWatcher install(Application application) { return refWatcher(application).listenerServiceClass(DisplayLeakService.class) .excludedRefs(AndroidExcludedRefs.createAppDefaults().build()) .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; }
通過AndroidRefWatcherBuilder
物件進行一系列相關物件的初始化, 包括ServiceHeapDumpListener
,ExcludedRefs
,以及最重要的RefWatcher
.
ServiceHeapDumpListener ExcludedRefs RefWatcher
我們通過ActivityRefWatcher.install(context, refWatcher)
檢視Actvitiy的記憶體洩漏的分析流程
public static void install(Context context, 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); } };
可以看到註冊了一個ActivityLifecycleCallbacks
, 在頁面生命週期走到onDestroy
的時候, 會觸發refWatcher.watch(activity)
public void watch(Object watchedReference) { watch(watchedReference, ""); } /** * Watches the provided references and checks if it can be GCed. This method is non blocking, * the check is done on the {@link WatchExecutor} this {@link RefWatcher} has been constructed * with. * * @param referenceName An logical identifier for the watched object. */ 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); }
申明弱引用, 放入activity
物件, 註冊關聯queue
引用佇列
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) { watchExecutor.execute(new Retryable() { @Override public Retryable.Result run() { return ensureGone(reference, watchStartNanoTime); } }); }
在AndroidRefWatcherBuilder
可以通過watchDelay(long delay, TimeUnit unit)
設定是否延遲觀測, 如果有設定, 則會在保證在主執行緒內延遲進行分析記憶體洩漏; 否則直接執行分析處理
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; } // 重新執行gc 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 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); } }
每個activity
申明弱引用的時候都會有個ID, ID儲存在retainedKeys
集合中, 首先遍歷移除被gc回收的物件, 如果這個時候retainedKeys
集合為空, 則表示不存在記憶體洩漏的情況. 否則手動執行GC, 再次判斷移除, 這個時候如果retainedKeys
內仍存在ID, 則說明有記憶體洩漏的情況存在.
在存在記憶體洩漏的情況下, 通過heapDumper.dumpHeap()
獲取堆記憶體快照, 通過heapdumpListener.analyze
去進行解析.
public void analyze(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, 這個服務主要做的就是去解析我們的.hprof檔案, 主要的工作內容在onHandleIntentInForeground
方法內
@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); }
然後通過haha
庫, 將.hprof檔案解析結果AnalysisResult
物件, 通過AbstractAnalysisResultService.sendResultToListener
傳遞啟動DisplayLeakService
服務.
@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); } 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) { pendingIntent = DisplayLeakActivity.createPendingIntent(this, heapDump.referenceKey); if (result.failure == null) { if (result.retainedHeapSize == AnalysisResult.RETAINED_HEAP_SKIPPED) { String className = classSimpleName(result.className); if (result.excludedLeak) { 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); String className = classSimpleName(result.className); 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 { 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); }
將對應的.hprof檔案重新命名, 對應洩漏的記憶體物件關聯key, 傳遞到DisplayLeakActivity
做顯示, 另外進行通知顯示.