作者博客
http://wingjay.com/
源码地址
https://github.com/square/leakcanary
文章目录
1
前言
OOM是Android开发中常见的问题,而内存泄漏往往是罪魁祸首。
为了简单方便的检测内存泄漏,Square开源了LeakCanary,它可以实时监测活动是否发生了泄漏,一旦发现就会自动弹出提示及相关的泄漏信息供分析。
本文的目的是试图通过分析LeakCanary源码来探讨它的活动泄漏检测机制。
2
LeakCanary 使用方式
将为了LeakCanary引入到我们的项目里,我们只需要做以下两步:
可以看出,关键最就是的LeakCanary.install(this);这么一句话,开启正式了LeakCanary的大门,未来它就会自动帮我们检测内存泄漏,并在发生泄漏是弹出通知信息。
3
从LeakCanary.install(this);开始
下面我们来看下它做了些什么?
首先,我们先看最重要的部分,就是:
先生成了一个RefWatcher,这个东西非常关键,从名字可以看出,的英文它用来watch Reference的,也就是用来一个监控引用的工具。再把然后refWatcher状语从句:我们自己提供的application传入到ActivityRefWatcher.installOnIcsPlus(application, refWatcher);这句里面,继续看。
创建了一个ActivityRefWatcher,大家应该能感受到,东西这个就是用来监控点的我们的Activity泄漏状况的,调用它watchActivities()方法,就可以开始进行监控了下面就是它监控的核心原理:
它向application里注册了一个ActivitylifecycleCallbacks的回调函数,可以用来监听Application整个生命周期所有Activity的生命周期事件。再看下这个lifecycleCallbacks是什么?
它只原来监听了所有Activity的onActivityDestroyed事件,当Activity被Destory时,调用ActivityRefWatcher.this.onActivityDestroyed(activity);函数。
猜测下,正常情况下,当一个这个函数应该activity被Destory时,那这个activity对象应该变成null才是正确的。如果没有变成null,那么就意味着发生了内存泄漏。
因此我们向,这个函数ActivityRefWatcher.this.onActivityDestroyed(activity);应该是用来监听activity对象是否变成了null,继续看。
可以看出,函数这个目标把activity对象传给了RefWatcher,去让它监控点的这个activity是否被正常回收了,若未被回收,则意味着发生了内存泄漏。
4
RefWatcher如何监控活动是否被正常回收
先我们来看看这个RefWatcher究竟的英文个什么东西?
这里面涉及到两个新的对象:AndroidHeapDumper和AndroidWatchExecutor,前者用来转储内存状态的,后者则是用来观看一个引用的监听器。具体原理后面再看。总之,这里已经生成好了一个RefWatcher对象了。
再看现在上面onActivityDestroyed(Activity activity)里调用的refWatcher.watch(activity);,来看下面这个下最为核心的watch(activity)方法,它了解如何的英文监控点的activity是否被回收的。
可以看到,它首先把我们传入的activity包装成了一个KeyedWeakReference(可以暂时看成一个普通的WeakReference),然后watchExecutor会去执行一个Runnable,这个Runnable会调用ensureGone(reference, watchStartNanoTime)函数。
看这个函数之前猜测下,知道我们watch函数本身就是用来监听activity是否被正常回收,这就涉及到两个问题:
所以觉得我们ensureGone函数本身要做的事正如它的名字,确保就是reference被回收掉了,否则就意味着内存泄漏。
5
核心函数:ensureGone(reference)检测回收
下面来看这个函数实现:
先这里解释来下WeakReference状语从句:ReferenceQueue的工作原理。
1.弱引用WeakReference
被强引用的对象就算发生OOM也永远不会被垃圾回收机回收;被弱引用的对象,只要被垃圾回收器发现就会立即被回收;被软引用的对象,具备内存敏感性,只有内存不足时才会被回收,常用来做内存敏感缓存器;虚引用则任意时刻都可能被回收,使用较少。
2.引用队列ReferenceQueue
我们常用一个WeakReference<Activity> reference = new WeakReference(activity);,这里我们创建了一个reference来弱引用到某个activity,当这个activity被垃圾回收器回收后,这个reference会被放入内部的ReferenceQueue中。也就是说,从队列ReferenceQueue取出来的所有reference,它们指向的真实对象都已经成功被回收了。
然后再回到上面的代码。
在一个活动传给RefWatcher时会创建一个唯一的对应这个活动,该密钥存入一个集合retainedKeys中。也就是说,所有我们想要观测的activity对应的retainedKeys唯一键都会被放入集合中。
基于我们对ReferenceQueue的了解,只要把列列中所有的参考取出来,并把对应retainKeys里的钥移除,剩下的对应的对象都没有被回收。
至此,核心的内存泄漏检测机制便看完了。
6
内存泄漏检测小结
从上面我们大概了解了内存泄漏检测机制,大概是以下几个步骤:
7
一些探讨关于LeakCanary有趣的问题
学习在了LeakCanary的源码之后,我想再提几个有趣的问题做些探讨。
LeakCanary 项目目录结构为什么这样分?
对于开发者而言,只需要使用到LeakCanary.install(this);这一句即可。那整个项目为什么要分成这么多个模块呢?
实际上,这里面每一个模块都有自己的角色。
当活动被destory后,LeakCanary多久后会去进行检查其是否泄漏呢?
在源码中可以看到,LeakCanary并不会在destory后立即去检查,而是让一个AndroidWatchExecutor去进行检查。它会做什么呢?
以看到,它首先会向主线程的MessageQueue添加一个IdleHandler。
什么是IdleHandler?我们知道活套会不断从的MessageQueue里取出消息并执行。当没有新的消息执行时,活套进入空闲状态时,取出就会IdleHandler来执行。
换句话说,IdleHandler就是 优先级别较低的 Message,只有当Looper没有消息要处理时才得到处理。而且,内部的queueIdle()方法若返回true,表示该任务一直存活,每次Looper进入Idle时就执行;反正,如返回false,则表示只会执行一次,执行完后丢弃。
那么,这件优先级较低的任务是什么呢?backgroundHandler.postDelayed(runnable, delayMillis);,runnable就是之前ensureGone()。
也就是说,当主线程空闲了,没事做了,开始向后台线程发送一个延时消息,告诉后台线程,5S(delayMillis)开始后检查Activity是否被回收了。
所以,当Activity发生destory后,首先要等到主线程空闲,然后再延时5s(delayMillis),才开始执行泄漏检查。
知识点:
1.如何创建一个优先级低的主线程任务,它只会在主线程空闲时才执行,不会影响到app的性能?
2.如何快速创建一个主/子线程处理程序?
3.如何快速判断当前是否运行在主线程?
System.gc()可以触发立即gc吗?如果不行那怎么才能触发即时gc呢?
在LeakCanary里,需要立即触发gc,并在之后立即判断弱引用是否被回收。这意味着该
gc必须能够立即同步执行。
常用的触发gc方法是System.gc(),那它能达到我们的要求吗?
我们来看下其实现方式:
注释里清楚说了,只是System.gc()建议垃圾回收器来执行回收,但是 不能保证真的去回收,从代码也能看出,必须先判断shouldRunGC才能决定是否真的要gc。
知识点:
那要怎么实现即时GC呢?
LeakCanary参考了一段AOSP的代码
8
可以怎样来改造LeakCanary呢?
忽略某些已知泄漏的类或活动
LeakCanary提供了ExcludedRefs类,可以向里面添加某些主动忽略的类。比如已知Android源代码里有某些内存泄漏,不属于我们App的泄漏,那么就可以排除掉。
另外,如果不想监控某些特殊的活动,那么可以在onActivityDestroyed(Activity activity)里,过滤掉特殊的活动,只对其它活动调用refWatcher.watch(activity)监控。
把内存泄漏数据上传至服务器
在LeakCanary提供了AbstractAnalysisResultService,它是一个intentService,接收到的意图内包含了HeapDump数据和AnalysisResult结果,我们只要继承这个类,实现自己的listenerServiceClass,就可以将数据和分析结果上传到我们自己的服务器上。
9
小结
本文通过源代码分析了LeakCanary的原理,并提出了一些有趣的问题,学习了一些实用的知识点。希望对读者有所启动,欢迎讨论。