Android开发者,是时候了解LeakCanary了


Android开发者,是时候了解LeakCanary了

文章插图
 
原理概述
关于LeakCanary的原理,官网上已经给出了详细的解释 。翻译过来就是:1.LeakCanary使用ObjectWatcher来监控Android的生命周期 。当Activity和Fragment被destroy以后,这些引用被传给ObjectWatcher以WeakReference的形式引用着 。如果gc完5秒钟以后这些引用还没有被清除掉,那就是内存泄露了 。
2.当被泄露掉的对象达到一个阈值,LeakCanary就会把JAVA的堆栈信息dump到.hprof文件中 。
3.LeakCanary用Shark库来解析.hprof文件,找到无法被清理的引用的引用栈,然后再根据对Android系统的知识来判定是哪个实例导致的泄露 。
4.通过泄露信息,LeakCanary会将一条完整的引用链缩减到一个小的引用链,其余的因为这个小的引用链导致的泄露链都会被聚合在一起 。
通过官网的介绍,我们很容易就抓住了学习LeakCanary这个库的重点:
  • LeakCanary是如何使用ObjectWatcher 监控生命周期的?
  • LeakCanary如何dump和分析.hprof文件的?
看官方原理总是感觉不过瘾,下面我们从代码层面上来分析 。本文基于LeakCanary 2.0 beta版 。
基本使用
LeakCanary的使用相当的简单 。只需要在module的build.gradle添加一行依赖,代码侵入少 。
就这样,应用非常简单就接入了LeakCanary内存检测功能 。当然还有一些更高级的用法,比如更改自定义config,增加监控项等,大家可以参考官网
源码分析
1. 初始化
和之前的1.x版本相比,2.0甚至都不需要再在Application里面增加install的代码 。可能很多的同学都会疑惑,LeakCanary是如何插入自己的初始化代码的呢? 其实这里LeakCanary是使用了ContentProvider来进行初始化 。我之前在介绍Android插件化系列三:技术流派和四大组件支持的时候曾经介绍过ContentProvider的特点,即在打包的过程中来自不同module的ContentProvider最后都会merge到一个文件中,启动app的时候ContentProvider是自动安装,并且安装会比Application的onCreate还早 。LeakCanary就是依据这个原理进行的设计 。具体可以参考【译】你的Android库是否还在Application中初始化?
我们可以查看LeakCanary源码,发现它在leakcanary-object-watcher-android的AndroidManifest.xml中有一个ContentProvider 。
然后我们查看AppWatcherInstaller的代码,发现内部是使用InternalAppWatcher进行的install 。
可以看到这里主要把Activity和Fragment区分了开来,然后分别进行注册 。Activity的生命周期监听是借助于Application.ActivityLifecycleCallbacks 。
而Fragment的生命周期监听是借助了Activity的ActivityLifecycleCallbacks生命周期回调,当Activity创建的时候去调用FragmentManager.registerFragmentLifecycleCallbacks方法注册Fragment的生命周期监听 。
【Android开发者,是时候了解LeakCanary了】最终,Activity和Fragment都将自己的引用传入了ObjectWatcher.watch()进行监控 。从这里开始进入到LeakCanary的引用监测逻辑 。
题外话:LeakCanary 2.0版本和1.0版本相比,增加了Fragment的生命周期监听,每个类的职责也更加清晰 。但是我个人觉得使用 (Activty)->Unit 这种lambda表达式作为类的写法不是很优雅,倒不如面向接口编程 。完全可以设计成ActivityWatcher和FragmentWatcher都继承自某个接口,这样也方便后续扩展 。
2. 引用监控
2.1 引用和GC
  1. 引用
    首先我们先介绍一点准备知识 。大家都知道,java中存在四种引用:
  • 强引用:垃圾回收器绝不会回收它,当内存空间不足,Java虚拟机宁愿抛出OOM
  • 软引用:只有在内存不足的时候JVM才会回收仅有软引用指向的对象所占的空间
  • 弱引用:当JVM进行垃圾回收时,无论内存是否充足,都会回收仅被弱引用关联的对象 。
  • 虚引用:和没有任何引用一样,在任何时候都可能被垃圾回收 。
一个对象在被gc的时候,如果发现还有软引用(或弱引用,或虚引用)指向它,就会在回收对象之前,把这个引用加入到与之关联的引用队列(ReferenceQueue)中去 。如果一个软引用(或弱引用,或虚引用)对象本身在引用队列中,就说明该引用对象所指向的对象被回收了 。
当软引用(或弱引用,或虚引用)对象所指向的对象被回收了,那么这个引用对象本身就没有价值了,如果程序中存在大量的这类对象(注意,我们创建的软引用、弱引用、虚引用对象本身是个强引用,不会自动被gc回收),就会浪费内存 。因此我们这就可以手动回收位于引用队列中的引用对象本身 。


推荐阅读