注册

Andorid进阶二:LeakCanary源码分析,从头到尾搞个明白

四,ObjectWatcher 保留对象检查分析

我们转到 ObjectWatcher 的 expectWeaklyReachable 方法看看

@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
//是否启用 , AppWatcher 持有的ObjectWatcher 默认是启用的
if (!isEnabled()) {
return
}
///移除之前已经被回收的监听对象
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
//(1) 创建弱引用
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 {
//(2)
moveToRetained(key)
}
}

继续分析源码中标注的地方。

(1) 创建弱引用

标注(1.2.4)处的代码是初始化的主要代码,创建要观察对象的弱引用,传入queue 作为gc 后的对象信息存储队列,WeakReference 中,当持有对象呗gc的时候,会将其包装对象压入队列中。可以在后续对该队列进行观察。

(2) moveToRetained(key),检查对应key对象的保留

作为Executor的runner 执行,在AppWatcher中,默认延迟五秒后执行该方法 查看源码分析

@Synchronized private fun moveToRetained(key: String) {
///移除已经被回收的观察对象
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
//记录泄漏时间
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
//回调泄漏监听
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}

从上述代码可知,ObjectWatcher 监测内存泄漏总共有以下几步

  1. 清除已经被内存回收的监听对象
  2. 创建弱引用,传入 ReferenceQueue 作为gc 信息保存队列
  3. 在延迟指定的时间后,再次检查针对的对象是否被回收(通过检查ReferenceQueue队列内有无该WeakReference实例)
  4. 检测到对象没有被回收后,回调 onObjectRetainedListeners 们的 onObjectRetained

五,dumpHeap,怎么个DumpHeap流程

(1.1)objectWatcher 添加 OnObjectRetainedListeners 监听

回到最初AppWatcher的 manualInstall 方法。 可以看到其中执行了loadLeakCanary 方法。 代码如下:

///(2)
LeakCanaryDelegate.loadLeakCanary(application)
//反射获取InternalLeakCanary实例
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 的静态实例。 并且调用了他的 invoke(application: Application)方法,所以我们接下来看InternalLeakCanary的该方法:

override fun invoke(application: Application) {
_application = application

checkRunningInDebuggableBuild()
//(1.2)添加 addOnObjectRetainedListener
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
//Gc触发器
val gcTrigger = GcTrigger.Default

val configProvider = { LeakCanary.config }

val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
///(1.3)
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
///(1.4) 添加application前后台变化监听
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
//(1.5)
registerResumedActivityListener(application)
//(1.6)
addDynamicShortcut(application)

// 6 判断是否应该DumpHeap
// 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()
)
}
}
}
}
}

我们看到初始化的时候做了这么6步

  • (1.2) 将自己加入到ObjectWatcher 的对象异常持有监听器中
  • (1.3)创建内存快照转储触发器 HeapDumpTrigger
  • (1.4)监听application 前后台变动,并且记录来到后台时间,便于LeakCanary 针对刚刚切入后台的一些destroy操作做泄漏监测
  • (1.5)注册activity生命周期回调,获取当前resumed的activity实例
  • (1.6)添加动态的桌面快捷入口
  • (1.7)在异步线程中,判断是否处于可dumpHeap的状态,如果处于触发一次内存泄漏检查 其中最重要的是 1.2,我们重点分析作为ObjectRetainedListener 他在回调中做了哪些工作。

(1.2)添加对象异常持有监听

可以看到代码(1.2),在objectWatcher将自己加入到泄漏监测回调中。 当ObjectWatcher监测到对象依然被异常持有的时候,会回调 onObjectRetained 方法。 从源码中可知,其中调用了 heapDumpTrigger的 scheduleRetainedObjectCheck方法, 代码如下。

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

HeapDumpTrigger 顾名思义,就是内存快照转储的触发器。在回调中最终调用了HeapDumpTrigger 的 checkRetainedObjects方法来检查内存泄漏。

(1.3)检查内存泄漏checkRetainedObjects

private fun checkRetainedObjects() {
val iCanHasHeap = HeapDumpControl.iCanHasHeap()

val config = configProvider()
//省略一些代码,主要是判断 iCanHasHeap。
//如果当前处于不dump内存快照的状态,就先不处理。如果有新的异常持有对象被发现则发送通知提示
//%d retained objects, tap to dump heap
/** ...*/

var retainedReferenceCount = objectWatcher.retainedObjectCount

//主动触发gc
if (retainedReferenceCount > 0) {
gcTrigger.runGc()
//重新获取异常持有对象
retainedReferenceCount = objectWatcher.retainedObjectCount
}
//如果泄漏数量小于阈值,且app在前台,或者刚转入后台,就展示泄漏通知,并先返回
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

//如果泄漏数量到达dumpHeap要求,继续往下
///转储内存快照在 WAIT_BETWEEN_HEAP_DUMPS_MILLIS (默认60秒)只会触发一次,如果之前刚触发过,就先不生成内存快照,直接发送通知了事。
//省略转储快照时机判断,不满足的话会提示 Last heap dump was less than a minute ago
/**...*/

dismissRetainedCountNotification()
val visibility = if (applicationVisible) "visible" else "not visible"
///转储内存快照
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility"
)
}

这一块也可以看出检测是否需要dumpHeap分为4步。

  1. 如果没有检测到异常持有的对象,返回
  2. 如果有异常对象,主动触发gc
  3. 如果还有异常对象,就是内存泄漏了。
  4. 判断泄漏数量是否到达需要dump的地步
  5. 判断一分钟内是否叫进行过dump了
  6. dumpHeap 前面都是判断代码,关键重点在于dumpHeap方法

(1.4)dumpHeap 转储内存快照

private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean,
reason: String
) {
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
when (val heapDumpResult = heapDumper.dumpHeap()) {
is NoHeapDump -> {
//省略 dump失败,等待重试代码和发送失败通知代码
}
is HeapDump -> {
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
///清除 objectWatcher 中,在heapDumpUptimeMillis之前持有的对象,也就是已经dump的对象
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
// 发送文件到HeapAnalyzerService解析
HeapAnalyzerService.runAnalysis(
context = application,
heapDumpFile = heapDumpResult.file,
heapDumpDurationMillis = heapDumpResult.durationMillis,
heapDumpReason = reason
)
}
}
}

HeapDumpTrigger#dumpHeap中调用到了 AndroidHeapDumper#dumpHeap方法。 并且在dump后马上调用 HeapAnalyzerService.runAnalysis 进行内存分析工作,该方法在下一节分析。先看AndroidHeapDumper#dumHeap源码

override fun dumpHeap(): DumpHeapResult {
//创建新的hprof 文件
val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return NoHeapDump

val waitingForToast = FutureResult<Toast?>()
///展示dump吐司
showToast(waitingForToast)

///如果展示吐司时间超过五秒,就不dump了
if (!waitingForToast.wait(5, SECONDS)) {
SharkLog.d { "Did not dump heap, too much time waiting for Toast." }
return NoHeapDump
}

//省略dumpHeap通知栏提示消息代码
val toast = waitingForToast.get()

return try {
val durationMillis = measureDurationMillis {
//调用DumpHprofData
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)
}
}

在该方法内,最终调用 Debug.dumpHprofData 方法 完成hprof 快照的生成。

六,分析内存 HeapAnalyzerService

上面代码分析中可以看到,在dumpHeap后紧跟着就是启动内存分析服务的方法。 现在我们跳转到HeapAnalyzerService的源码处。

override fun onHandleIntentInForeground(intent: Intent?) {
//省略参数获取代码
val config = LeakCanary.config
val heapAnalysis = if (heapDumpFile.exists()) {
analyzeHeap(heapDumpFile, config)
} else {
missingFileFailure(heapDumpFile)
}
//省略完善分析结果属性的代码
onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
}

可以看到重点在于 analyzeHeap,其中调用了 HeapAnalyzer#analyze HeapAnalyzer 类位于shark模块中。

(1)HeapAnalyzer#analyze

内存分析方法代码如下:

fun analyze(
heapDumpFile: File,
leakingObjectFinder: LeakingObjectFinder,
referenceMatchers: List<ReferenceMatcher> = emptyList(),
computeRetainedHeapSize: Boolean = false,
objectInspectors: List<ObjectInspector> = emptyList(),
metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP,
proguardMapping: ProguardMapping? = null
): HeapAnalysis {

//省略内存快照文件不存在的处理代码

return try {
listener.onAnalysisProgress(PARSING_HEAP_DUMP)
///io读取 内存快照
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
)
val lruCacheStats = (graph as HprofHeapGraph).lruCacheStats()
///io读取状态
val randomAccessStats =
"RandomAccess[" +
"bytes=${sourceProvider.randomAccessByteReads}," +
"reads=${sourceProvider.randomAccessReadCount}," +
"travel=${sourceProvider.randomAccessByteTravel}," +
"range=${sourceProvider.byteTravelRange}," +
"size=${heapDumpFile.length()}" +
"]"
val stats = "$lruCacheStats $randomAccessStats"
result.copy(metadata = result.metadata + ("Stats" to stats))
}
} catch (exception: Throwable) {
//省略异常处理
}
}

通过分析代码可知:分析内存快照分为以下5步:

  1. 读取hprof内存快照文件
  2. 找到LeakCanary 标记的泄漏对象们的数量和弱引用包装 ids,class name 为com.squareup.leakcanary.KeyedWeakReference

代码在 KeyedWeakReferenceFinder#findLeakingObjectIds

  1. 找到泄漏对象的gcRoot开始的路径

代码在PathFinder#findPathsFromGcRoots

  1. 返回分析结果,走结果回调
  2. 回调内 展示内存分析成功或者失败的通知栏消息,并将泄漏列表存储到数据库中

详情代码看 DefaultOnHeapAnalyzedListener#onHeapAnalyzed 以及 LeaksDbHelper

  1. 点开通知栏跳转到LeaksActivity 展示内存泄漏信息。

七,总结

终于从头到尾,总算是梳理了一波LeakCanary 源码

过程中学习到了这么多—>

  • 主动调用Gc的方式 GcTrigger.Default.runGc()
Runtime.getRuntime().gc()
  • seald class 密封类来表达状态,比如以下几个(关键好处在于使用when可以直接覆盖所有情况,而不必使用else)。
sealed class ICanHazHeap {
object Yup : ICanHazHeap()
abstract class Nope(val reason: () -> String) : ICanHazHeap()
class SilentNope(reason: () -> String) : Nope(reason)
class NotifyingNope(reason: () -> String) : Nope(reason)
}
sealed class Result {
data class Done(
val analysis: HeapAnalysis,
val stripHeapDumpDurationMillis: Long? = null
) : Result()
data class Canceled(val cancelReason: String) : Result()
}
  • 了解了系统创建内存快照的api
 Debug.dumpHprofData(heapDumpFile.absolutePath)
  • 知道了通过 ReferenceQueue 检测内存对象是否被gc,之前WeakReference都很少用。
  • 学习了leakCanary的分模块思想。作为sdk,很多功能模块引入自动开启。比如 leakcanary-android-process 自动开启对应进程等。
  • 学习了通过反射hook代码,替换实例达成添加钩子的操作。比如在Service泄漏监听代码中,替换HandleractivityManager的操作。

0 个评论

要回复文章请先登录注册