内存泄漏,LeakCanary原理

339 阅读4分钟

一. Activity 内存泄漏的场景

1. 将 Context 或者 View 置为 static

2. 未解注册各种 Listener。(在 Activity 中可能会注册各种系统监听器,比如广播)

3. 非静态 Handler 导致 Activity 泄漏。(一般需要将其置为static,然后内部持有一个Activity的弱引用来避免内存泄漏)

4. 三方库使用 Context。(在三方库中将传入的 context 重新置为一个静态 static 类型。这种情况是一种隐形的 Activity 泄漏,在我们自己的项目中很难察觉出,所以平时开发过程中,尽量使用 Context.getApplicationContext,不要直接将 Activity 传递给其他组件。)

二. 如何检测内存泄漏

Java 中的 WeakReference 是弱引用类型,每当发生 GC 时,它所持有的对象如果没有被其他强引用所持有,那么它所引用的对象就会被回收

实现思路

LeakCanary 中对内存泄漏检测的核心原理就是基于 WeakReference 和 ReferenceQueue 实现的

当一个 Activity 需要被回收时,就将其包装到一个 WeakReference 中,并且在 WeakReference 的构造器中传入自定义的 ReferenceQueue。

然后给包装后的 WeakReference 做一个标记 Key,并且在一个强引用 Set 中添加相应的 Key 记录

最后主动触发 GC,遍历自定义 ReferenceQueue 中所有的记录,并根据获取的 Reference 对象将 Set 中的记录也删除

经过上面 3 步之后,还保留在 Set 中的就是:应当被 GC 回收,但是实际还保留在内存中的对象,也就是发生泄漏了的对象。

源码分析

按照正常流程,当 Activity 调用 onDestroy 方法时就说明这个 Activity 就已经处于无用状态了。因此我们需要监听到每一个 Activity 的 onDestroy 方法的调用。

LeakCanary 中监听 Activity 生命周期是由 ActivityRefWatch 来负责的,主要是通过注册 Android 系统提供的 ActivityLifecycleCallbacks,来监听 Activity 的生命周期方法的调用

当监听到 Activity 的 onDestroy 方法后,会将其传给 RefWatcher 的 watch 方法

RefWatcher是 LeakCanary 的一个核心类,用来检测一个对象是否会发生内存泄漏。主要实现是在 watch 方法中

1.生成一个随机的字符串 key,用来标识 WeakReference 的

2.将被检测对象包装到一个WeakReference 中,使用key;

3.调用 ensureGoneAsync 开始执行检测操作。

ensureGoneAsync()通过 WatchExecutor 执行了一个重载的方法 ensureGone。

ensureGone 中实现了内存泄漏的检测

1.遍历 ReferenceQueue 中所有的元素,并根据每个元素中的 key,相应的将集合 retainedKeys 中的元素也删除。

2.判断集合 retainedKeys 是否还包含被检测对象的弱引用,如果包含说明被检测对象并没有被回收,也就是发生了内存泄漏。

3.生成 Heap “堆”信息,并生成内存泄漏的分析报告,上报给程序开发人员。

LeakCanary 的实现原理其实比较简单,但是内部实现还有一些其他的细节值得我们注意。

内存泄漏的检测时机

这种内存泄漏的检测与分析是比较消耗性能的,因此为了尽量不影响 UI 线程的渲染,LeakCanary 也做了些优化操作。在 ensureGoneAsync 方法中调用了 WatchExecutor 的 execute 方法来执行检测操作

实际是向主线程 MessageQueue 中插入了一个 IdleHandler,IdleHandler 只会在主线程空闲时才会被 Looper 从队列中取出并执行。因此能够有效避免内存检测工作占用 UI 渲染时间。

通过 addIdleHandler 也经常用来做 App 的启动优化,比如在 Application 的 onCreate 方法中经常做 3 方库的初始化工作。可以将优先级较低、暂时使用不到的 3 方库的初始化操作放到 IdleHandler 中,从而加快 Application 的启动过程。不过个人感觉方法名叫 addIdleMessage 更合适一些,因为向 MessageQueue 插入的都是 Message 对象。

特殊机型适配

因为有些特殊机型的系统本身就存在一些内存泄漏的情况,导致 Activity 不被回收,所以在检测内存泄漏时,需要将这些情况排除在外。在 LeakCanary 的初始化方法 install 中,通过 excludedRefs 方法指定了一系列需要忽略的场景。这些场景都被枚举在 AndroidExcludedRefs 中

LeakCanary 如何检测其他类

LeakCanary 默认只能机检测 Activity 的泄漏,但是 RefWatcher 的 watch 方法传入的参数实际是 Object,所以理论上是可以检测任何类的。LeakCanary 的 install 方法会返回一个 RefWatcher 对象,我们只需要在 Application 中保存此 RefWatch 对象,然后将需要被检测的对象传给 watch 方法即可,