不深入的分析leakcanry2.0

Leakcanary2.0

Leakcanary

使用

1
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-2'

原理

Reference 、ReferenceQueue 详解

ReferenceQueue
引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中

WeakReference weakReference = new WeakReference(list, new ReferenceQueue>());

WeakReference 创建时,如果指定一个ReferenceQueue 对象,在垃圾回收器检测到被引用对象的可到达性更改后,垃圾回收器会将已注册的引用对象添加到ReferenceQueue队列中,等待ReferenceQueue处理。但是如果当 GC 过后引用对象仍然不被加入ReferenceQueue中,就有可能存在内存泄漏问题。


实现无需初始化启动

【译】你的Android库是否还在Application中初始化?

大概原理,gradle打包会将不同的manifest文件合并,从而在启动application的时候带动provider,实现初始化。

1
2
3
4
5
6
<application>
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:exported="false"/>
</application>

AppWatcherInstaller.kt

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

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


分析代码

InternalAppWatcher.kt

1
2
3
4
5
6
7
8
9
10
11
12
13
fun install(application: Application) {
SharkLog.logger = DefaultCanaryLog()
checkMainThread()
if (this::application.isInitialized) {
return
}
InternalAppWatcher.application = application

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

1.安装Activity监听

ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
1
2
3
4
5
6
7
8
9
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> Config
) {
val activityDestroyWatcher =
ActivityDestroyWatcher(objectWatcher, configProvider)
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}

构造activityDestroyWatcher对象,并注册到application的lifecyclecallbacks中

1
2
3
4
5
6
7
8
9
10
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"
)
}
}
}

configProvider()对应AppWather
objectWatcher是在install传入的参数,和1.5版本leakcanary原理类似,依旧是在Application的lifecycle执行onActivityDestroyed回调时,进行监控watch()

1.1 ObjectWatcher#watch

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Synchronized fun watch(
watchedObject: Any,
description: String
) {

removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
val reference =
KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)


watchedObjects[key] = reference
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
1
2
3
@Synchronized fun watch(watchedObject: Any) {
watch(watchedObject, "")
}

熟悉的味道

  • removeWeaklyReachableObjects()
    移除弱可达的对象,在GC发生之前
  • val reference =KeyedWeakReference() ObjectWatcher通过其来判断是否是若可达,用key来追踪是否关联ReferenceQueue
  • moveToRetained(key)
    先removeWeaklyReachableObjects,在执行所有onObjectRetainedListeners的onObjectRetained()

1.1.1 removeWeaklyReachableObjects()

首先清理一次watchedObjects中已经在保存在ReferenceQueue的引用,不再监控。

弱引用一旦变得弱可达,就会立即入队。这将在 finalization 或者 GC 之前发生。
也就是说,会被 GC 回收的对象引用,会保存在队列 queue 中。

1
2
3
4
5
6
7
8
9
10
11
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)
}

1.1.2 val reference =KeyedWeakReference()

KeyedWeakReference弱引用

1
2
3
A weak reference used by [ObjectWatcher] to determine which objects become weakly reachable
and which don't. [ObjectWatcher] uses [key] to keep track of [KeyedWeakReference] instances that
haven't made it into the associated [ReferenceQueue] yet.

随机生成key,将watchUptimeMillis,key,queue传入KeyedWeakReference中,构造出reference对象

大概可以理解为

1
WeakReference reference = new WeakReference(activity实例, new ReferenceQueue<WeakReference>());

1
2
3
4
5
6
7
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
val reference =
KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)

watchedObjects[key] = reference

将reference加入watchedObjects观察的map中。

1.1.3 moveToRetained(key)

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

再removeWeaklyReachableObjects()一遍,如果对应key的监控对象并没有remove掉(加入queue队列),则很可能是泄漏,会继续进行heapDumpTrigger操作。

