1.LeakCanary简介
LeakCanary是Square公司开源的内存泄露检测工具。
github:LeakCanary
2.源码分析
LeakCanary源码分析我们从install方法开始,大家应该知道引入LeakCanary的时候install方法时在Application#onCreate的方法中执行的。
我们首先看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 引用监控,我们跟进这个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生命周期回调来监控检查是否有内存泄露的。当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触发回收。
3.接下来再触发步骤1的removeWeaklyReachableReferences方法来移除Activity引用,判断是否回收,如果可以回收就直接返回,反之就说明对象泄露了就会触发heapdumpListener.analyze()方法来分析,看到HeapDump对象,我们可以通过heapDumper.dumpHeap()方法看到dump当前的heap hprof快照文件。
具体通过Debug.dumpHprofData(heapDumpFile.getAbsolutePath())方法去dump内存文件的。实现如下:
@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方法来进行对象内存泄露的分析。实现如下:
@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来检测是否内存泄露的。实现如下:
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信息传入到子类实现。
@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是怎样实现内存泄露的检测。
利用Application#registerActivityLifecycleCallbacks Activity生命周期回调onActivityDestroyed方法通过调用RefWatcher#wather方法来检测对象是否回收,通过removeWeaklyReachableReferences--->gcTrigger.runGc--->removeWeaklyReachableReferences--->heapdumpListener.analyze 三步二次检测来确定内存泄露,最终dump 内存信息来分析到最终显示分析出的泄露信息。
4.LeakCanary原理实践
分析完LeakCanary原理,其实我们也可以在自己的项目中利用LeakCanary原理在打开每个Actvitiy或者Fragment时定义一个弱引用实例,然后关联在一个ReferenceQueue队列中,然后通过Application#registerActivityLifecycleCallbacks#onActivityDestroyed回调去关联的ReferenceQueue中检测当前的Actvitiy或者Fragment是否被回收来检测是否泄漏,这样就是一个精简版的LeakCanary。当发现内存泄漏,然后dump hprof内存快照文件到服务器,然后分析内存文件,从而可以发现内存泄漏。