我们知道LeakCanary是用来检查Activity和Fragment是否存在内存泄漏的。那么他是如何监测的呢?我们本篇文章就来解析
监测Activity生命周期
public final class ActivityRefWatcher {
public static void install(@NonNull Context context, @NonNull 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);
}
};
}
实现很简单,就是注册Android原生的ActivityLifecycleCallbacks的监听接口,可以监测到当前自己app里所有Activity的销毁的回调。
同样,我们看看Fragment
class AndroidOFragmentRefWatcher implements FragmentRefWatcher {
private final RefWatcher refWatcher;
AndroidOFragmentRefWatcher(RefWatcher refWatcher) {
this.refWatcher = refWatcher;
}
private final FragmentManager.FragmentLifecycleCallbacks fragmentLifecycleCallbacks =
new FragmentManager.FragmentLifecycleCallbacks() {
@Override public void onFragmentViewDestroyed(FragmentManager fm, Fragment fragment) {
View view = fragment.getView();
if (view != null) {
refWatcher.watch(view);
}
}
@Override
public void onFragmentDestroyed(FragmentManager fm, Fragment fragment) {
refWatcher.watch(fragment);
}
};
@Override public void watchFragments(Activity activity) {
FragmentManager fragmentManager = activity.getFragmentManager();
fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true);
}
}
同样也是注册系统的Fragment的生命周期的接口,也就是FragmentLifecycleCallbacks。我们发现他们在Activity/Fragment销毁的时候,做了同一件事情,就是refWatcher.watch()方法,我们下面具体讲解如何监测。
看看watch在干嘛?
public void watch(Object watchedReference, String 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/Fragment包装到了KeyedWeakReference里面,这个类是什么?
final class KeyedWeakReference extends WeakReference<Object> {
public final String key;
public final String name;
KeyedWeakReference(Object referent, String key, String name,
ReferenceQueue<Object> referenceQueue) {
this.key = checkNotNull(key, "key");
this.name = checkNotNull(name, "name");
}
}
就是一个弱引用,弱引用的特点我们也知道,虚拟机一旦发现其不可达就会回收。
那我们看看之后走了什么,主要实现代码ensureGoneAsync:
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
long gcStartNanoTime = System.nanoTime();
long watchDurationMs = NANOSECONDS.toMillis(gcStartNanoTime - watchStartNanoTime);
removeWeaklyReachableReferences(); // 函数一
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);
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);
}
}
很简单,也就是遍历我们在queue中的存放的虚引用包装过的对象,找到销毁的,并从队列移除。
函数二:
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
也很简单,就是判断,到底还有没有对象没有释放了。
函数三:触发虚拟机的GC
函数四:dump 内存泄漏发生时的调用栈,改实现在AndroidHeapDumper中,我们看一下:
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;
}
// 代码块二
Notification.Builder builder = new Notification.Builder(context)
.setContentTitle(context.getString(R.string.leak_canary_notification_dumping));
Notification notification = LeakCanaryInternals.buildNotification(context, builder);
NotificationManager notificationManager =
(NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
int notificationId = (int) SystemClock.uptimeMillis();
notificationManager.notify(notificationId, notification);
// 代码块三
Toast toast = waitingForToast.get();
try {
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
cancelToast(toast);
notificationManager.cancel(notificationId);
return heapDumpFile;
} catch (Exception e) {
CanaryLog.d(e, "Could not dump heap");
// Abort heap dump
return RETRY_LATER;
}
}
我们看一下代码块一的实现,实际最主要的就是创建dump文件,大致的流程分为:
1、确认当前有没有未完成的dump文件,如果有,则直接返回RETRY_LATER。当然这些未完成的dump文件有个上限时间,超过上限时间,则认为他失效了。
2、删除失效的未完成dump。
3、如果1中没有正在进行的dump,则先申请存储权限,然后建立一个(随机字串)_pending.hrof的文件。_pending就是用来判断未完成的。
代码块二就是在生成通知。
代码块三则是真正dump文件的过程。调用的就是原生的接口
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
这个dump完成之后,就会生成hprof文件了。
所以其实LeakCanary的实现很简单,主要还是在使用原生的接口,通过检测Activity/Fragment的生命周期,可以知道是否页面关闭了。
然后通过弱引用包装一下关闭的Activity/Fragment,通过主动触发一次GC,然后再检测弱引用里面是否还包含这些Activity/Fragment,来判断是否内存泄漏。
最后也是通过Debug.dumpHprofData接口实现了,dump栈的能力。