LeakCanary 的启动
在引入LeakCanary的时候,只需要在app/build.gradle中加入下面这行配置即可:
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
从引入方式可以看到,用的是debugImplementation方式,即只在debug模式的编译和最终的debug apk打包时有效,LeakCanary在分析时会影响一定性能的,影响app的运行速度,因此只在debug模式下使用
我们在使用的过程中发现根本没有在Application中调用init()初始化LeakCanary,那LeakCanary是怎么工作的?
通过查看引入库的源码AndroidManifest.xml文件,原来LeakCanary是使用ContentProvider自动初始化,不需要手动调用install方法。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.squareup.leakcanary.objectwatcher" >
<uses-sdk android:minSdkVersion="14" />
<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>
可以看到AppWatcherInstaller继承自ContentProvider。
当我们启动App时,这里指的是冷启动,在ActivityTaskSupervisor这个类中startSpecificActivity方法中会判断启动的App进程是否存在,如果是冷启动的情况:
mService.startProcessAsync(r, knownToBeDead, isTop, isTop ? "top-activity" : "activity");
会通过ATMS将进程创建出来,Post消息以启动进程,以避免在ATM锁保持的情况下调用AMS的可能死锁。
final Message m = PooledLambda.obtainMessage(ActivityManagerInternal::startProcess,
mAmInternal, activity.processName, activity.info.applicationInfo, knownToBeDead,
isTop, hostingType, activity.intent.getComponent());
mH.sendMessage(m);
使用socket方式通知zygote去fork新的进程,这个进程的入口就会在android.app.ActivityThread.java的main方法。
thread.attach(false, startSeq);
继续看attach方法
try {
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
进到AMS中的attachApplication
thread.bindApplication(processName, appInfo, providerList,
instr2.mClass,
profilerInfo, instr2.mArguments,
instr2.mWatcher,
instr2.mUiAutomationConnection, testMode,
mBinderTransactionTrackingEnabled, enableTrackAllocation,
isRestrictedBackupMode || !normalMode, app.isPersistent(),
new Configuration(app.getWindowProcessController().getConfiguration()),
app.getCompat(), getCommonServicesLocked(app.isolated),
mCoreSettingsObserver.getCoreSettingsLocked(),
buildSerial, autofillOptions, contentCaptureOptions,
app.getDisabledCompatChanges(), serializedSystemFontMap);
再进入到ActivityThread的bindApplication,通过Handler在进到handleBindApplication,终于看到注册ContentProvider的方法,可以看到Application创建后的回调方法在注册ContentProvider之后
app = data.info.makeApplication(data.restrictedBackupMode, null);
...
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
installContentProviders(app, data.providers);
}
}
...
try {
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
这样 AppWatcherInstaller 就会被调用。
internal sealed class AppWatcherInstaller : ContentProvider() {
/**
* [MainProcess] automatically sets up the LeakCanary code that runs in the main app process.
*/
internal class MainProcess : AppWatcherInstaller()
/**
* When using the `leakcanary-android-process` artifact instead of `leakcanary-android`,
* [LeakCanaryProcess] automatically sets up the LeakCanary code
*/
internal class LeakCanaryProcess : AppWatcherInstaller()
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
可以看到:LeakCanary是用kotlin写的,在ContentProvider的onCreate()方法内执行了AppWatcher.manualInstall(application),检测内存泄露逻辑就从这个方法开始了,接下来通过这个方法一步一步对源码进行分析。
这里有个application的变量是获取到Application,但是这个ContentProvider不是在Application.onCreate之前就启动了么?那是如何获取到Application的
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),//延迟5s
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application) //需要监控的对象
) {
checkMainThread()
//检查是否是主线程
if (isInstalled) {
throw IllegalStateException(
"AppWatcher already installed, see exception cause for prior install call", installCause
)
}
check(retainedDelayMillis >= 0) {
"retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
}
installCause = RuntimeException("manualInstall() first called here")
this.retainedDelayMillis = retainedDelayMillis
if (application.isDebuggableBuild) {
LogcatSharkLog.install()
}
// Requires AppWatcher.objectWatcher to be set
//采用反射形式进行初始化
LeakCanaryDelegate.loadLeakCanary(application)
watchersToInstall.forEach {
//添加监控对象的回调
it.install()
}
}
manualInstall 是一个很重要的方法,主要是启动AppWatcher的使用,并且其参数也是需要细细看的,第二个参数是延时时间5s,也就是延迟 5s 再去进行内存泄漏的检测。第三个参数就是需要监控对象的list。来看看都有哪些对象:
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
//avtivity监听
ActivityWatcher(application, reachabilityWatcher),
//fragment监听
FragmentAndViewModelWatcher(application, reachabilityWatcher),
//view监听
RootViewWatcher(reachabilityWatcher),
//service监听
ServiceWatcher(reachabilityWatcher)
)
}
- activity,通过 Application.ActivityLifecycleCallbacks 来判断 activity 是否已经销毁了;
- fragment,fragment 的不同版本,会有不同的处理,具体可以参考 AndroidSupportFragmentDestroyWatcher, AndroidOFragmentDestroyWatcher,AndroidXFragmentDestroyWatcher 这三个类。其中还包含了对 viewModel 的监控
- rootview,通过 OnRootViewAddedListener 来进行监控,当 android.view.WindowManager.addView 调用的时候,会对其 onRootViewAdded 进行回调,从而可以获得 rootview 。
- service,这里比较复杂,需要了解相关的源码。主要是利用反射来获取 service 相关的通知。比如获取到 mH 的 mCallback,并把自己的 callback 交给 mH,这样当 mh 收到消息就会回调 callback,然后再去调用拦截的 mCallback,这样就不会改变原有的运行轨迹。
internal object LeakCanaryDelegate {
@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
}
}
object NoLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
override fun invoke(application: Application) {
}
override fun onObjectRetained() {
}
}
}
可以发现这里反射来获取InternalLeakCanary的实例,前面调用的方式LeakCanaryDelegate.loadLeakCanary(application),这会触发 LeakCanaryDelegate 中的 invoke 方法。
那为什么会触发呢,因为 LeakCanaryDelegate 继承了一个函数
internal object InternalLeakCanary : (Application) -> Unit
接着看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 }
//提供一个后台线程的looper
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
//初始化heapDump触发器
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
//添加可见性回调
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
//对Activity状态的监听
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()
)
}
}
}
}
}
可以发现 invoke 才是 LeakCanary 启动后初始化的核心逻辑。在这里注册了很多回调,启动了后台线程,heapdump 触发器,gc 触发器等。到这里,关于 LeakCanary 的启动逻辑就讲完了。
如何触发检测
在AppWatcher.manualInstall方法中就提到四个监控对象,当四个对象的生命周期发生变化的时候,就会触发相应的检测流程。以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)
}
}
通过执行Application.registerActivityLifecycleCallbacks()注册Activity生命周期中Destroyed的监听,在onActivityDestroyed()中 添加reachabilityWatcher的回调,reachabilityWatcher就是AppWatcher中的objectWatcher这个变量
val objectWatcher = ObjectWatcher(
clock = { SystemClock.uptimeMillis() },
checkRetainedExecutor = {
check(isInstalled) {
"AppWatcher not installed"
}
mainHandler.postDelayed(it, retainedDelayMillis)
},
isEnabled = { true }
)
因此,接下去我们需要去看看 ObjectWatcher 这个类的相关逻辑了。
@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {//一般为true
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"
}
//加入观察map中
watchedObjects[key] = reference
checkRetainedExecutor.execute {
//可以知道5s后才会执行
moveToRetained(key)
}
}
expectWeaklyReachable所做的事情很简单,具体如下:
1.removeWeaklyReachableObjects先把已经回收的监控对象从watchedObjects中删除;
2.通过唯一表示key,当前时间戳来为当前需要监控的对象构造一个KeyedWeakReference,并且所有的监控对象都是共用一个queue;
3.把监控对象添加到watchedObjects中;
这里有个很关键的类KeyedWeakReference,下面来具体看看这个类的实现:
class KeyedWeakReference(
referent: Any,
val key: String,
val description: String,
val watchUptimeMillis: Long,
referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(
referent, referenceQueue
)
我们惊喜的发现KeyedWeakReference继承自WeakReference,并且新增了一些额外的参数。
这里通过 activity 为例子介绍了触发检测的逻辑,所有监控对象都是在监听到其被销毁的时候才会触发检测,一旦销毁了就会把监控对象放在 watchedObjects,等待5s后再来看是否已经被回收。
回收操作
等待5s之后再来看看moveToRetained 的具体逻辑:
@Synchronized private fun moveToRetained(key: String) {
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
可以看到再次调用了removeWeaklyReachableObjects()方法,也就是5s后,再次把已经回收的监控对象从watchedObjects中删除。
这里并不是对watchedObjects进行遍历来判断是否回收的,而是从queue中取出来对应key的对象,如果取不到就表示该对象已经被回收了,内存没有泄露;如果取到了(retainedRef != null)就表示该对象依然存活,内存泄露了。
判断对象依然存活之后可以看到更新存活对象的保留时间,触发InternalLeakCanary中的onObjectRetained()回调对泄露的对象进行检测.
override fun onObjectRetained() = scheduleRetainedObjectCheck()
fun scheduleRetainedObjectCheck() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
这里调用 HeapDumpTrigger 来对存活的对象进行检测,下面看看具体的检测逻辑:
fun scheduleRetainedObjectCheck(
delayMillis: Long = 0L
) {
val checkCurrentlyScheduledAt = checkScheduledAt
if (checkCurrentlyScheduledAt > 0) {
return
}
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
backgroundHandler.postDelayed({
checkScheduledAt = 0
checkRetainedObjects()
}, delayMillis)
}
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
)
}
} 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
}
// 判断剩下的数量小于规定数量直接返回,默认是5个起步
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
onRetainInstanceListener.onEvent(DumpHappenedRecently)
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
)
scheduleRetainedObjectCheck(
delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
)
return
}
dismissRetainedCountNotification()
val visibility = if (applicationVisible) "visible" else "not visible"
//进行heap dump
dumpHeap(
retainedReferenceCount = retainedReferenceCount,
retry = true,
reason = "$retainedReferenceCount retained objects, app is $visibility"
)
}
代码比较长,整理下相关知识点:
1.如果保留对象(未回收对象)的数量大于0,则进行一次GC,避免额外的转储堆,尽可能的将对象回收;
2.默认情况下,保留对象的数量<5,不会进行转储堆,节省资源
3.如果两次转储堆之间时间少于60s,也会直接返回,避免频繁转储堆
4.调用dunpHeap()进行真正的转储堆操作
5.当然在真正进行dump前,还需要依赖ICanHazHeap来判断是否可以进行转储堆,里面会做一些检查,确保转储堆的条件是满足的
转储堆
对于实在无法回收的,这时候就采用转储堆来将其显出原形
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean,
reason: String
) {
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
when (val heapDumpResult = heapDumper.dumpHeap()) {
//没有dump
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" }
}
//显示dump失败通知
showRetainedCountNotification(
objectCount = retainedReferenceCount,
contentText = application.getString(
R.string.leak_canary_notification_retained_dump_failed
)
)
}
//dump成功
is HeapDump -> {
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
HeapAnalyzerService.runAnalysis(
context = application,
heapDumpFile = heapDumpResult.file,
heapDumpDurationMillis = heapDumpResult.durationMillis,
heapDumpReason = reason
)
}
}
}
HeapDumpTrigger 如其名,就是一个 dump 触发器,这里最终是调用 AndroidHeapDumper 来进行 dump 的,最后会得到 dump 的结果。
可以看到上述主要讲结果分为两类,一个是 NoHeapDump,如果需要继续尝试的话,会延迟一段时间后继续重试。另一个结果自然就是成功了。
暂时先不看结果,这里先来看看 AndroidHeapDumper dump 过程,具体代码如下:
override fun dumpHeap(): DumpHeapResult {
//获取文件名,如果为null,就直接返回
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 {
//测量dump耗时
val durationMillis = measureDurationMillis {
//将data dump到指定文件中
Debug.dumpHprofData(heapDumpFile.absolutePath)
}
//文件长度为0,表明没有数据
if (heapDumpFile.length() == 0L) {
SharkLog.d { "Dumped heap file is 0 byte length" }
NoHeapDump
} else {
//存在数据,说明dump成功,同时记录耗时
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)
}
}
整体来看 AndroidHeapDumper dumpheap 方法先是创建一个 dump 的file,用于保存堆数据,然后就是通知栏的一些通知的创建, 最后就是调用 Debug.dumpHprofData(heapDumpFile.absolutePath) 来进行堆数据的转储到Android文件系统上的.hprof文件中。
下面可以看下该文件创建的代码,如下所示:
fun newHeapDumpFile(): File? {
cleanupOldHeapDumps()
var storageDirectory = externalStorageDirectory()
if (!directoryWritableAfterMkdirs(storageDirectory)) {
if (!hasStoragePermission()) {
if (requestExternalStoragePermission()) {
SharkLog.d { "WRITE_EXTERNAL_STORAGE permission not granted, requesting" }
requestWritePermissionNotification()
} else {
SharkLog.d { "WRITE_EXTERNAL_STORAGE permission not granted, ignoring" }
}
} else {
val state = Environment.getExternalStorageState()
if (Environment.MEDIA_MOUNTED != state) {
SharkLog.d { "External storage not mounted, state: $state" }
} else {
SharkLog.d {
"Could not create heap dump directory in external storage: [${storageDirectory.absolutePath}]"
}
}
}
// Fallback to app storage.
storageDirectory = appStorageDirectory()
if (!directoryWritableAfterMkdirs(storageDirectory)) {
SharkLog.d {
"Could not create heap dump directory in app storage: [${storageDirectory.absolutePath}]"
}
return null
}
}
最终会创建一个年月日时分秒的.hprof 文件。
Debug 是 android 系统自带的方法,最终也会调用 VMDebug 来实现,这个其实就是虚拟机的了。
public static void dumpHprofData(String fileName) throws IOException {
VMDebug.dumpHprofData(fileName);
}
前文提到的两种结果,其实都是继承自 DumpHeapResult,其中 HeapDump 的数据结构如下:
internal data class HeapDump(
val file: File,
val durationMillis: Long
) : DumpHeapResult()
分析堆
当堆数据成功转储,就是对 hprof 文件的分析了。Leakcanary2.0版本开源了自己实现的 hprof 文件解析以及泄漏引用链查找的功能模块(命名为shark)。
分析hprof文件的工作主要是在 HeapAnalyzerService 类中完成的。
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)
}
可以看到这里启动了一个后台service 来对数据进行解析。直接查看HeapAnalyzerService内的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()
)
}
再进入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 {
val analysisStartNanoTime = System.nanoTime()
//判断hprof文件是否存在
if (!heapDumpFile.exists()) {
val exception = IllegalArgumentException("File does not exist: $heapDumpFile")
return HeapAnalysisFailure(
heapDumpFile = heapDumpFile,
createdAtTimeMillis = System.currentTimeMillis(),
analysisDurationMillis = since(analysisStartNanoTime),
exception = HeapAnalysisException(exception)
)
}
return try {
listener.onAnalysisProgress(PARSING_HEAP_DUMP)
//捕获IO读取度量,而不使用太多内存
val sourceProvider = ConstantMemoryMetricsDualSourceProvider(FileSourceProvider(heapDumpFile))
//从DualSourceProvider中打开CloseableHeapGraph
sourceProvider.openHeapGraph(proguardMapping).use { graph ->
//封装到FindLeakInput类
val helpers =
FindLeakInput(graph, referenceMatchers, computeRetainedHeapSize, objectInspectors)
//FindLeakInput分析graph返回HeapAnalysisSuccess(堆分析成功)类
val result = helpers.analyzeGraph(
metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime
)
val lruCacheStats = (graph as HprofHeapGraph).lruCacheStats()
val randomAccessStats =
"RandomAccess[" +
"bytes=${sourceProvider.randomAccessByteReads}," +
"reads=${sourceProvider.randomAccessReadCount}," +
"travel=${sourceProvider.randomAccessByteTravel}," +
"range=${sourceProvider.byteTravelRange}," +
"size=${heapDumpFile.length()}" +
"]"
//为堆分析成功类增加metaData等信息
val stats = "$lruCacheStats $randomAccessStats"
result.copy(metadata = result.metadata + ("Stats" to stats))
}
} catch (exception: Throwable) {
HeapAnalysisFailure(
heapDumpFile = heapDumpFile,
createdAtTimeMillis = System.currentTimeMillis(),
analysisDurationMillis = since(analysisStartNanoTime),
exception = HeapAnalysisException(exception)
)
}
}
主要是将泄露的保留对象的堆转储文件(heapDumpFile)封装一下再进行分析(helpers.analyzeGraph),分析成功返回HeapAnalysisSuccess类的实例
private fun FindLeakInput.analyzeGraph(
metadataExtractor: MetadataExtractor,
leakingObjectFinder: LeakingObjectFinder,
heapDumpFile: File,
analysisStartNanoTime: Long
): HeapAnalysisSuccess {
listener.onAnalysisProgress(EXTRACTING_METADATA)
val metadata = metadataExtractor.extractMetadata(graph)
val retainedClearedWeakRefCount = KeyedWeakReferenceFinder.findKeyedWeakReferences(graph)
.filter { it.isRetained && !it.hasReferent }.count()
// This should rarely happens, as we generally remove all cleared weak refs right before a heap
// dump.
val metadataWithCount = if (retainedClearedWeakRefCount > 0) {
metadata + ("Count of retained yet cleared" to "$retainedClearedWeakRefCount KeyedWeakReference instances")
} else {
metadata
}
listener.onAnalysisProgress(FINDING_RETAINED_OBJECTS)
val leakingObjectIds = leakingObjectFinder.findLeakingObjectIds(graph)
val (applicationLeaks, libraryLeaks, unreachableObjects) = findLeaks(leakingObjectIds)
return HeapAnalysisSuccess(
heapDumpFile = heapDumpFile,
createdAtTimeMillis = System.currentTimeMillis(),
analysisDurationMillis = since(analysisStartNanoTime),
metadata = metadataWithCount,
applicationLeaks = applicationLeaks,
libraryLeaks = libraryLeaks,
unreachableObjects = unreachableObjects
)
}
可以看到分析成功HeapAnalysisSuccess包括堆转储文件,创建时间,堆转储时长,堆分析时长,元数据信息以及应用程序泄露和库泄露,这里可以看到LeakCanary将应用中的泄露分类了。