LeakCanary源码解析之——内存泄漏监测

前言

在日常开发中,肯定都使用过LeakCanary这个库来监测app的内存泄漏问题。LeakCanary会自动监测、分析以及上报内存泄漏,其工作主要是分为以下四步:

1、监测泄漏的对象

2、dump堆栈

3、分析堆栈

4、对泄漏进行归类,然后通过通知的方式上报内存泄漏

那么LeakCanary监测内存泄漏的原理是什么呢,怎么判断一个Activity或者Fragment被泄漏了呢?本篇文章就从源码的角度来对LeakCanary工作的第一步:监测泄漏的对象 来进行剖析。

本次分析源码基于:

1
2
3
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
}

对应的github官网:https://github.com/square/leakcanary

有一个需要注意的点:

LeakCanary 2.x版本相比之前的1.x版本相比有比较大的改动,包括集成方式的改变以及使用kotlin进行了重写,如果之前项目中集成的是1.x版本,想升级到2.x版本的话,可以参考官网的升级文档:
https://square.github.io/leakcanary/upgrading-to-leakcanary-2.0/

本次源码分析是基于kotlin的,如果对kotlin不太了解,可以上kotlin中文官网学习一下:
https://www.kotlincn.net/docs/reference/

关于LeakCanary更多资料参考官网:https://square.github.io/leakcanary/

源码分析

根据官方文档,升级到2.x版本之后,只需要在gradle集成一下leakcanary就行了,在2.x版本之前是需要应用的Application里面主动调用以下代码来安装Leakcanary的:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ExampleApplication extends Application {

@Override public void onCreate() {
super.onCreate();
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
// Normal app init code...
}
}

那么2.x版本是怎么做到自动自动安装Leakcanary的呢?到这里大家可能会想到使用ContentProvider?

LeakCanary安装

没错,在Leakcanary里面有一个AppWatcherInstaller类,继承自ContentProvider:

1
2
3
4
5
6
7
8
9
10
11
/**
* Content providers are loaded before the application class is created. [AppWatcherInstaller] is
* used to install [leakcanary.AppWatcher] on application start.
*/
internal sealed class AppWatcherInstaller : ContentProvider() {

override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}

在manifest里面的定义如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.leakcanary.objectwatcher" >

<uses-sdk android:minSdkVersion="14" />

<application>
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false" />
</application>

</manifest>

可以通过leak_canary_watcher_auto_install开关控制其是否enable。

从AppWatcherInstaller可以看出在onCreate方法里面调用了AppWatcher.manualInstall(application)方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
object AppWatcher {
/**
* [AppWatcher] is automatically installed in the main process on startup. You can
* disable this behavior by overriding the `leak_canary_watcher_auto_install` boolean resource:
*
*
* <?xml version="1.0" encoding="utf-8"?>
* <resources>
* <bool name="leak_canary_watcher_auto_install">false</bool>
* </resources>
*
*
* If you disabled automatic install then you can call this method to install [AppWatcher].
*/
fun manualInstall(application: Application) {
InternalAppWatcher.install(application)
}
}

这个AppWatcher是一个单例,如果我们通过设置leak_canary_watcher_auto_install把AppWatcher自动安装给关掉了,外部可以直接调用AppWatcher.manualInstall(application)方法进行手动安装。AppWatcher还提供了一个Config类来进行一些配置,比如配置是否要监测fragment销毁,是否要监测ViewModel销毁以及监测时长等等,具体可以深入AppWatcher里面去查看,config使用方式为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Builder for [Config] intended to be used only from Java code.
*
* Usage:
*
* AppWatcher.Config config = AppWatcher.getConfig().newBuilder()
* .watchFragmentViews(false)
* .build();
* AppWatcher.setConfig(config);
*
*
* For idiomatic Kotlin use `copy()` method instead:
*
* AppWatcher.config = AppWatcher.config.copy(watchFragmentViews = false)
*
*/

在AppWatcher.manualInstall里面又调用了InternalAppWatcher.install方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
internal object InternalAppWatcher {

private val checkRetainedExecutor = Executor {
mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
}
val objectWatcher = ObjectWatcher(
clock = clock,
checkRetainedExecutor = checkRetainedExecutor,
isEnabled = { true }
)

fun install(application: Application) {
checkMainThread()
if (this::application.isInitialized) {
return
}
InternalAppWatcher.application = application
if (isDebuggableBuild) {
SharkLog.logger = DefaultCanaryLog()
}

val configProvider = { AppWatcher.config }
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
onAppWatcherInstalled(application)
}
}

在InternalAppWatcher的install当中,首先检查是否在主线程,如果不是在主线程就会抛crash。然后分别调用了ActivityDestoryWatcher.install方法以及FragmentDestroyWatcher.install方法,传入了全局的AppWatcher.config配置以及一个ObjectWatcher对象。这个ObjectWatcher才是真正的主角,稍后会讲到。接下来看下ActivityDestroyWatcher的实现原理。FragmentDestroyWatcher的实现原理也是差不多的,只不过是监听fragment的destory的回调而已,感兴趣的可以看下

