1
2
3
4dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
}
只需要这样简单配置,就能接入LeakCanary内存泄漏分析,到底是怎样做到的?
我们将这个问题分成两个问题:
- 如何自动进行初始化的;
- 如何检测到内存泄漏的。
如何自动进行初始化的
这部分,我们可以分成两部分去理解——自动和初始化。
自动
这一切还要从ActivityThread
说起。ActivityThread
中,执行了一些应用启动的初始化工作,在ActivityThread
源码中,我们可以看到其内部类class H extends Handler
的handleMessage
方法中,有很多与应用相关的一些基本操作,比如BIND_APPLICATION, EXIT_APPLICATION, CREATE_SERVICE, BIND_SERVICE等,其中需要我们关注的是BIND_APPLICATION。
1 | public void handleMessage(Message msg) { |
我们可以看到,其中调用了handleBindApplication
方法。进入这个方法查看。
1 |
|
从这个方法中,我们可以找到这样一段代码,需要重点关注的是,ContentProvider
的初始化是先于Application.onCreate
的,且是被ActivityThread
自动执行的。
接下来再看LeakCanary源码。找到AppWatcherInstaller.kt这个类。
1 | /** |
我们可以看到,这个类是一个ContentProvider
的子类,其query, insert等方法根本没有实际作用,有实际作用的只有onCreate
方法,在这个方法中,执行了AppWatcher
的install工作。
这里我们就可以看出来,LeakCanary就是利用ContentProvider
的onCreate
方法自动执行的特性,来自动“安装”这个类库的。
初始化
通过追踪AppWatcher.manualInstall(application)
这句代码,我们可以追踪到InternalLeakCanary.kt的install
方法,如下:
1 | fun install(application: Application) { |
我们可以看到,先后执行了ActivityDestroyWatcher.install
,FragmentDestroyWatcher.install
和onAppWatcherInstalled(application)
方法。
其中在onAppWatcherInstalled
创建了LeakCanary图标的快捷方式,用于方便查看内存泄漏的路径信息。最终实现的具体过程可以查看InternalLeakCanary.kt的addDynamicShortcut
方法。
其他的两段代码——ActivityDestroyWatcher.install
和FragmentDestroyWatcher.install
,分别对应着两个类——ActivityDestroyWatcher
和FragmentDestroyWatcher
。这两个类相对来说比较简单,主要工作就是执行了application.registerActivityLifecycleCallbacks
这段代码,目的是为了监听每个Activity的onDestroy事件。这也是判断该Activity是否泄漏的开端。
以ActivityDestroyWatcher
为例,其ActivityLifecycleCallback中代码如下:
1 | private val lifecycleCallbacks = |
我们可以看到,这其中,最终是objectWatcher
来进行内存泄漏监控的。
如何检测到内存泄漏的
这里涉及到两个关键的类:**ObjectWatcher
和KeyedWeakReference
**。
KeyedWeakReference
是WeakReference
的子类,添加了额外的属性,代码十分简单,如下:
1 | class KeyedWeakReference( |
接下来我们看ObjectWatcher
中的watchObject
方法。
1 | /** |
这里分为三步:
- 执行
removeWeaklyReachableObjects()
方法,这个方法之后讲到; - 生成一个
KeyedWeakReference
对象,并将这个对象添加到watchedObjects
去; - 定时执行
moveToRetained
方法。
我们先看第二步,生成
KeyedWeakReference
对象时候,传入了一个一个ReferenceQueue
对象,这是检测对象是否被回收的关键。假如一个对象O,被弱引用WR持有的时候,同时这个弱引用WR在构造时候传入了一个ReferenceQueue
对象Q,则这个对象O被回收时候,WR将会被添加到Q中去,这样,通过检测Q中有没有值,便可以知道O有没有被回收掉。这也就是第一步做的事。接下来我们查看
removeWeaklyReachableObjects
方法中做了什么。
1 | private fun removeWeaklyReachableObjects() { |
在这个方法中,从queue中取值,取出来ref,则说明被ref修饰的对象已经被回收了,则将这个弱引用ref从watchedObjects
清除掉。
- 接下来到了第三步,这一步实际上是一个定时5秒(LeakCanary默认)去将watchedObjects中残留的引用,移入到
retainedObjects
中去。我们来看其中代码:
1 | private fun moveToRetained(key: String) { |
执行这个任务的Executor实际实现在InternalAppWatcher.kt中,代码如下:
1 | private val checkRetainedExecutor = Executor { |
我们发现,在moveToRetained
中,还是先执行了removeWeaklyReachableObjects
这一方法。目的是再次清除已经被回收的对象。如果经过这一步,仍然有引用留在watchedObjects中,则可以认为,这些对象泄漏了。
1 | /** |
总结
不要在发行版本中使用LeakCanary,因为一系列初始化动作,可能会导致应用启动较慢。如果要用,请使用LeakCanary-Object-Watcher,或者直接使用Buggly这样的成熟框架。
Author: boybeak