InternalLeakCanary#onObjectRetained()
1
2
3
4
5
override fun onObjectRetained() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.onObjectRetained()
}
}
HeapDumpTrigger#onObjectRetained()
1
2
3
4
5
6
fun onObjectRetained() {
scheduleRetainedObjectCheck(
reason = "found new object retained",
rescheduling = false
)
}
1
2
3
4
5
6
7
8
9
10
11
private fun scheduleRetainedObjectCheck(
reason: String,
rescheduling: Boolean,
delayMillis: Long = 0L
) {

backgroundHandler.postDelayed({
checkScheduledAt = 0
checkRetainedObjects(reason)
}, delayMillis)
}

reason = “found new object retained”,

checkRetainedObjects(reason)
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

private fun checkRetainedObjects(reason: String) {


var retainedReferenceCount = objectWatcher.retainedObjectCount

if (retainedReferenceCount > 0) { //判断剩余引用的数量
gcTrigger.runGc()
retainedReferenceCount = objectWatcher.retainedObjectCount
}

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

if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
onRetainInstanceListener.onEvent(DebuggerIsAttached)
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_debugger_attached
)
)//发notification
scheduleRetainedObjectCheck(
reason = "debugger is attached",
rescheduling = true,
delayMillis = WAIT_FOR_DEBUG_MILLIS
)
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(
reason = "previous heap dump was ${elapsedSinceLastDumpMillis}ms ago (< ${WAIT_BETWEEN_HEAP_DUMPS_MILLIS}ms)",
rescheduling = true,
delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
)
return
}

SharkLog.d { "Check for retained objects found $retainedReferenceCount objects, dumping the heap" }
dismissRetainedCountNotification()
dumpHeap(retainedReferenceCount, retry = true)
}
判断剩余引用数量 > 0 时,gcTrigger.runGc()

同1.5时,常规做法

1
2
3
4
5
6
7
8
9
override fun runGc() {

// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perform a gc.
Runtime.getRuntime()
.gc()
enqueueReferences()
System.runFinalization()
}

检查剩余引用数量 checkRetainedCount()

retainedVisibleThreshold 默认是5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* When the app is visible, LeakCanary will wait for at least
* [retainedVisibleThreshold] retained instances before dumping the heap. Dumping the heap
* freezes the UI and can be frustrating for developers who are trying to work. This is
* especially frustrating as the Android Framework has a number of leaks that cannot easily
* be fixed.
*
* When the app becomes invisible, LeakCanary dumps the heap after
* [AppWatcher.Config.watchDurationMillis] ms.
*
* The app is considered visible if it has at least one activity in started state.
*
* A higher threshold means LeakCanary will dump the heap less often, therefore it won't be
* bothering developers as much but it could miss some leaks.
*
* Defaults to 5.
*/
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
private fun checkRetainedCount(
retainedKeysCount: Int,
retainedVisibleThreshold: Int
): Boolean {
val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
lastDisplayedRetainedObjectCount = retainedKeysCount
if (retainedKeysCount == 0) {//如果没有剩余引用 return true
SharkLog.d { "Check for retained object found no objects remaining" }
if (countChanged) {
//判断和上次display的数量是否一只,不一致show notification
onRetainInstanceListener.onEvent(NoMoreObjects)
showNoMoreRetainedObjectNotification()
}
return true
}
//如果小于5
if (retainedKeysCount < retainedVisibleThreshold) {
if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
if (countChanged) {
//发Event
onRetainInstanceListener.onEvent(BelowThreshold(retainedKeysCount))
}
//发notification
showRetainedCountNotification(
objectCount = retainedKeysCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_visible, retainedVisibleThreshold
)
)

//继续安排剩余object检查 reason不一样了 delay = 2s
scheduleRetainedObjectCheck(
reason = "found only $retainedKeysCount retained objects (< $retainedVisibleThreshold while app visible)",
rescheduling = true,
delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS
)
return true
}
}
//大于5 false 继续进行
return false
}
判断是否isDebuggerAttached

(这个我不懂)

