LeakCanary源码分析
基本原理
LeakCanary里面注册了Application.ActivityLifecycleCallbacks
的事件监听,在这个里面监听每一个Activity的onDestory()
回调。
LeakCanary是从install()
方法开始的,从这里为入口进行分析。
代码片段1 —— 何时触发检查?
AndroidRefWatcherBuilder.java
public RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
LeakCanary.enableDisplayLeakActivity(context);
ActivityRefWatcher.install((Application) context, refWatcher);
}
return refWatcher;
}
ActivityRefWatcher.java
public static void install(Application application, RefWatcher refWatcher) {
new ActivityRefWatcher(application, refWatcher).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);
}
};
void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
之后就是进入watch阶段。
WeakReference & ReferenceQueue
WeakReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收。也就是说,一旦WeakReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,WeakReference类所提供的get()方法返回Java对象的强引用。另外,一旦垃圾线程回收该Java对象之后,get()方法将返回null。
那ReferenceQueue有什么卵用呢?
ReferenceQueue的作用就是清除失去了引用值的WeakReference对象。
WeakReference作为具有保存“弱”值的特殊性以外,它自己其实还是一般的java对象,有着一般对象的特性。当“弱”值被回收后,虽然这个WeakReference对象的get()方法返回null,但是WeakReference对象还是存在的,没有释放,所以需要一个适当的清除机制,来避免大量的WeakReference对象锁带来的内存消耗和泄漏。
而ReferenceQueue的出现就是为了解决这个问题的,将WeakReference与ReferenceQueue绑定在一起,代码如下:
MyObject aMyObject = new MyObject();
ReferenceQueue queue = new ReferenceQueue();
WeakReference ref = new WeakReference(aMyObject, queue);
当aMyobject被GC的同时,ref对象会被加入到ReferenceQueue队列中,也就是说ReferenceQueue中保存的对象是WeakReference对象本身,而且是已经失去了它所引用值的WeakReference对象。
这样,我们都可以调用ReferenceQueue的poll()方法来检查是否有它所关心的非强可及对象被回收。如果返回非null,则我们可以将这个WeakReference对象清除掉。一般的代码:
SoftReference ref = null;
while ((ref = (EmployeeRef) q.poll()) != null) {
// 清除ref
}
完整的一个例子:WeakReference & ReferenceQueue实际使用的例子
在LeakCanary里,它是这样使用的:
// 随机产生的key
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference =
new KeyedWeakReference(watchedReference, key, referenceName, queue);
final class KeyedWeakReference extends WeakReference<Object> {
public final String key;
public final String name;
KeyedWeakReference(Object referent, String key, String name,
ReferenceQueue<Object> referenceQueue) {
super(checkNotNull(referent, "referent"), checkNotNull(referenceQueue, "referenceQueue"));
this.key = checkNotNull(key, "key");
this.name = checkNotNull(name, "name");
}
}
代码片段2 —— 如何判断泄漏?
入口是在RefWatcher.ensureGone()方法。
1. 首先把已经已经回收的对象的对应的key,从retainedKeys移除
private void removeWeaklyReachableReferences() {
KeyedWeakReference ref;
while ((ref = (KeyedWeakReference) queue.poll()) != null) {
retainedKeys.remove(ref.key);
}
}
从上面的分析,可以知道,被监控的对象与queue绑定在了一起,只要被回收,那么queue中就有了值。
2. 判断这个被监控的对象的key是否在retainedKeys里
if (gone(reference)) {
return DONE;
}
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
如果不在retainedKeys里,则表示不在,则表示回收了。
3. 执行GC
立即把所有 WeakReference 引用的对象回收。
gcTrigger.runGc();
4. 重复1、2步骤
如果被监控的对象的key不在retainedKeys里,则已经通过GC回收了。
5. 利用 heapDumper 把内存情况 dump 成文件,并调用 heapdumpListener 进行内存分析,进一步确认是否发生内存泄漏
File heapDumpFile = heapDumper.dumpHeap();
if (heapDumpFile == RETRY_LATER) {
// Could not dump the heap.
return RETRY;
}
6. 如果确认发生内存泄漏,调用 DisplayLeakService 发送通知
heapdumpListener.analyze(
new HeapDump(heapDumpFile, reference.key, reference.name, excludedRefs, watchDurationMs,
gcDurationMs, heapDumpDurationMs));
这样,核心的内存泄漏检测机制便看完了。
代码片段3 —— 如何dump内存情况?
这个整个的代码都是在AndroidHeapDumper
里。
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;
}
}
private void showToast(final FutureResult<Toast> waitingForToast) {
mainHandler.post(new Runnable() {
@Override public void run() {
final Toast toast = new Toast(context);
toast.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
toast.setDuration(Toast.LENGTH_LONG);
LayoutInflater inflater = LayoutInflater.from(context);
toast.setView(inflater.inflate(R.layout.leak_canary_heap_dump_toast, null));
toast.show();
// Waiting for Idle to make sure Toast gets rendered.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
waitingForToast.set(toast);
return false;
}
});
}
});
}
这里是整个的dump的逻辑,核心的代码就是Debug.dumpHprofData()
。
技术细节点:
这个里面有个这个代码:
FutureResult<Toast> waitingForToast = new FutureResult<>();
使用这个进行了等待,最长5s(这个时间也是ANR产生的时间),因为GC不是马上执行,另外在showToast里Looper.myQueue().addIdleHandler
,这个也是表示等待这个Looper Message都执行完之后才去采集。
代码片段4 —— 如何分析hump文件的?
入口:
public final class ServiceHeapDumpListener implements HeapDump.Listener {
private final Context context;
private final Class<? extends AbstractAnalysisResultService> listenerServiceClass;
public ServiceHeapDumpListener(Context context,
Class<? extends AbstractAnalysisResultService> listenerServiceClass) {
setEnabled(context, listenerServiceClass, true);
setEnabled(context, HeapAnalyzerService.class, true);
this.listenerServiceClass = checkNotNull(listenerServiceClass, "listenerServiceClass");
this.context = checkNotNull(context, "context").getApplicationContext();
}
@Override public void analyze(HeapDump heapDump) {
checkNotNull(heapDump, "heapDump");
HeapAnalyzerService.runAnalysis(context, heapDump, listenerServiceClass);
}
}
它实际会走到HeapAnalyzer
类:
至此,LeakCanary的源码部分已经分析完毕,它还有一些其他的技术点可以学习,详见:
细节点 — 1 细节点 — 2 细节点 — 3 细节点 — 4
最后附上一张完整的LeakCanary分析的流程图: