1.LeakCanary簡介
LeakCanary是Square公司開源的內存泄露檢測工具。
github:LeakCanary
2.源碼分析
LeakCanary源碼分析我們從install方法開始帽芽,大家應該知道引入LeakCanary的時候install方法時在Application#onCreate的方法中執(zhí)行的删掀。
我們首先看isInAnalyzerProcess方法,看注釋如果是LeakCanary進程就不允許初始化其他任務导街。這個進程是為LeakCanary分析堆內存用的披泪。
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
我們知道引入LeakCanary在你的項目編譯成功之后會自動安裝一個Leaks apk“峁澹看到上面LeakCanary.install(this)這行代碼款票,是不是安裝Leaks apk?是不是Leaks通過跨進程來檢測我項目呢泽论?檢測原理又是怎樣的艾少?帶著這些問題,接下來看看具體做了什么事情翼悴。會調用LeakCanary#install方法
public static RefWatcher install(Application application) {
return refWatcher(application).listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
首先我們看看buildAndInstall方法具體做了什么事情
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.installOnIcsPlus((Application) context, refWatcher);
}
return refWatcher;
}
看ActivityRefWatcher類名應該是Activity 引用監(jiān)控缚够,我們跟進這個installOnIcsPlus方法看看
public static void installOnIcsPlus(Application application, RefWatcher refWatcher) {
if (SDK_INT < ICE_CREAM_SANDWICH) {
// If you need to support Android < ICS, override onDestroy() in your base activity.
return;
}
ActivityRefWatcher activityRefWatcher = new ActivityRefWatcher(application, refWatcher);
activityRefWatcher.watchActivities();
}
然后installOnIcsPlus方法又調用了ActivityRefWatcher#watchActivities方法,我們再跟進分析
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new Application.ActivityLifecycleCallbacks() {
@Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
}
@Override public void onActivityStarted(Activity activity) {
}
@Override public void onActivityResumed(Activity activity) {
}
@Override public void onActivityPaused(Activity activity) {
}
@Override public void onActivityStopped(Activity activity) {
}
@Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
}
@Override public void onActivityDestroyed(Activity activity) {
ActivityRefWatcher.this.onActivityDestroyed(activity);
}
};
public void watchActivities() {
// Make sure you don't get installed twice.確保不要注冊兩次
stopWatchingActivities();
application.registerActivityLifecycleCallbacks(lifecycleCallbacks);
}
我們首先看看Application#registerActivityLifecycleCallbacks方法鹦赎,我們知道原理registerActivityLifecycleCallbacks是注冊Activity生命周期回調的方法谍椅。管理生命周期的方法,每個Activity都會回調到這里古话。我們看lifecycleCallbacks這個變量對應著Activity的生命周期雏吭。
到這里所以我們是不是可以猜想到LeakCanary其實就是通過注冊Activity生命周期回調來監(jiān)控檢查是否有內存泄露的。當Activity界面關閉回調用對應的onActivityDestroyed方法煞额。
接下來我們看看Application.ActivityLifecycleCallbacks#onActivityDestroyed方法干了些什么事情思恐。
ActivityRefWatcher.this.onActivityDestroyed(activity);
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
接下來會調用到com.squareup.leakcanary.RefWatcher#watch(java.lang.Object)方法沾谜。
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引用包裝成一個KeyedWeakReference弱引用。被WeakReference包裝的引用如果被回收會添加到ReferenceQueue隊列中胀莹,WeakReference和ReferenceQueue將在接下來的文章里分析基跑。WeakReference和ReferenceQueue
通過檢查ReferenceQueue里的Activity引用來檢測是否能夠被回收。下面具體看看檢測方法:
ensureGoneAsync(watchStartNanoTime, reference)--->ensureGone(reference, watchStartNanoTime)
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);
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
}
return DONE;
}
通過ensureGone這個方法名可以看出應該是確保引用釋放的意思描焰。具體看看ensureGone方法做了些什么:
我們主要看removeWeaklyReachableReferences(),gcTrigger.runGc(),heapdumpListener.analyze()這三個方法媳否。
1.通過removeWeaklyReachableReferences移除所有ReferenceQueue隊列的WeakReference對象的Activity引用。如果可以回收就直接返回荆秦。
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);
}
}
2.Activity引用如果通過removeWeaklyReachableReferences還是沒有移除就通過gcTrigger.runGc()來GC觸發(fā)回收篱竭。
3.接下來再觸發(fā)步驟1的removeWeaklyReachableReferences方法來移除Activity引用,判斷是否回收步绸,如果可以回收就直接返回,反之就說明對象泄露了就會觸發(fā)heapdumpListener.analyze()方法來分析掺逼,看到HeapDump對象,我們可以通過heapDumper.dumpHeap()方法看到dump當前的heap hprof快照文件瓤介。
具體通過Debug.dumpHprofData(heapDumpFile.getAbsolutePath())方法去dump內存文件的吕喘。實現(xiàn)如下:
@Override public File dumpHeap() {
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
if (heapDumpFile == RETRY_LATER) {
return RETRY_LATER;
}
FutureResult<Toast> waitingForToast = new FutureResult<>();
showToast(waitingForToast);
if (!waitingForToast.wait(5, SECONDS)) {
CanaryLog.d("Did not dump heap, too much time waiting for Toast.");
return RETRY_LATER;
}
Toast toast = waitingForToast.get();
try {
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
cancelToast(toast);
return heapDumpFile;
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;
}
}
接下來會調用com.squareup.leakcanary.ServiceHeapDumpListener#analyze方法將內存文件目錄等相關信息通過HeapAnalyzerService#runAnalysis方法來進行對象內存泄露的分析。實現(xiàn)如下:
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
接著會啟動HeapAnalyzerService IntentService服務刑桑,具體IntentService的分析在我的IntentService源碼分析中有講過IntentService源碼分析
氯质。
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);
}
具體在HeapAnalyzerService#onHandleIntent方法中通過HeapAnalyzer#checkForLeak來檢測是否內存泄露的。實現(xiàn)如下:
public AnalysisResult checkForLeak(File heapDumpFile, String referenceKey) {
long analysisStartNanoTime = System.nanoTime();
if (!heapDumpFile.exists()) {
Exception exception = new IllegalArgumentException("File does not exist: " + heapDumpFile);
return failure(exception, since(analysisStartNanoTime));
}
try {
HprofBuffer buffer = new MemoryMappedFileBuffer(heapDumpFile);
HprofParser parser = new HprofParser(buffer);
Snapshot snapshot = parser.parse();
deduplicateGcRoots(snapshot);
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);
} catch (Throwable e) {
return failure(e, since(analysisStartNanoTime));
}
}
接下來啟動AbstractAnalysisResultService這個IntentService祠斧,啟動后會調用onHandleIntent方法闻察。
@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();
}
}
最終通過onHeapAnalyzed這個抽象方法將heapDump,AnalysisResult信息傳入到子類實現(xiàn)琢锋。
@Override protected final void onHeapAnalyzed(HeapDump heapDump, AnalysisResult result) {
String leakInfo = leakInfo(this, heapDump, result, true);
CanaryLog.d(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) {
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);
}
分析上面邏輯其實就是通過DisplayLeakService做內存泄露相關展示辕漂。
3.總結
到目前為止,我想大家應該知道LeakCanary是怎樣實現(xiàn)內存泄露的檢測吩蔑。
利用Application#registerActivityLifecycleCallbacks Activity生命周期回調onActivityDestroyed方法通過調用RefWatcher#wather方法來檢測對象是否回收钮热,通過removeWeaklyReachableReferences--->gcTrigger.runGc--->removeWeaklyReachableReferences--->heapdumpListener.analyze 三步二次檢測來確定內存泄露,最終dump 內存信息來分析到最終顯示分析出的泄露信息烛芬。
4.LeakCanary原理實踐
分析完LeakCanary原理隧期,其實我們也可以在自己的項目中利用LeakCanary原理在打開每個Actvitiy或者Fragment時定義一個弱引用實例,然后關聯(lián)在一個ReferenceQueue隊列中赘娄,然后通過Application#registerActivityLifecycleCallbacks#onActivityDestroyed回調去關聯(lián)的ReferenceQueue中檢測當前的Actvitiy或者Fragment是否被回收來檢測是否泄漏仆潮,這樣就是一個精簡版的LeakCanary。當發(fā)現(xiàn)內存泄漏遣臼,然后dump hprof內存快照文件到服務器性置,然后分析內存文件,從而可以發(fā)現(xiàn)內存泄漏揍堰。