初始化
LeakCanary 当前最新版本的初始化不需要显示调用 install 方法完成,原理是通过 ContentProvider 完成。
ContentProvider onCreate 方法在 handleBindApplication 中调用,并且在 Application 的 onCreate 之前。
接下来具体看 LeakCanary 实现的 ContentProvider
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
复制代码
在 onCreate 方法中调用了 AppWatcher.manualInstall(application) 进行初始化
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
checkMainThread()
// 省略判断代码
...
// Requires AppWatcher.objectWatcher to be set
LeakCanaryDelegate.loadLeakCanary(application)
watchersToInstall.forEach {
it.install()
}
}
复制代码
可以看到该方法有三个参数 上下文、任务延迟时间、待安装的监听器
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
监听通过 LifeCycle 实现,Activity 在 onActivityDestroyed 回调中触发检查,
Fragment
添加了 高版本、androidx、android.support.v4 支持的 fragment
RootView
包括 Toast 等顶层的 View,在 View 的 onDetachFromWindow 方法回调时触发检查
Service
通过添加 ActivityThread 中的 Handler 的 Callback 接受消息,在接受到 STOP_SERVICE 消息时, 通过添加代理的方法,记录将要结束生命周期的 Service,在检测到调用 serviceDoneExecuting (一般是 Service 的每个生命周期调用结束时调用)触发泄漏检查
检查内存泄漏
@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
watchedObjects[key] = reference
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
复制代码
为每一个被监听的对象生成一个 key,并生成一个弱引用记录在 watchedObjects 中,并启动一个任务 dump 内存信息
@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
复制代码
可以看到多次调用了 removeWeaklyReachableObjects 方法,当弱引用被回收后会被添加到队列中,则通过遍历队列,删除记录的观察对象,当删除结束后,watchedObjects 中仍存在记录,则发生了内存泄漏
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)
}
复制代码
dump 内存信息
通过提交任务执行到 HeadDumpTrigger 中 checkRetainedObjects
private fun checkRetainedObjects() {
val iCanHasHeap = HeapDumpControl.iCanHasHeap()
val config = configProvider()
// 1.不能触发 dump
if (iCanHasHeap is Nope) {
if (iCanHasHeap is NotifyingNope)
// 触发 gc,获取仍然遗留的对象数量
// 未能触发 dump,判断是否需要发送通知提示泄漏的数量
return
}
// 2.触发 gc,获取仍然遗留的对象数量,判断是否需要 dump
// 3.若 dump 发生的太快,则更新泄漏的数量不触发 dump
// 4.移除内存泄漏数量通知,开始 dump
dismissRetainedCountNotification()
val visibility = if (applicationVisible) "visible" else "not visible"
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility"
)
}
复制代码
上述过程中,多次调用了 checkRetainedCount 方法
private fun checkRetainedCount(
retainedKeysCount: Int,
retainedVisibleThreshold: Int,
nopeReason: String? = null
): Boolean {
// 1.若泄漏的对象都已经被回收,调用 showNoMoreRetainedObjectNotification,通知用户
val applicationVisible = applicationVisible
val applicationInvisibleLessThanWatchPeriod = applicationInvisibleLessThanWatchPeriod
// 2.若当前泄漏数量发生变化,打印日志
// 3.判断当前泄漏数量是否超过阈值,默认为 5
if (retainedKeysCount < retainedVisibleThreshold) {
// 3.1 若应用在前台或应用切换到后台的时间小于 watchDuration 默认为 5s
// 则发送通知,并延迟执行尝试 dump,return true
if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
if (countChanged) {
onRetainInstanceListener.onEvent(BelowThreshold(retainedKeysCount))
}
showRetainedCountNotification(
objectCount = retainedKeysCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_visible, retainedVisibleThreshold
)
)
// 3.2 将任务提交到 HandlerThread 中执行
scheduleRetainedObjectCheck(
delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS
)
return true
}
}
return false
}
复制代码
即 checkRetainedCount 方法 return true,不会发生 dump
dump
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean,
reason: String
) {
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
// 使用 AndroidHeadDumper 进行 dump
when (val heapDumpResult = heapDumper.dumpHeap()) {
is NoHeapDump -> {
// 1.1.失败且可重试,则进行重试,默认延迟 5s
// 1.2.发送通知
}
is HeapDump -> {
// 2 已经 dump 完成,清除已经完成 dump 的观察对象,通过 HeapAnalyzerService 分析日志
}
}
}
复制代码
通过 AndroidHeapDumper 进行 Dump
override fun dumpHeap(): DumpHeapResult {
val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return NoHeapDump
// 显示 toast,LeakCanary is dumping memory to investigate leaks.
// 发送正在 dump 的通知
return try {
// 调用 VMDebug dumpHprofData 方法进行 dump
val durationMillis = measureDurationMillis {
Debug.dumpHprofData(heapDumpFile.absolutePath)
}
if (heapDumpFile.length() == 0L) {
SharkLog.d { "Dumped heap file is 0 byte length" }
NoHeapDump
} else {
HeapDump(file = heapDumpFile, durationMillis = durationMillis)
}
} catch (e: Exception) {
SharkLog.d(e) { "Could not dump heap" }
// Abort heap dump
NoHeapDump
} finally {
cancelToast(toast)
notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
}
}
复制代码
通过 HeapAnalyzerService 分析 Hprof 文件,该 service 继承自 IntentService