1
2
3
4
5
scheduleRetainedObjectCheck(
reason = "debugger is attached",
rescheduling = true,
delayMillis = WAIT_FOR_DEBUG_MILLIS
)
如果上次dump 和现在dump的时间 < 60s 继续 ,然后return
1
2
3
4
5
6
7
8
9
if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {

scheduleRetainedObjectCheck(
reason = "previous heap dump was ${elapsedSinceLastDumpMillis}ms ago (< ${WAIT_BETWEEN_HEAP_DUMPS_MILLIS}ms)",
rescheduling = true,
delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
)
return
}
dumpHeap(retainedReferenceCount, retry = true)

判断有没有hprof,如果没有重新scheduleRetainedObjectCheck,如果有dumpheap

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
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean
) {
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
val heapDumpFile = heapDumper.dumpHeap()
if (heapDumpFile == null) {
if (retry) {
SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" }
scheduleRetainedObjectCheck(
reason = "failed to dump heap",
rescheduling = true,
delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
)
} else {
SharkLog.d { "Failed to dump heap, will not automatically retry" }
}

return
}
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
HeapAnalyzerService.runAnalysis(application, heapDumpFile)
}


2.安装fragment监听

FragmentDestroyWatcher.install(application, objectWatcher, configProvider)

1
2
3
4
5
6
7
8
9
10
application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
for (watcher in fragmentDestroyWatchers) {
watcher(activity)
}
}
})

2.1 AndroidOFragmentDestroyWatcher

  • onFragmentViewDestroyed
  • onFragmentDestroyed

在以上两个回调时,通过ObjectWatcher监视fragment 和view 对象,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

internal class AndroidOFragmentDestroyWatcher(
private val objectWatcher: ObjectWatcher,
private val configProvider: () -> Config
) : (Activity) -> Unit {
private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
val view = fragment.view
if (view != null && configProvider().watchFragmentViews) {
objectWatcher.watch(
view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
"(references to its views should be cleared to prevent leaks)"
)
}
}

override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
if (configProvider().watchFragments) {
objectWatcher.watch(
fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
)
}
}
}


}

3.处理初始化工作

onAppWatcherInstalled(application)

onAppWatcherInstalled对应init中internalLeakCanary对象,可以看到反射调用。

1
2
3
4
5
6
7
8
9
10
11
init {
val internalLeakCanary = try {
val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
leakCanaryListener.getDeclaredField("INSTANCE")
.get(null)
} catch (ignored: Throwable) {
NoLeakCanary
}
@kotlin.Suppress("UNCHECKED_CAST")
onAppWatcherInstalled = internalLeakCanary as (Application) -> Unit
}

3.1 InternalLeakCanary#invoke

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
override fun invoke(application: Application) {
this.application = application

checkRunningInDebuggableBuild()

//注册listener
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
//创建 AndroidHeapDumper
val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)
//初始化gctrigger
val gcTrigger = GcTrigger.Default

val configProvider = { LeakCanary.config }

val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
//初始化handler 可以看到绑定的looper
val backgroundHandler = Handler(handlerThread.looper)
//初始化HeapDumpTrigger
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
registerResumedActivityListener(application)
addDynamicShortcut(application)

disableDumpHeapInTests()
}

以上进行了一堆的初始化操作,比较重要的只有一条

1
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)

3.2 HeapDumpTrigger#onApplicationVisibilityChanged

这里解释了leakcanary如何自动监听内存泄漏

判断applicationvisible是否可见。如果不可见scheduleRetainedObjectCheck,delay 默认5s

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun onApplicationVisibilityChanged(applicationVisible: Boolean) {
if (applicationVisible) {
applicationInvisibleAt = -1L
} else {
applicationInvisibleAt = SystemClock.uptimeMillis()
// Scheduling for after watchDuration so that any destroyed activity has time to become
// watch and be part of this analysis.
scheduleRetainedObjectCheck(
reason = "app became invisible",
rescheduling = false,
delayMillis = AppWatcher.config.watchDurationMillis
)
}
}


Android开发者,是时候了解LeakCanary2.0了

LeakCanary 2.0原理