前言
偶然看到一篇文章,思路清晰写得很好,但有些细节和现有版本不太一样,自己看了一遍做了一些调整与补充,加深印象。本文字数达6k,大部分为源码,请配合源码食用,否则没有什么意义(PS:性能不好的设备可能展示不完整,请及时跟上时代)。
2019 年 11 月,LeakCanary2 正式版发布,和 LeakCanary1 相比,LeakCanary2 有以下改动:
其中,将 Heap 分析模块作为一个独立的模块,是一个非常不错的改动。这意味着,可以基于 Shark 来做很多有意思的事情,比如,用于线上分析或者开发一个”自己”的 LeakCanary。
整体架构
在分析源码之前,我们先看 LeakCanary 的整体结构,这有助于我们对项目整体设计上有一定理解。LeakCanary2 有以下几个模块:
- leakcanary-android
集成入口模块,提供 LeakCanary 安装,公开 API 等能力 - leakcanary-android-core
核心模块 - leakcanary-object-watcher, leakcanary-object-watcher-android, leakcanary-object-watcher-androidx, leakcanary-object-watcher-support-fragments
对象实例观察模块,在 Activity,Fragment 等对象的生命周期中,注册对指定对象实例的观察,有 Activity,Fragment,ViewModel,Service 等 - shark-android
提供特定于 Android 平台的分析能力。例如设备的信息,Android 版本,已知的内存泄露问题等 - shark
hprof 文件解析与分析的入口模块 - shark-graph
分析堆中对象的关系图模块 - shark-hprof
解析 hprof 文件模块 - shark-log
日志模块
集成方式
首先,我们从集成方式入手,LeakCanary1 的依赖为:
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3'
}接着在 Application 中调用 LeakCanary.install() 方法。而 LeakCanary2 集成则要简单不少,只需要增加以下依赖即可:
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6'
}也就是说 LeakCanary2 实现了自动调用 install() 方法,实现方式可能大部分人都能猜到,就是使用的 ContentProvider,相关代码位于 leakcanary-object-watcher-android 模块中的 AppWatcherInstaller.kt 中。
AppWatcherInstaller 继承 ContentProvider,重写了 onCreate() 方法,这里利用的是,注册在 Manifest 文件中的 ContentProvider,会在应用启动时,由 ActivityThread 创建并初始化。
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}AppWatcherInstaller 有两个实现类:
一个是 MainProcess,当我们使用 leakcanary-android 模块时,会默认使用这个,表示在当前 App 进程中使用 LeakCanary。
另外一个类为 LeakCanaryProcess,当使用 leakcanary-android-process 模块代替 leakcanary-android 模块时,则会使用这个类,我们可以看下 leakcanary-object-watcher-android 的 Manifest 文件:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.leakcanary.objectwatcher" >
<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>AppWatcher
Watcher 功能的入口位于 AppWatcher.manualInstall(application) 方法中,这个方法的调用时机则是我们上面说到的 AppWatcherInstaller.onCreate() 中。
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
checkMainThread()
check(!isInstalled) {
"AppWatcher already installed"
}
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()
}
} 这里主要做了两件事,首先是 Activity 和 Fragment 对象的注册观察:
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
注意默认参数传递:
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}不传递参数时将默认初始化以上四个监听器。以ActivityWatcher为例:
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)
}
}调用 registerActivityLifecycleCallbacks() 方法注册 Activity 生命周期回调。在每个 Activity.onDestory 回调中,将每个 Activity 对象加到观察列表中。
/**
* References passed to [watch].
*/private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
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)
}
}首先我们要知道 KeyedWeakReference 继承于 WeakReference,弱引用是不会阻止 GC 回收对象的,同时我们可以在构造函数中传递一个 ReferenceQueue,用于对象被 GC 后存放的队列。
PS:将弱引用和引用队列 ReferenceQueue 关联起来,如果弱引用referent持有的对象被GC回收,JVM就会把这个弱引用加入到与之关联的引用队列referenceQueue中。
/** * 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. * * [heapDumpUptimeMillis] should be set with the current time from [Clock.uptimeMillis] right * before dumping the heap, so that we can later determine how long an object was retained. */class KeyedWeakReference( referent: Any, val key: String, val description: String, val watchUptimeMillis: Long, referenceQueue: ReferenceQueue<Any> )
所以 removeWeaklyReachableObjects() 方法的作用就是将已经被 GC 的对象从 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)
}当我们调用 expectWeaklyReachable() 方法时,先清理已经被 GC 的对象,接着将需要观察的对象,存储为一个 KeyedWeakReference 的弱引用对象,再存放到 watchedObjects 集合中,最后使用 checkRetainedExecutor 安排一次 moveToRetained 任务。
@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}先调用一次 removeWeaklyReachableObjects() 删除已经 GC 的对象,那么剩下的对象就可以认为是被保留(没办法 GC)的对象,会调通知事件。
回到流程开始 AppWatcher.manualInstall(application) 方法,会发现在初始化默认的监听器之前还有一个重要的模块需要初始化:
// Requires AppWatcher.objectWatcher to be set
LeakCanaryDelegate.loadLeakCanary(application)
@Suppress("UNCHECKED_CAST")
val loadLeakCanary by lazy {
try {
val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
leakCanaryListener.getDeclaredField("INSTANCE")
.get(null) as (Application) -> Unit
} catch (ignored: Throwable) {
NoLeakCanary
}
}而这个对象的值是通过反射获取的 InternalLeakCanary.INSTANCE 这是一个单例对象。InternalLeakCanary 位于 leakcanary-android-core 模块,这也是需要反射的原因。
InternalLeakCanary
接着上面末尾讲,当调用 loadLeakCanary() 方法时,实际会调用 InternalLeakCanary.invoke() 方法:
override fun invoke(application: Application) {
_application = application
checkRunningInDebuggableBuild()
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
val gcTrigger = GcTrigger.Default
val configProvider = { LeakCanary.config }
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
registerResumedActivityListener(application)
addDynamicShortcut(application)
// We post so that the log happens after Application.onCreate()
mainHandler.post {
// https://github.com/square/leakcanary/issues/1981
// We post to a background handler because HeapDumpControl.iCanHasHeap() checks a shared pref
// which blocks until loaded and that creates a StrictMode violation.
backgroundHandler.post {
SharkLog.d {
when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
is Nope -> application.getString(
R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
)
}
}
}
}
}这里做的事情比较多,首先是 addOnObjectRetainedListener() 方法,这里会注册一个 OnObjectRetainedListener 事件,也就是我们上面说到的在 moveToRetained() 方法中的回调事件。
AndroidHeapDumper 则是通过调用 Debug.dumpHprofData() 方法从虚拟机中 dump hprof 文件。
override fun dumpHeap(): DumpHeapResult {
val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return NoHeapDump
val waitingForToast = FutureResult<Toast?>()
showToast(waitingForToast)
if (!waitingForToast.wait(5, SECONDS)) {
SharkLog.d { "Did not dump heap, too much time waiting for Toast." }
return NoHeapDump
}
val notificationManager =
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Notifications.canShowNotification) {
val dumpingHeap = context.getString(R.string.leak_canary_notification_dumping)
val builder = Notification.Builder(context)
.setContentTitle(dumpingHeap)
val notification = Notifications.buildNotification(context, builder, LEAKCANARY_LOW)
notificationManager.notify(R.id.leak_canary_notification_dumping_heap, notification)
}
val toast = waitingForToast.get()
return try {
val durationMillis = measureDurationMillis {
// ---- main
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)
}
}GcTrigger 通过调用 Runtime.getRuntime().gc() 方法触发虚拟机进行 GC 操作:
object Default : GcTrigger {
override fun runGc() {
// Code taken from AOSP FinalizationTest:
// https://android.googlesource.com/platform/libcore/+/master/support/src/test/java/libcore/
// java/lang/ref/FinalizationTester.java
// System.gc() does not garbage collect every time. Runtime.gc() is
// more likely to perform a gc.
Runtime.getRuntime()
.gc()
enqueueReferences()
System.runFinalization()
}HeapDumpTrigger 管理触发 Heap Dump 的逻辑,有两个地方会触发 Heap Dump:
- 保留对象超过阙值 这个阙值默认为 5(应用可见的情况下),可以通过 Config 配置:
val retainedVisibleThreshold: Int = 5
- 当 ObjectWatcher 回调 onObjectRetained() 方法时,HeapDumpTrigger.onObjectRetained() 方法会被调用:
//InternalLeakCanary.kt
override fun onObjectRetained() = scheduleRetainedObjectCheck()
fun scheduleRetainedObjectCheck() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
//HeapDumpTrigger.kt
fun scheduleRetainedObjectCheck(
delayMillis: Long = 0L
) {
val checkCurrentlyScheduledAt = checkScheduledAt
if (checkCurrentlyScheduledAt > 0) {
// 同时只会有一个任务
return
}
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
backgroundHandler.postDelayed({
checkScheduledAt = 0
checkRetainedObjects()
}, delayMillis)
}
//HeapDumpTrigger.kt
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()
// 检测当前保留对象数量
val wouldDump = !checkRetainedCount(
retainedReferenceCount, config.retainedVisibleThreshold, nopeReason
)
if (wouldDump) {
val uppercaseReason = nopeReason[0].toUpperCase() + nopeReason.substring(1)
onRetainInstanceListener.onEvent(DumpingDisabled(uppercaseReason))
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = uppercaseReason
)
//此处不会 return 会继续往下走 符合条件则 dumpHeap
}
} 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) {
// 60s 内只会执行一次,重新安排
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"
// ---- main
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility"
)
}
fun scheduleRetainedObjectCheck(
delayMillis: Long = 0L
) {
val checkCurrentlyScheduledAt = checkScheduledAt
if (checkCurrentlyScheduledAt > 0) {
return
}
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
backgroundHandler.postDelayed({
checkScheduledAt = 0
// ---- main
checkRetainedObjects()
}, delayMillis)
}所以不论是超出阀值还是生命周期对象结束时的回调,最终都是执行dumpHeap:
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean,
reason: String
) {
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
// ---- main
when (val heapDumpResult = heapDumper.dumpHeap()) {
is NoHeapDump -> {
if (retry) {
SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" }
scheduleRetainedObjectCheck(
delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
)
} else {
SharkLog.d { "Failed to dump heap, will not automatically retry" }
}
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_dump_failed
)
)
}
is HeapDump -> {
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
HeapAnalyzerService.runAnalysis(
context = application,
heapDumpFile = heapDumpResult.file,
heapDumpDurationMillis = heapDumpResult.durationMillis,
heapDumpReason = reason
)
}
}
}when 语句块的条件 heapDumper.dumpHeap()是主要代码,会发现调用的是AndroidHeapDumper$dumpHeap(),这点在上面提过,主要是调用系统的 Debug 工具来导出dump hprof 文件:
//AndroidHeapDumper.kt fun dumpHeap() Debug.dumpHprofData(heapDumpFile.absolutePath)
导出完成后根据结果判断弹什么样的通知,成功会将结果转交给HeapAnalyzerService完成结果展示。
HeapAnalyzerService
HeapAnalyzerService 是继承于 IntentService,调用 runAnalysis() 方法最终会调用到 analyzeHeap() 方法:
internal class HeapAnalyzerService : ForegroundService //parent internal abstract class ForegroundService( name: String, private val notificationContentTitleResId: Int, private val notificationId: Int ) : IntentService(name)
fun runAnalysis(
context: Context,
heapDumpFile: File,
heapDumpDurationMillis: Long? = null,
heapDumpReason: String = "Unknown"
) {
val intent = Intent(context, HeapAnalyzerService::class.java)
intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
intent.putExtra(HEAPDUMP_REASON_EXTRA, heapDumpReason)
heapDumpDurationMillis?.let {
intent.putExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, heapDumpDurationMillis)
}
startForegroundService(context, intent)
}
override fun onHandleIntentInForeground(intent: Intent?) {
if (intent == null || !intent.hasExtra(HEAPDUMP_FILE_EXTRA)) {
SharkLog.d { "HeapAnalyzerService received a null or empty intent, ignoring." }
return
}
// Since we're running in the main process we should be careful not to impact it.
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File
val heapDumpReason = intent.getStringExtra(HEAPDUMP_REASON_EXTRA)
val heapDumpDurationMillis = intent.getLongExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, -1)
val config = LeakCanary.config
val heapAnalysis = if (heapDumpFile.exists()) {
// ---- main
analyzeHeap(heapDumpFile, config)
} else {
missingFileFailure(heapDumpFile)
}
val fullHeapAnalysis = when (heapAnalysis) {
is HeapAnalysisSuccess -> heapAnalysis.copy(
dumpDurationMillis = heapDumpDurationMillis,
metadata = heapAnalysis.metadata + ("Heap dump reason" to heapDumpReason)
)
is HeapAnalysisFailure -> heapAnalysis.copy(dumpDurationMillis = heapDumpDurationMillis)
}
onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
}看下analyzeHeap方法:
private fun analyzeHeap(
heapDumpFile: File,
config: Config
): HeapAnalysis {
val heapAnalyzer = HeapAnalyzer(this)
val proguardMappingReader = try {
ProguardMappingReader(assets.open(PROGUARD_MAPPING_FILE_NAME))
} catch (e: IOException) {
null
}
return heapAnalyzer.analyze(
heapDumpFile = heapDumpFile,
leakingObjectFinder = config.leakingObjectFinder,
referenceMatchers = config.referenceMatchers,
computeRetainedHeapSize = config.computeRetainedHeapSize,
objectInspectors = config.objectInspectors,
metadataExtractor = config.metadataExtractor,
proguardMapping = proguardMappingReader?.readProguardMapping()
)
}proguardMappingReader 是用于处理代码混淆的,支持在测试版本打开代码混淆开关,PROGUARD_MAPPING_FILE_NAME 表示 Mapping 文件,这个文件在 leakcanary-deobfuscation-gradle-plugin 模块处理的,具体的可以看 CopyObfuscationMappingFileTask。
HeapAnalyzer.analyze() 这个方法的作用是:从 hprof 文件中搜索泄露对象,然后计算它们到 GC Roots 的最短路径。
val sourceProvider = ConstantMemoryMetricsDualSourceProvider(FileSourceProvider(heapDumpFile))
sourceProvider.openHeapGraph(proguardMapping).use { graph ->
val helpers =
FindLeakInput(graph, referenceMatchers, computeRetainedHeapSize, objectInspectors)
val result = helpers.analyzeGraph(
metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime
)
//...
}解析 hprof 文件
先了解下 hprof 文件:hprof 是由 JVM TI Agent HPROF 生成的一种二进制文件,具体文件格式可以看官方文档:链接
根据上面的链接大致翻译下,则 hprof 文件可以分为以下几个部分:
- header header 中有以下三个部分组成:
- 文件格式名和版本号
JDK 1.6 的值为 “JAVA PROFILE 1.0.2″,在此之前还有 “JAVA PROFILE 1.0” 和 “JAVA PROFILE 1.0.1”,如果是 Android 平台的话,这个值为 “JAVA PROFILE 1.0.3″,这也是为什么 MAT 不支持直接解析 Android 平台生成的 hprof 文件了。 - identifiers 标识符的大小。
标识符用于表示 UTF8字符串、对象、堆栈跟踪等。它们可以具有与主机指针或 sizeof (void *)相同的大小,但不一定是这样 - 时间戳
高位 4 字节 + 低位 4 字节
- 文件格式名和版本号
- records record 表示文件中记录的信息,每个 record 都由以下 4 个部分组成:
- tag
1 字节,表示 record 的类型,支持的值可以看文档。 - time
4 字节,表示 record 的时间戳。 - length
4 字节,表示 body 的字节长度。 - body
表示 record 中存储的数据。
- tag
在了解完 hprof 文件的格式后,我们再来看 LeakCanary 的解析 hprof 文件的代码,
sourceProvider.openHeapGraph(proguardMapping)源码:
fun DualSourceProvider.openHeapGraph(
proguardMapping: ProguardMapping? = null,
indexedGcRootTypes: Set<HprofRecordTag> = HprofIndex.defaultIndexedGcRootTags()
): CloseableHeapGraph {
val header = openStreamingSource().use { HprofHeader.parseHeaderOf(it) }
val index = HprofIndex.indexRecordsOf(this, header, proguardMapping, indexedGcRootTypes)
return index.openHeapGraph()
}读取 hprof 文件:
HprofHeader.parseHeaderOf(it)
/**
* Reads the header of the provided [source] and returns it as a [HprofHeader].
* This does not close the [source].
*/fun parseHeaderOf(source: BufferedSource): HprofHeader {
require(!source.exhausted()) {
throw IllegalArgumentException("Source has no available bytes")
}
val endOfVersionString = source.indexOf(0)
val versionName = source.readUtf8(endOfVersionString)
val version = supportedVersions[versionName]
checkNotNull(version) {
"Unsupported Hprof version [$versionName] not in supported list ${supportedVersions.keys}"
}
// Skip the 0 at the end of the version string.
source.skip(1)
val identifierByteSize = source.readInt()
val heapDumpTimestamp = source.readLong()
return HprofHeader(heapDumpTimestamp, version, identifierByteSize)
}从上面的代码可以看到先依次读取 version(versionName)、identifierByteSize、heapDumpTimestamp 后,再创建一个 HprofReader 用于读取 records。HprofReader 的工作方式也是类似的,接着openHeapGraph处源码调用路径(层层递进):
-> HprofIndex.indexRecordsOf -> HprofInMemoryIndex.indexHprof -> reader.readRecords
最后在 reader.readRecords 方法会发现,实际上是根据 tag 的值去读取不同的 record,这里我们以 “STRING IN UTF8” 为例:
//StreamingHprofReader.kt fun readRecords
STRING_IN_UTF8.tag -> {
if (STRING_IN_UTF8 in recordTags) {
listener.onHprofRecord(STRING_IN_UTF8, length, reader)
} else {
reader.skip(length)
}
}生成 heap graph
HeapGraph 用于表示 heap 中的对象关系图,通过调用 HprofHeapGraph.openHeapGraph 生成:
fun DualSourceProvider.openHeapGraph(
proguardMapping: ProguardMapping? = null,
indexedGcRootTypes: Set<HprofRecordTag> = HprofIndex.defaultIndexedGcRootTags()
): CloseableHeapGraph {
val header = openStreamingSource().use { HprofHeader.parseHeaderOf(it) }
val index = HprofIndex.indexRecordsOf(this, header, proguardMapping, indexedGcRootTypes)
return index.openHeapGraph()
}
//index.openHeapGraph()
/**
* Opens a [CloseableHeapGraph] which you can use to navigate the indexed hprof and then close.
*/fun openHeapGraph(): CloseableHeapGraph {
val reader = RandomAccessHprofReader.openReaderFor(sourceProvider, header)
return HprofHeapGraph(header, reader, index)
}indexedGcRootTypes 表示我们要收集的 GC Roots 节点,通过查看源码得知可以作为 GC Roots 节点的有以下对象:
fun defaultIndexedGcRootTags() = EnumSet.of( // native 中的全局变量 HprofRecordTag.ROOT_JNI_GLOBAL, // java 中的局部变量 HprofRecordTag.ROOT_JAVA_FRAME, // native 中的局部变量 HprofRecordTag.ROOT_JNI_LOCAL, // 调用 wait() 或者 notify(),或者 synchronized 锁对象 HprofRecordTag.ROOT_MONITOR_USED, // native 中的入参和出参 HprofRecordTag.ROOT_NATIVE_STACK, // 系统类 HprofRecordTag.ROOT_STICKY_CLASS, // 活动线程引用的代码块 HprofRecordTag.ROOT_THREAD_BLOCK, // ThreadObject points to threads, which we need to find the thread that a JavaLocalPattern // belongs to // 活动线程引用的对象 HprofRecordTag.ROOT_THREAD_OBJECT, // native 中的锁对象 HprofRecordTag.ROOT_JNI_MONITOR )
接着会从 hprof 文件中读取 records,读取原理可以参考 hprof 文件格式。这里有个小细节,LeakCanary 只会读取以下几种类型的 record:
val bytesRead = reader.readRecords(
EnumSet.of(CLASS_DUMP, INSTANCE_DUMP, OBJECT_ARRAY_DUMP, PRIMITIVE_ARRAY_DUMP),
//...
val recordTypes = EnumSet.of(
STRING_IN_UTF8,
LOAD_CLASS,
CLASS_DUMP,
INSTANCE_DUMP,
OBJECT_ARRAY_DUMP,
PRIMITIVE_ARRAY_DUMP
) + HprofRecordTag.rootTags.intersect(indexedGcRootTags)- STRING_IN_UTF8
UTF8 格式的字符串 - LOAD CLASS
虚拟机中加载的类 - CLASS_DUMP
dump 出来内存中的类实例 - INSTANCE_DUMP
dump 出来内存中的对象实例 - OBJECT_ARRAY_DUMP
dump 出来内存中的对象数组实例 - PRIMITIVE_ARRAY_DUMP
dump 出来内存中的原始类型数组实例 - indexedGcRootTags
包括了上面定义的所有 GC Roots 对象实例
查询泄露对象
在生成 heap graph 后,我们就可以根据它,来获取泄露对象的 objectIds:
//回到 FindLeakInput.analyzeGraph 方法 //... val leakingObjectIds = leakingObjectFinder.findLeakingObjectIds(graph) //...
LeakingObjectFinder 用于查询泄露对象,它的实现有两个:KeyedWeakReferenceFinder 和 FilteringLeakingObjectFinder,默认为 KeyedWeakReferenceFinder,即通过 KeyedWeakReference 引用的对象,关于 KeyedWeakReference 的作用我们在 AppWatcher 那里有说到。
override fun findLeakingObjectIds(graph: HeapGraph): Set<Long> =
findKeyedWeakReferences(graph)
.filter { it.hasReferent && it.isRetained }
.map { it.referent.value }
.toSet()
internal fun findKeyedWeakReferences(graph: HeapGraph): List<KeyedWeakReferenceMirror> {
return graph.context.getOrPut(KEYED_WEAK_REFERENCE.name) {
val keyedWeakReferenceClass = graph.findClassByName("leakcanary.KeyedWeakReference")
val keyedWeakReferenceClassId = keyedWeakReferenceClass?.objectId ?: 0
val legacyKeyedWeakReferenceClassId =
graph.findClassByName("com.squareup.leakcanary.KeyedWeakReference")?.objectId ?: 0
val heapDumpUptimeMillis = heapDumpUptimeMillis(graph)
val addedToContext: List<KeyedWeakReferenceMirror> = graph.instances
.filter { instance ->
instance.instanceClassId == keyedWeakReferenceClassId || instance.instanceClassId == legacyKeyedWeakReferenceClassId
}
.map {
KeyedWeakReferenceMirror.fromInstance(
it, heapDumpUptimeMillis
)
}
.toList()
graph.context[KEYED_WEAK_REFERENCE.name] = addedToContext
addedToContext
}
}KeyedWeakReferenceFinder 通过过滤 heap dump 中的所有 KeyedWeakReference 实例,来获取泄露对象实例。而 FilteringLeakingObjectFinder 则是用于我们自定义的泄露对象判断逻辑:
override fun findLeakingObjectIds(graph: HeapGraph): Set<Long> {
return graph.objects
.filter { heapObject ->
filters.any { filter ->
filter.isLeakingObject(heapObject)
}
}
.map { it.objectId }
.toSet()
}生成泄露对象报告
LeakCanary 定义了两个主要泄露类型:ApplicationLeak 和 LibraryLeak,同时还有一些unreachableObjects:
- ApplicationLeak
表示应用本身导致内存泄露 - LibraryLeak
表示依赖库导致的内存泄露,例如 Android Framework 等
val (applicationLeaks, libraryLeaks, unreachableObjects) = findLeaks(leakingObjectIds)
//... val pathFinder = PathFinder(graph, listener, referenceMatchers) //查询泄露对象到 GC ROOTS 路径 val pathFindingResults = pathFinder.findPathsFromGcRoots(leakingObjectIds, computeRetainedHeapSize) //...
查询泄露对象到 GC Roots 的路径,一层套一层:
fun findPathsFromGcRoots(
leakingObjectIds: Set<Long>,
computeRetainedHeapSize: Boolean
): PathFindingResults {
listener.onAnalysisProgress(FINDING_PATHS_TO_RETAINED_OBJECTS)
val objectClass = graph.findClassByName("java.lang.Object")
val sizeOfObjectInstances = determineSizeOfObjectInstances(objectClass, graph)
val javaLangObjectId = objectClass?.objectId ?: -1
// Estimate of how many objects we'll visit. This is a conservative estimate, we should always
// visit more than that but this limits the number of early array growths.
val estimatedVisitedObjects = (graph.instanceCount / 2).coerceAtLeast(4)
val state = State(
leakingObjectIds = leakingObjectIds.toLongScatterSet(),
sizeOfObjectInstances = sizeOfObjectInstances,
computeRetainedHeapSize = computeRetainedHeapSize,
javaLangObjectId = javaLangObjectId,
estimatedVisitedObjects = estimatedVisitedObjects
)
return state.findPathsFromGcRoots()
}最后到达的方法:
private fun State.findPathsFromGcRoots(): PathFindingResults {
enqueueGcRoots()
val shortestPathsToLeakingObjects = mutableListOf<ReferencePathNode>()
visitingQueue@ while (queuesNotEmpty) {
val node = poll()
if (leakingObjectIds.contains(node.objectId)) {
shortestPathsToLeakingObjects.add(node)
// Found all refs, stop searching (unless computing retained size)
if (shortestPathsToLeakingObjects.size == leakingObjectIds.size()) {
if (computeRetainedHeapSize) {
listener.onAnalysisProgress(FINDING_DOMINATORS)
} else {
break@visitingQueue
}
}
}
when (val heapObject = graph.findObjectById(node.objectId)) {
is HeapClass -> visitClassRecord(heapObject, node)
is HeapInstance -> visitInstance(heapObject, node)
is HeapObjectArray -> visitObjectArray(heapObject, node)
}
}
return PathFindingResults(
shortestPathsToLeakingObjects,
if (visitTracker is Dominated) visitTracker.dominatorTree else null
)
}State.findPathsFromGcRoots() 的代码有点长,我们一点点分析。首先是 enqueueGcRoots() 方法,它的作用是将所有 GC Roots 节点放入到队列中:
private fun State.enqueueGcRoots() {
// 将 GC Roots 进行排序
val gcRoots = sortedGcRoots()
// 存储线程名称
val threadNames = mutableMapOf<HeapInstance, String>()
// 存储线程的 SerialNumber,可以通过 SerialNumber 访问对应的线程信息
val threadsBySerialNumber = mutableMapOf<Int, Pair<HeapInstance, ThreadObject>>()
gcRoots.forEach { (objectRecord, gcRoot) ->
when (gcRoot) {
is ThreadObject -> {
// 活动的 Thread 实例 缓存 threadsBySerialNumber
threadsBySerialNumber[gcRoot.threadSerialNumber] = objectRecord.asInstance!! to gcRoot
// 入列 NormalRootNode
enqueue(NormalRootNode(gcRoot.id, gcRoot))
}
is JavaFrame -> {
// Java 局部变量
val threadPair = threadsBySerialNumber[gcRoot.threadSerialNumber]
if (threadPair == null) {
// Could not find the thread that this java frame is for.
enqueue(NormalRootNode(gcRoot.id, gcRoot))
} else {
val (threadInstance, threadRoot) = threadPair
val threadName = threadNames[threadInstance] ?: {
val name = threadInstance[Thread::class, "name"]?.value?.readAsJavaString() ?: ""
threadNames[threadInstance] = name
name
}()
// RefreshceMatchers 用于匹配已知的引用节点
// IgnoredReferenceMatcher 表示忽略这个引用节点
// LibraryLeakReferenceMatcher 表示这是库内存泄露对象
val referenceMatcher = threadNameReferenceMatchers[threadName]
if (referenceMatcher !is IgnoredReferenceMatcher) {
val rootNode = NormalRootNode(threadRoot.id, gcRoot)
val refFromParentType = LOCAL
// Unfortunately Android heap dumps do not include stack trace data, so
// JavaFrame.frameNumber is always -1 and we cannot know which method is causing the
// reference to be held.
val refFromParentName = ""
val childNode = if (referenceMatcher is LibraryLeakReferenceMatcher) {
LibraryLeakChildNode(
objectId = gcRoot.id,
parent = rootNode,
refFromParentType = refFromParentType,
refFromParentName = refFromParentName,
matcher = referenceMatcher
)
} else {
NormalNode(
objectId = gcRoot.id,
parent = rootNode,
refFromParentType = refFromParentType,
refFromParentName = refFromParentName
)
}
// 入列 LibraryLeakChildNode 或 NormalNode
enqueue(childNode)
}
}
}
is JniGlobal -> {
// Native 全局变量
// 是否匹配已知引用节点
val referenceMatcher = when (objectRecord) {
is HeapClass -> jniGlobalReferenceMatchers[objectRecord.name]
is HeapInstance -> jniGlobalReferenceMatchers[objectRecord.instanceClassName]
is HeapObjectArray -> jniGlobalReferenceMatchers[objectRecord.arrayClassName]
is HeapPrimitiveArray -> jniGlobalReferenceMatchers[objectRecord.arrayClassName]
}
if (referenceMatcher !is IgnoredReferenceMatcher) {
if (referenceMatcher is LibraryLeakReferenceMatcher) {
// 入列 LibraryLeakRootNode
enqueue(LibraryLeakRootNode(gcRoot.id, gcRoot, referenceMatcher))
} else {
// 入列 NormalRootNode
enqueue(NormalRootNode(gcRoot.id, gcRoot))
}
}
}
// 其他 GC Roots,入列 NormalRootNode
else -> enqueue(NormalRootNode(gcRoot.id, gcRoot))
}
}
}在将 GC Roots 节点入列的过程,有两个地方值得注意:
- ReferenceMatcher
ReferenceMatcher 用于匹配引用节点,判断是否要忽略它。LeakCanary 支持 4 种类型的匹配:- 类实例字段
缓存在fieldNameByClassName里,例如,android.os.Message中的 obj 字段 - 类静态字段
缓存在staticFieldNameByClassName里,例如,android.app.ActivityManager的mContext字段 - 指定线程
缓存在threadNames里,例如,FinalizerWatchdogDaemon线程 - Native 全局变量
缓存在jniGlobals里,例如,android.widget.Toast\$TN类
- 类实例字段
内置的引用节点匹配为 AndroidReferenceMatchers.appDefaults。
- VisitQueue
PathFinder中有两个队列,一个优先级更高的toVisitQueue,另外一个是toVisitLastQueue,同时提供toVisitSet和toVisitLastSet用于提供常数级查询。 队列中的节点分为两种:- RootNode 根节点,它有两个实现类:
- LibraryLeakRootNode 依赖库的泄露根节点
- NormalRootNode 普通的根节点
- ChildNode 子节点,可以通过 parent 字段访问父节点。它有两个实现类:
- LibraryLeakChildNode 依赖库的泄露子节点
- NormalNode 普通的子节点
- RootNode 根节点,它有两个实现类:
以下 3 种情况会将节点放入到 toVisitLastQueue 中:
- LibraryLeakNode
- GC Root 为 ThreadObject
- 父节点的 GC Root 为 JavaFrame
因为这 3 种导致的内存泄露情况比较少,所以降低它们的访问优先级。
val visitLast =
visitingLast ||
node is LibraryLeakNode ||
// We deprioritize thread objects because on Lollipop the thread local values are stored
// as a field.
(node is RootNode && node.gcRoot is ThreadObject) ||
(node is NormalNode && node.parent is RootNode && node.parent.gcRoot is JavaFrame)在将所有的 GC Roots 节点入列后,使用广度优先遍历所有的节点,当访问节点是泄露节点,则添加到 shortestPathsToLeakingObjects 中:
val shortestPathsToLeakingObjects = mutableListOf<ReferencePathNode>()
visitingQueue@ while (queuesNotEmpty) {
val node = poll()
if (leakingObjectIds.contains(node.objectId)) {
shortestPathsToLeakingObjects.add(node)
// Found all refs, stop searching (unless computing retained size)
if (shortestPathsToLeakingObjects.size == leakingObjectIds.size()) {
if (computeRetainedHeapSize) {
listener.onAnalysisProgress(FINDING_DOMINATORS)
} else {
break@visitingQueue
}
}
}
when (val heapObject = graph.findObjectById(node.objectId)) {
is HeapClass -> visitClassRecord(heapObject, node)
is HeapInstance -> visitInstance(heapObject, node)
is HeapObjectArray -> visitObjectArray(heapObject, node)
}
}在遍历子节点时,有 3 种情况需要考虑:
- HeapClass
当节点表示 HeapClass,我们将它的静态变量入列:
val node = when (val referenceMatcher = ignoredStaticFields[fieldName]) {
null -> NormalNode(
objectId = objectId,
parent = parent,
refFromParentType = STATIC_FIELD,
refFromParentName = fieldName
)
is LibraryLeakReferenceMatcher -> LibraryLeakChildNode(
objectId = objectId,
parent = parent,
refFromParentType = STATIC_FIELD,
refFromParentName = fieldName,
matcher = referenceMatcher
)
// 忽略 IgnoredReferenceMatcher
is IgnoredReferenceMatcher -> null
}
if (node != null) {
enqueue(node)
}- HeapInstance
当节点表示 HeapInstance,我们将它的实例变量入列:
val node = when (val referenceMatcher = fieldReferenceMatchers[instanceRefField.fieldName]) {
null -> NormalNode(
objectId = instanceRefField.refObjectId,
parent = parent,
refFromParentType = INSTANCE_FIELD,
refFromParentName = instanceRefField.fieldName,
owningClassId = instanceRefField.declaringClassId
)
is LibraryLeakReferenceMatcher ->
LibraryLeakChildNode(
objectId = instanceRefField.refObjectId,
parent = parent,
refFromParentType = INSTANCE_FIELD,
refFromParentName = instanceRefField.fieldName,
matcher = referenceMatcher,
owningClassId = instanceRefField.declaringClassId
)
is IgnoredReferenceMatcher -> null
}
if (node != null) {
enqueue(node)
}- HeapObjectArray
当节点表示 HeapObjectArray,我们将它的非空元素入列:
val record = objectArray.readRecord()
val nonNullElementIds = record.elementIds.filter { objectId ->
objectId != ValueHolder.NULL_REFERENCE && graph.objectExists(objectId)
}
nonNullElementIds.forEachIndexed { index, elementId ->
val name = index.toString()
enqueue(
NormalNode(
objectId = elementId,
parent = parent,
refFromParentType = ARRAY_ENTRY,
refFromParentName = name
)
)
}
}这里不需要考虑 HeapPrimitiveArray 的情况,因为原始类型不能导致内存泄露。至此,通过调用 findPathsFromGcRoots() 方法可以将所有泄露对象的引用节点都查询出来了。
最短路径
返回上层方法 FindLeakInput.findLeaks,会发现在通过 findPathsFromGcRoots() 获取的节点中,一个泄露对象可能会有多个引用路径,所以我们还需要做一次遍历,找到每个泄露对象的最短路径(导致泄露的可能性最大)。
private fun FindLeakInput.findLeaks(leakingObjectIds: Set<Long>): LeaksAndUnreachableObjects {
val pathFinder = PathFinder(graph, listener, referenceMatchers)
val pathFindingResults =
pathFinder.findPathsFromGcRoots(leakingObjectIds, computeRetainedHeapSize)
val unreachableObjects = findUnreachableObjects(pathFindingResults, leakingObjectIds)
val shortestPaths =
deduplicateShortestPaths(pathFindingResults.pathsToLeakingObjects)
//...
}构建最短路径树:
private fun deduplicateShortestPaths(
inputPathResults: List<ReferencePathNode>
): List<ShortestPath> {
val rootTrieNode = ParentNode(0)
inputPathResults.forEach { pathNode ->
// Go through the linked list of nodes and build the reverse list of instances from
// root to leaking.
val path = mutableListOf<Long>()
var leakNode: ReferencePathNode = pathNode
while (leakNode is ChildNode) {
path.add(0, leakNode.objectId)
leakNode = leakNode.parent
}
path.add(0, leakNode.objectId)
updateTrie(pathNode, path, 0, rootTrieNode)
}
val outputPathResults = mutableListOf<ReferencePathNode>()
findResultsInTrie(rootTrieNode, outputPathResults)
if (outputPathResults.size != inputPathResults.size) {
SharkLog.d {
"Found ${inputPathResults.size} paths to retained objects," +
" down to ${outputPathResults.size} after removing duplicated paths"
}
} else {
SharkLog.d { "Found ${outputPathResults.size} paths to retained objects" }
}
return outputPathResults.map { retainedObjectNode ->
val shortestChildPath = mutableListOf<ChildNode>()
var node = retainedObjectNode
while (node is ChildNode) {
shortestChildPath.add(0, node)
node = node.parent
}
val rootNode = node as RootNode
ShortestPath(rootNode, shortestChildPath)
}
}
private fun updateTrie(
pathNode: ReferencePathNode,
path: List<Long>,
pathIndex: Int,
parentNode: ParentNode
) {
val objectId = path[pathIndex]
if (pathIndex == path.lastIndex) {
parentNode.children[objectId] = LeafNode(objectId, pathNode)
} else {
val childNode = parentNode.children[objectId] ?: {
val newChildNode = ParentNode(objectId)
parentNode.children[objectId] = newChildNode
newChildNode
}()
if (childNode is ParentNode) {
updateTrie(pathNode, path, pathIndex + 1, childNode)
}
}
}通过遍历泄露对象节点的父节点,构建出一棵树,多个相同泄露对象节点的不同路径,最终获取最短路径的树。多条最短路径(不同泄露对象)最终合并成一棵树。(这里已经是 shark 的算法内容,不做多描述,说得越多可能错得越多)
生成 LeakTrace
从上面生成泄露对象路径 ReferencePathNode 到最终的 LeakTrace:
private fun FindLeakInput.findLeaks(leakingObjectIds: Set<Long>): LeaksAndUnreachableObjects {
//...
val inspectedObjectsByPath = inspectObjects(shortestPaths)
//...
}有两个地方需要注意下:
- inspectObjects
通过调用FindLeakInput.inspectObjects,可以对每个 ObjectReporter 添加一些说明。
private fun FindLeakInput.inspectObjects(shortestPaths: List<ShortestPath>): List<List<InspectedObject>> {
//...
objectInspectors.forEach { inspector ->
leakReportersByPath.forEach { leakReporters ->
leakReporters.forEach { reporter ->
inspector.inspect(reporter)
}
}
}
return leakReportersByPath.map { leakReporters ->
computeLeakStatuses(leakReporters)
}
}例如源码中的判断对象是否为单例:
/**
* Inspector that automatically marks instances of the provided class names as not leaking
* because they're app wide singletons.
*/ class AppSingletonInspector(private vararg val singletonClasses: String) : ObjectInspector {
override fun inspect(
reporter: ObjectReporter
) {
if (reporter.heapObject is HeapInstance) {
reporter.heapObject.instanceClass
.classHierarchy
.forEach { heapClass ->
if (heapClass.name in singletonClasses) {
reporter.notLeakingReasons += "${heapClass.name} is an app singleton"
}
}
}
}
}可以通过 Config.objectInspectors 添加自定义的 ObjectInspector。
- LeakingStatus
在inspectObjects方法返回时还调用了computeLeakStatuses,通过调用HeapAnalyzer.computeLeakStatuses()可以计算路径上每个节点的泄露状态。
UI 展现
在HeapDumpTrigger.dumpHeap完成后会调用HeapAnalyzerService.runAnalysis
fun runAnalysis(
context: Context,
heapDumpFile: File,
heapDumpDurationMillis: Long? = null,
heapDumpReason: String = "Unknown"
) {
val intent = Intent(context, HeapAnalyzerService::class.java)
intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
intent.putExtra(HEAPDUMP_REASON_EXTRA, heapDumpReason)
heapDumpDurationMillis?.let {
intent.putExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, heapDumpDurationMillis)
}
startForegroundService(context, intent)
}
override fun onHandleIntentInForeground(intent: Intent?) {
//...
val config = LeakCanary.config
val heapAnalysis = if (heapDumpFile.exists()) {
analyzeHeap(heapDumpFile, config)
} else {
missingFileFailure(heapDumpFile)
}
val fullHeapAnalysis = when (heapAnalysis) {
is HeapAnalysisSuccess -> heapAnalysis.copy(
dumpDurationMillis = heapDumpDurationMillis,
metadata = heapAnalysis.metadata + ("Heap dump reason" to heapDumpReason)
)
is HeapAnalysisFailure -> heapAnalysis.copy(dumpDurationMillis = heapDumpDurationMillis)
}
onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
//...
}当前 hprof 文件分析成功后,会回调 onHeapAnalyzed() 方法,在默认实现的 DefaultOnHeapAnalyzedListener 中,可以看到内存泄露导出后的相关通知内容。
override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
SharkLog.d { "\u200B\n${LeakTraceWrapper.wrap(heapAnalysis.toString(), 120)}" }
val db = LeaksDbHelper(application).writableDatabase
val id = HeapAnalysisTable.insert(db, heapAnalysis)
db.releaseReference()
val (contentTitle, screenToShow) = when (heapAnalysis) {
is HeapAnalysisFailure -> application.getString(
R.string.leak_canary_analysis_failed
) to HeapAnalysisFailureScreen(id)
is HeapAnalysisSuccess -> {
val retainedObjectCount = heapAnalysis.allLeaks.sumBy { it.leakTraces.size }
val leakTypeCount = heapAnalysis.applicationLeaks.size + heapAnalysis.libraryLeaks.size
application.getString(
R.string.leak_canary_analysis_success_notification, retainedObjectCount, leakTypeCount
) to HeapDumpScreen(id)
}
}
if (InternalLeakCanary.formFactor == TV) {
showToast(heapAnalysis)
printIntentInfo()
} else {
showNotification(screenToShow, contentTitle)
}
}当点击通知栏消息后,再跳转到 LeakActivity:
private fun showNotification(
screenToShow: Screen,
contentTitle: String
) {
val pendingIntent = LeakActivity.createPendingIntent(
application, arrayListOf(HeapDumpsScreen(), screenToShow)
)
val contentText = application.getString(R.string.leak_canary_notification_message)
Notifications.showNotification(
application, contentTitle, contentText, pendingIntent,
R.id.leak_canary_notification_analysis_result,
LEAKCANARY_MAX
)
}总结
LeakCanary 通过 ContentProvider 的形式进行自动初始化,初始化时会默认注册ActivityWatcher、FragmentAndViewModelWatcher、RootViewWatcher、ServiceWatcher,额外还会初始化一个核心类InternalLeakCanary,用于创建各种回调钩子。以ActivityWatcher为例,当Activity销毁时,将 activity 的弱引用加入到观察列表中,然后触发InternalLeakCanary.onObjectRetained(),从而触发HeapDumpTrigger类的checkRetainedObjects()方法,先手动 gcTrigger.runGc(),再检测泄露对象。在导出堆数据时,使用的是 android.os.Debug 类的 Debug.dumpHprofData(heapDumpFile.absolutePath),导出一个Hprof文件,导出完成后,调用HeapAnalyzerService前台服务,开始堆栈分析,分析使用的是 Shark,涉及到Hprof相关内容以及由 GC root 组建的最短路径树,分析完成后回调DefaultOnHeapAnalyzedListener.onHeapAnalyzed,触发相关通知显示以及点击后的 LeakActivity 数据展示。
参考
主要参考/复制:LeakCanary2 源码分析
源码分析耗时巨大,感谢原作者。
其他:
本站广告由 Google AdSense 提供
0条评论