LeakCanary 是由 Square 开发的一款内存泄露检测工具。相比与用 IDE dump memory 的繁琐,它以轻便的日志被广大开发者所喜好。让咱们看看它是如何实现的吧。java
ps: Square 以著名框架 Okhttp 被广大开发者所熟知。api
分析一个框架,咱们能够尝试先分层。好的框架层次清晰,像TCP/IP那样,一层一层的封装起来。这里,我按照主流程大体分了一下。架构
一图流,你们能够参考这个图,来跟源码。 oracle
按照教程,咱们一般会有以下初始化代码:app
mRefWatcher = LeakCanary.install(this);
mRefWatcher.watch(this);
虽然是用户端的代码,不过做为分析框架的入口,不妨称为业务层。框架
这一层咱们考虑的是检测咱们的业务对象 Activity。固然你也能够用来检测 Service。dom
从业务层切入,咱们引出了两个类LeakCanary
、RefWatcher
,组成了咱们的 api 层。异步
这一层咱们要考虑如何对外提供接口,并隐藏内部实现。一般会使用
Builder
、单例
、适当的包私有权限
。ide
public final class LeakCanary {
public static @NonNull RefWatcher install(@NonNull Application application) {
return refWatcher(application)
.listenerServiceClass(DisplayLeakService.class)
.excludedRefs(AndroidExcludedRefs.createAppDefaults().build())
.buildAndInstall();
}
public static @NonNull AndroidRefWatcherBuilder refWatcher(@NonNull Context context) {
return new AndroidRefWatcherBuilder(context);
}
}
复制代码
咱们先看install()
,先拿到一个RefWatcherBuilder
,转而使用Builder
模式构造一个RefWatcher
做为返回值。 大概能够知道是框架的一些初始配置。忽略其余,直接看buildAndInstall()
。工具
public final class AndroidRefWatcherBuilder extends RefWatcherBuilder<AndroidRefWatcherBuilder> {
...
private boolean watchActivities = true;
private boolean watchFragments = true;
public @NonNull RefWatcher buildAndInstall() {
RefWatcher refWatcher = build();
if (refWatcher != DISABLED) {
...
if (watchActivities) { // 1
ActivityRefWatcher.install(context, refWatcher);
}
if (watchFragments) { // 2
FragmentRefWatcher.Helper.install(context, refWatcher);
}
}
return refWatcher;
}
}
复制代码
能够看到 1, 2 两处,默认行为是,监控 Activity 和 Fragment。 以 Activity为例:
public final class ActivityRefWatcher {
public static void install(@NonNull Context context, @NonNull RefWatcher refWatcher) {
...
application.registerActivityLifecycleCallbacks(activityRefWatcher.lifecycleCallbacks);
}
private final Application.ActivityLifecycleCallbacks lifecycleCallbacks =
new ActivityLifecycleCallbacksAdapter() {
@Override public void onActivityDestroyed(Activity activity) {
refWatcher.watch(activity);
}
};
}
复制代码
使用了Application.ActivityLifecycleCallbacks
,看来咱们基类里的watch()
是多余的。Fragment 也是相似的,就不分析了,使用了FragmentManager.FragmentLifecycleCallbacks
。
PS: 老版本默认只监控 Activity,watchFragments 这个字段是 2018/6 新增的。
以前的分析,引出了RefWatcher.watch()
,它能够检测任意对象是否正常销毁,不仅仅是 Activity。咱们来分析看看:
public final class RefWatcher {
private final WatchExecutor watchExecutor;
public void watch(Object watchedReference, String referenceName) {
...
String key = UUID.randomUUID().toString();
retainedKeys.add(key);
final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
}
复制代码
经过这个 watch(),咱们能够注意到这几点:
onDestory()
,特地设计成异步调用——WatchExecutor
。KeyedWeakReference
,干吗用的呢?咱们该怎么设计 WatchExecutor 呢?AsyncTask?线程池?咱们接着往下看
如今咱们来到了很是关键的一层,这一层主要是分析是否泄露,产物是.hprof文件
。 咱们日常用 IDE dump memory 的时候,生成的也是这种格式的文件。
接以前的分析,WatchExecutor
主要是用于异步任务,同时提供了失败重试的机制。
public final class AndroidWatchExecutor implements WatchExecutor {
private final Handler mainHandler;
private final Handler backgroundHandler;
public AndroidWatchExecutor(long initialDelayMillis) {
mainHandler = new Handler(Looper.getMainLooper());
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
handlerThread.start();
backgroundHandler = new Handler(handlerThread.getLooper());
...
}
@Override public void execute(@NonNull Retryable retryable) {
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}
...
}
复制代码
看来是使用了HandlerThread
。没啥说的,要注意一会儿线程Handler
的使用方式。以后便会回调ensureGone()
,注意此时执行环境已经切到子线程了。
分析下一步以前,咱们先介绍一下 ReferenceQueue
。
说白了,ReferenceQueue 提供了一种通知机制,以便在 GC 发生前,咱们能作一些处理。
好了,让咱们回到 RefWatcher。
final class KeyedWeakReference extends WeakReference<Object> {
public final String key; // 因为真正的 value 正等待回收,咱们追加一个 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");
}
}
public final class RefWatcher {
private final Set<String> retainedKeys; // 保存未回收的引用的 key。 watch()时 add, 在 queue 中找到则 remove。
private final ReferenceQueue<Object> queue; // 收集全部变得不可达的对象。
public void watch(Object watchedReference, String referenceName) {
...
String key = UUID.randomUUID().toString();
retainedKeys.add(key); // 1
final KeyedWeakReference reference = new KeyedWeakReference(watchedReference, key, referenceName, queue);
ensureGoneAsync(watchStartNanoTime, reference);
}
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
removeWeaklyReachableReferences(); // 2
...
if (gone(reference)) { // 3
return DONE;
}
gcTrigger.runGc(); // 4
removeWeaklyReachableReferences(); // 5
if (!gone(reference)) { // 6
// 发现泄漏
...
}
return DONE;
}
private boolean gone(KeyedWeakReference reference) {
return !retainedKeys.contains(reference.key);
}
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);
}
}
}
复制代码
咱们有这样的策略:用retainedKeys
保存未回收的引用的 key。
4-6. 引用还在,然而这里没有当即断定为泄漏,而是很谨慎的手动触发 gc,再次校验。
这里注意一点 Android 下边的 jdk 和 oracle 公司的 jdk 在一些方法的实现上有区别。好比这个 System.gc()
就被改了,再也不保证一定触发 gc。做者使用Runtime.getRuntime().gc()
做为代替。
了解更多:System.gc() 源码解读
public final class RefWatcher {
private final HeapDumper heapDumper;
private final HeapDump.Listener heapdumpListener;
Retryable.Result ensureGone(final KeyedWeakReference reference, final long watchStartNanoTime) {
...
gcTrigger.runGc();
removeWeaklyReachableReferences();
if (!gone(reference)) {
// 发现泄漏
File heapDumpFile = heapDumper.dumpHeap();
HeapDump heapDump = heapDumpBuilder.heapDumpFile(heapDumpFile)
...
.build();
heapdumpListener.analyze(heapDump);
}
return DONE;
}
}
复制代码
咱们跟进 heapDumper.dumpHeap(),略去一些 UI 相关代码:
public final class AndroidHeapDumper implements HeapDumper {
@Override @Nullable
public File dumpHeap() {
File heapDumpFile = leakDirectoryProvider.newHeapDumpFile();
...
try {
Debug.dumpHprofData(heapDumpFile.getAbsolutePath());
...
return heapDumpFile;
} catch (Exception e) { ... }
}
}
复制代码
最后用了 Android 原生的 api —— Debug.dumpHprofData()
,生成了堆快照。
生成 .hprof
以后,以后由 heapdumpListener.analyze(heapDump)
把数据转到下一层。其实这两层没啥好分析的,.hprof
已是标准的堆快照格式,平时用 AS 分析内存生成的也是这个格式。
因此,LeakCanary 在这一层只是帮咱们读取了堆中的引用链。而后,日志展现层也没啥说的,就一个 ListView。
最后,咱们能够看到一个优秀的框架须要那些东西:
分层
api层
有哪些接口以及业务层
怎么使用;而对于维护者来讲,不少时候只须要关心核心逻辑日志产生层
,UI层不怎么改动丑一点也不要紧。方便使用也方便维护。ReferenceQueue 的使用
new WeakReference()
。手动触发 gc
Runtime.getRuntime().gc()
是否能当即触发 gc,这点感受也比较含糊。这是一个 native 方法,依赖于 JVM 的实现,深究起来须要去看 Dalvik 的源码,先打一个问号。AOSP
里拷贝出来的。。。因此说,多看源码是个好习惯。leakcanary-no-op