讲完了通过手动分析Hprof来进行内存泄漏检测的方式,我们再来看自动分析的方式。自动分析可以帮助我们快速、及时地发现内存泄漏问题,常用的自动分析工具主要是LeakCanary。
1.LeakCanary的使用方式
LeakCanary的使用方式比较简单,只需要在dependencies依赖配置中引入LeakCanary的库即可。最新的LeakCanary已经不需要初始化的代码,引入即可开启使用。
我们在引入LeakCanary后再次运行示例程序中的内存泄漏案例,就可以看到LeakCanary生效且自动检测到发生了内存泄漏。检测到内存泄漏后,它会抓取堆栈并通过通知栏进行提醒,如图2-10所示,单击通知栏就会跳转到内存泄漏详情页,如图2-11所示。通过详情页,我们就能看到泄漏的对象有哪些以及持有这些对象的引用链。因为LeakCanary在内存泄漏发生时,会抓取并分析Hprof文件,由于这个过程会导致程序发生卡顿,所以只建议在测试包中使用LeakCanary。
图2-10 LeakCanary通知提醒
图2-11 内存泄漏详情页
2.LeakCanary原理
LeakCanary非常实用,在实际项目中得到广泛应用,但仅了解LeakCanary如何使用是无法应对复杂场景的。比如,我们可能需要用LeakCanary来进行二次开发,增加一些定制化的检测能力,或配置一些定制化的属性等,所以我们还需要了解LeakCanary原理。另外,LeakCanary的检测原理也是面试中常见的考点之一。
LeakCanary的本质是利用Java中的WeakReference这个弱引用对象的特点来进行内存泄漏检测,如果一个对象仅被弱引用,则当执行GC时,系统会清除该弱引用对象。LeakCanary会将Activity封装成WeakReference(弱引用对象),当Acitvity执行onDestory后再主动进行GC操作。如果此时该Activity的弱引用没有被回收,则判定该Activity发生了内存泄漏。笔者通过简化的流程代码来加深读者对检测流程的理解,主要流程如下。
1)LeakCanary会在应用启动时通过系统提供的ActivityLifecycleCallbacks来注册监听每个Activity的生命周期。从如下实现代码中可以看到,LeakCanary创建了继承自ActivityLifecycleCallbacks的ActivityRefWatcher对象,并在Activity执行销毁回调时调用了refWatcher.watch方法。
2)refWatcher.watch方法会将该Activity的引用包装成一个KeyedWeakReference对象,并加入自定义的ReferenceQueue对象中,代码如下。
3)接着RefWatcher会调用ensureGoneAsync方法来检测内存泄漏。需要注意的是,该方法调用后不会立即进行内存泄漏检测,而是通过WatchExecutor在主线程空闲时检测。检测的方式是主动触发一次GC,并检查ReferenceQueue中的引用是否被回收。如果发现有未被回收的引用,则说明该对象发生了内存泄漏,RefWatcher会通过AndroidHeapDumper对象将堆内存转存成一个Hprof文件,并将其传递给HeapAnalyzer进行分析,代码如下。
4)HeapAnalyzer会解析Hprof文件,找到泄漏对象的引用链,并生成一个LeakTrace对象(包含泄漏的类名、字段名、大小等信息),并将LeakTrace对象发送给DisplayLeakService。DisplayLeakService是一个运行在另一个进程中的服务,它会将泄漏信息展示在通知栏,并提供LeakActivity便于用户查看泄漏的详细信息。
LeakActivity对Hprof的抓取以及对引用链的分析都是通过HAHA这个第三方库实现的,这里不对此展开介绍,读者若对LeakActivity感兴趣可以进一步地去分析其中的代码细节。