带你学开源项目:LeakCanary-如何检测活动是否泄漏

作者博客

http://wingjay.com/

源码地址

https://github.com/square/leakcanary

文章目录

  1. 前言

  2. LeakCanary 使用方式

  3. 从LeakCanary.install(this);开始

  4. RefWatcher如何监控活动是否被正常回收?

  5. 核心函数:ensureGone(reference)检测回收

  6. 内存泄漏检测小结

  7. 一些探讨关于LeakCanary有趣的问题

  8. 可以怎样来改造LeakCanary呢?

  9. 小结

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是否被正常回收,这就涉及到两个问题:

  1. 何时去检查它是否回收?

  2. 如何有效地检查它真的被回收?

所以觉得我们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里的钥移除,剩下的对应的对象都没有被回收。

  1. 确保首先调用removeWeaklyReachableReferences把已被回收的对象的键从retainKeys移除,剩下的键都是未被回收的对象;

  2. if(gone(reference))用来判断某个参考的关键是否仍在retainKeys里,若不在,表示已回收,否则继续;

  3. gcTrigger.runGc(); 手动出发GC,立即把所有WeakReference引用的对象回收;

  4. removeWeaklyReachableReferences(); 再次清理retainKeys,如果该引用还在retainKeys里(if(!gone(reference))),表示泄漏;

  5. 利用heapDumper把内存情况转储成文件,并调用heapdumpListener进行内存分析,进一步确认是否发生内存泄漏。

  6. 如果确认发生内存泄漏,调用DisplayLeakService发送通知。

至此,核心的内存泄漏检测机制便看完了。

6

内存泄漏检测小结

从上面我们大概了解了内存泄漏检测机制,大概是以下几个步骤:

  1. 利用application.registerActivityLifecycleCallbacks(lifecycleCallbacks)来监听整个生命周期内的活动onDestoryed事件;

  2. 当Activity某个被毁坏后,将它传给RefWatcher去做观测,确保其后续会被正常回收;

  3. RefWatcher首先把Activity使用KeyedWeakReference引用起来,并使用一个ReferenceQueue来记录该KeyedWeakReference指向的对象是否已被回收;

  4. AndroidWatchExecutor会在5秒后,开始检查这个弱引用内的Activity。是否被正常回收判断条件是:若Activity被正常回收,那么引用它的KeyedWeakReference会被自动放入的ReferenceQueue中。

  5. 判断方式是:先看对应Activity的KeyedWeakReference是否已经放入ReferenceQueue中;如果没有,则手动GC :; gcTrigger.runGc();然后再一次判断ReferenceQueue是否已经含有对应的KeyedWeakReference。若还未被回收,则认为可能发生内存泄漏。

  6. 利用HeapAnalyzer对dump的内存情况进行分析并进一步确认,若确定发生泄漏,则利用DisplayLeakService发送通知。

7

一些探讨关于LeakCanary有趣的问题

学习在了LeakCanary的源码之后,我想再提几个有趣的问题做些探讨。

LeakCanary 项目目录结构为什么这样分?

对于开发者而言,只需要使用到LeakCanary.install(this);这一句即可。那整个项目为什么要分成这么多个模块呢?

实际上,这里面每一个模块都有自己的角色。

  • leakcanary-watcher:这是一个通用的内存检测器,对外提供一个RefWatcher#watch(Object watchedReference),可以看出,它不仅能够检测Activity,还能监测任意常规的Java对象的泄漏情况。

  • leakcanary-android:这个模块是与Android世界的接入点,用来专门监测Activity的泄漏情况,内部使用了应用#registerActivityLifecycleCallbacks方法来监听onDestory事件,然后利用leakcanary-watcher来进行弱引用+手动GC机制进行监控。

  • leakcanary-analyzer:这个模块提供了HeapAnalyzer,用来对倾出来的内存进行分析并返回内存分析结果AnalysisResult,内部包含了泄漏发生的路径等信息供开发者寻找定位。

  • leakcanary-android-no-op:这个模块是专门给发布的版本用的,内部只提供了两个完全空白的类LeakCanary和RefWatcher,这两个类不会做任何内存泄漏相关的分析。为什么?因为LeakCanary本身会由于不断gc影响到app本身的运行,而且主要用于开发阶段的内存泄漏检测。因此对于释放可以禁用所有泄漏分析。

  • leakcanary-sample:这个很简单,就是提供了一个用法示例。

当活动被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的原理,并提出了一些有趣的问题,学习了一些实用的知识点。希望对读者有所启动,欢迎讨论。

今日推荐

Android_其他语言交互篇——Js、C#、C、C++

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值