ActivityDestroyWatcher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
internal class ActivityDestroyWatcher private constructor(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) {

private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
if (configProvider().watchActivities) {
objectWatcher.watch(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
}

companion object {
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> Config
) {
val activityDestroyWatcher =
ActivityDestroyWatcher(objectWatcher, configProvider)
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
}
}

可以看出install方法里面就是往application里面注册了一个ActivityLifecycleCallbacks,当activity销毁的时候,就会调用objectWatcher的watch方法来观察这个对象。那么监测activity是否泄漏的逻辑肯定是在这个watch方法里面了!刚刚前面前面也说了ObjectWatcher才是真正的主角,那么我们来重点分析一下这个ObjectWatcher:

ObjectWatcher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
/**
* [ObjectWatcher] can be passed objects to [watch]. It will create [KeyedWeakReference] instances
* that reference watches objects, and check if those references have been cleared as expected on
* the [checkRetainedExecutor] executor. If not, these objects are considered retained and
* [ObjectWatcher] will then notify the [onObjectRetainedListener] on that executor thread.
*
* [checkRetainedExecutor] is expected to run its tasks on a background thread, with a significant
* to give the GC the opportunity to identify weakly reachable objects.
*
* [ObjectWatcher] is thread safe.
*/
// Thread safe by locking on all methods, which is reasonably efficient given how often
// these methods are accessed.
class ObjectWatcher constructor(
private val clock: Clock,
private val checkRetainedExecutor: Executor,
/**
* Calls to [watch] will be ignored when [isEnabled] returns false
*/
private val isEnabled: () -> Boolean = { true }
) {

private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()

/**
* References passed to [watch].
*/
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()

private val queue = ReferenceQueue<Any>()

@Synchronized fun addOnObjectRetainedListener(listener: OnObjectRetainedListener) {
onObjectRetainedListeners.add(listener)
}

@Synchronized fun removeOnObjectRetainedListener(listener: OnObjectRetainedListener) {
onObjectRetainedListeners.remove(listener)
}

@Synchronized fun watch(watchedObject: Any) {
watch(watchedObject, "")
}

/**
* Watches the provided [watchedObject].
*
* @param description Describes why the object is watched.
*/
@Synchronized fun watch(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
val reference =
KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
SharkLog.d {
"Watching " +
(if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
(if (description.isNotEmpty()) " ($description)" else "") +
" with key $key"
}

watchedObjects[key] = reference
checkRetainedExecutor.execute {
moveToRetained(key)
}
}

@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}

private fun removeWeaklyReachableObjects() {
// WeakReferences are enqueued as soon as the object to which they point to becomes weakly
// reachable. This is before finalization or garbage collection has actually happened.
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
}

在watch方法当中,首先会调用一个removeWeaklyReachableObjects方法。在这里先引入一个知识点:

Java的WeakRefrence可以关联一个queue,当弱引用保存的对象被回收了,就会把这个弱引用对象放入这个队列里面。

因此removeWeaklyReachableObjects方法主要做的事情是:从queue里面获取到一个弱引用对象,如果这个弱引用对象不为空,就把这个弱引用对象对应的对象从watchedObjects里面给移除掉,代表这个对象被回收了,没有泄漏。

执行removeWeaklyReachableObjects方法之后就会把传入的观察对象封装成一个KeyedWeakReference对象放入watchedObjects这个map当中保存起来。

最后会调用checkRetainedExecutor线程池来执行一个task。这个线程池的实现也挺简单的:

1
2
3
private val checkRetainedExecutor = Executor {
mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
}

就是延时一段时间来执行task,这个延时时间默认是5s,外部可配置。

延时一段时候之后,执行这个task,这个task里面会调用moveToRetained方法。可以看出这个方法也会先调用一下removeWeaklyReachableObjects方法,把可以被回收的对象从map里面移掉,然后再判断map里面是否还保存这个key对应的弱引用对象,如果还保存说明可能发生内存泄漏了!这个时候就会通过onObjectRetainedListeners通知出去交给下一步:dump堆栈 进行处理。在dump堆栈这步里面会先执行gc来进行对象回收,然后再次通过removeWeaklyReachableObjects方法来判断是否真正发生了内存泄漏,如果出现了内存泄漏就会进行dump堆栈处理,最终通过通知的方式上报内存泄漏。

总结

以上主要是针对Leakcnary中的内存泄漏监测这一部分,从源码的角度进行了分析,总结如下:

1、通过ContentProvier来进行Leakcanary自动安装(也可以采取手动安装的方式)

2、通过Lifecycle来监听activity或者fragment的destory回调

3、利用Java当中WeakRefrenece+ReferenceQueue的特性来判断弱引用对象是否被泄漏了

Leakcanary监控到有内存泄漏只是第一步,后续还有dump堆栈,分析堆栈等,后续有时间也会对这些部分进行学习。

做为一个开源库,Leakcanary框架源码还是挺值得学习的,其思想非常值得我们学习,并且个人觉得里面的kotlin代码还是写的挺好的,是学习kotlin的好材料。