Home LeakCanary使用和解析
Post
Cancel

LeakCanary使用和解析

使用

Maven地址

1
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'

因为是线下检测,所以依赖模式为debug,其余不用配置也可以直接使用了。

源码分析

核心原理

在Activity、Fragment、View等销毁时,利用弱引用依赖他们,当过5秒之后去进行检测和GC,若还存在未被回收,则表示有泄漏,打印对应的引用信息。

初始化

1
2
3
4
5
6
7
8
internal class MainProcessAppWatcherInstaller : ContentProvider() {

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

利用ContentProvider进行初始化,这样的好处是不需要单独手动初始化即可使用,坏处就是若三方库都采用这种方式,会比较影响启动速度。

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
@JvmOverloads
fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
    checkMainThread()
    if (isInstalled) {
        throw IllegalStateException(
            "AppWatcher already installed, see exception cause for prior install call", installCause
        )
    }
    check(retainedDelayMillis >= 0) {
        "retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
    }
    this.retainedDelayMillis = retainedDelayMillis
    if (application.isDebuggableBuild) {
        LogcatSharkLog.install()
    }
    // Requires AppWatcher.objectWatcher to be set
    LeakCanaryDelegate.loadLeakCanary(application)

    watchersToInstall.forEach {
        // 进行初始化
        it.install()
    }
    // Only install after we're fully done with init.
    installCause = RuntimeException("manualInstall() first called here")
}

fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
    // 默认能够检测的泄漏类型
    return listOf(
        ActivityWatcher(application, reachabilityWatcher),
        FragmentAndViewModelWatcher(application, reachabilityWatcher),
        RootViewWatcher(reachabilityWatcher),
        ServiceWatcher(reachabilityWatcher)
    )
}

默认会传入对Activity、Fragment、RootView、Service这四种类型的内存泄漏检测。

Activity的install

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ActivityWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

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

    override fun install() {
        application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
    }

    override fun uninstall() {
        application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
    }
}

利用ActivityLifecycleCallbacks在Activity即将销毁的时候去进行监听。当Activity销毁的时候,会调用reachabilityWatcher的expectWeaklyReachable方法传入activity

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
val objectWatcher = ObjectWatcher(
    clock = { SystemClock.uptimeMillis() },
    checkRetainedExecutor = {
        check(isInstalled) {
            "AppWatcher not installed"
        }
        // 利用handler发送到主线程进行延时,默认延时5秒
        mainHandler.postDelayed(it, retainedDelayMillis)
    },
    isEnabled = { true }
)

private val queue = ReferenceQueue<Any>()

@Synchronized override fun expectWeaklyReachable(
    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)
    }
}

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)
}

会对传入的对象生成一个随机key,然后把对象添加到弱引用当中去,然后传入弱引用队列,当对象被回收后,对象会被添加到队列中。所以第一个removeWeaklyReachableObjects方法则是取出队列中的元素,然后把watchedObjects中的对象移除掉。最后再把任务添加到executor中去。

1
2
3
4
5
6
7
8
9
@Synchronized 
private fun moveToRetained(key: String) {
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    if (retainedRef != null) {
        retainedRef.retainedUptimeMillis = clock.uptimeMillis()
        onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
}

在延时过后会再去检测是否有可能泄漏的对象,若还存在,则调用观察者的onObjectRetained方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
override fun onObjectRetained() = scheduleRetainedObjectCheck()

fun scheduleRetainedObjectCheck() {
    if (this::heapDumpTrigger.isInitialized) {
        heapDumpTrigger.scheduleRetainedObjectCheck()
    }
}

fun scheduleRetainedObjectCheck(
    delayMillis: Long = 0L
) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) {
        return
    }
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    backgroundHandler.postDelayed({
        checkScheduledAt = 0
        checkRetainedObjects()
    }, delayMillis)
}

然后会再两秒延时抛送到子线程中去

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
private fun checkRetainedObjects() {
    val iCanHasHeap = HeapDumpControl.iCanHasHeap()

    val config = configProvider()

    if (iCanHasHeap is Nope) {
        if (iCanHasHeap is NotifyingNope) {
            // Before notifying that we can't dump heap, let's check if we still have retained object.
            var retainedReferenceCount = objectWatcher.retainedObjectCount

            if (retainedReferenceCount > 0) {
                // 先gc再检测数量
                gcTrigger.runGc()
                retainedReferenceCount = objectWatcher.retainedObjectCount
            }

            val nopeReason = iCanHasHeap.reason()
            // 判断是否达到需要dump的标准,满足达到5个,如果没有持续循环2s的检查
            val wouldDump = !checkRetainedCount(
                retainedReferenceCount, config.retainedVisibleThreshold, nopeReason
            )

            if (wouldDump) {
                // 达到标准则直接dump
                val uppercaseReason = nopeReason[0].toUpperCase() + nopeReason.substring(1)
                onRetainInstanceListener.onEvent(DumpingDisabled(uppercaseReason))
                showRetainedCountNotification(
                    objectCount = retainedReferenceCount,
                    contentText = uppercaseReason
                )
            }
        } else {
            SharkLog.d {
                application.getString(
                    R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
                )
            }
        }
        return
    }

    var retainedReferenceCount = objectWatcher.retainedObjectCount

    if (retainedReferenceCount > 0) {
        gcTrigger.runGc()
        retainedReferenceCount = objectWatcher.retainedObjectCount
    }

    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

    val now = SystemClock.uptimeMillis()
    val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
    if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
        onRetainInstanceListener.onEvent(DumpHappenedRecently)
        showRetainedCountNotification(
            objectCount = retainedReferenceCount,
            contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
        )
        scheduleRetainedObjectCheck(
            delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
        )
        return
    }

    dismissRetainedCountNotification()
    val visibility = if (applicationVisible) "visible" else "not visible"
    dumpHeap(
        retainedReferenceCount = retainedReferenceCount,
        retry = true,
        reason = "$retainedReferenceCount retained objects, app is $visibility"
    )
}

最后的检测泄漏个数,若还存在,先gc再次检测,若泄漏个数达到5个则直接dump堆栈,若没有则持续2s的循环检测。

总结

LeakCanary利用弱引用的性质,在Activity销毁的时候,把Activity包装成为一个弱引用对象保存到map中,如何发送一个5秒的延迟去检测弱引用队列中是否有刚回收的对象,根据队列的元素key清除之前保存在map中的元素,若map中依然有元素,则表示可能存在内存泄漏。然后发送子线程延迟两秒的事件,去进行gc和检测,若最后map中元素依然不为0,则表示有真实的泄漏。当元素个数小于5时会循环检测。若元素个数大于等于5则dump出对应的堆栈。

This post is licensed under CC BY 4.0 by the author.