前言
偶然看到一篇文章,思路清晰写得很好,但有些细节和现有版本不太一样,自己看了一遍做了一些调整与补充,加深印象。本文字数达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 后存放的队列。
/** * 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 中的所有 KeyedWeakRef
erence 实例,来获取泄露对象实例。而 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 源码分析
源码分析耗时巨大,感谢原作者。
其他:
本站由以下主机服务商提供服务支持:
0条评论