写点什么

【带着问题学】关于 LeakCanary2,2021 百度 Android 岗面试真题收录解析

用户头像
Android架构
关注
发布于: 2021 年 11 月 05 日

ContentProvider会在Application.onCreate前初始化,这样就调用到了LeakCanary的初始化方法


实现了免手动初始化

[](

)2.1 跨进程初始化


注意,AppWatcherInstaller有两个子类,MainProcessLeakCanaryProcess


其中默认使用MainProcess,会在App进程初始化


有时我们考虑到LeakCanary比较耗内存,需要在独立进程初始化


使用leakcanary-android-process模块的时候,会在一个新的进程中去开启LeakCanary

[](

)2.2 LeakCanary2.0手动初始化的方法


LeakCanary在检测内存泄漏时比较耗时,同时会打断App操作,在不需要检测时的体验并不太好


所以虽然LeakCanary可以自动初始化,但我们有时其实还是需要手动初始化


LeakCanary的自动初始化可以手动关闭


<?xml version="1.0" encoding="utf-8"?>


<resources>


<bool name="leak_canary_watcher_auto_install">false</bool>


</resources>


复制代码


1.然后在需要初始化的时候,调用AppWatcher.manualInstall即可


2.是否开始dump与分析开头:LeakCanary.config = LeakCanary.config.copy(dumpHeap = false)


3.桌面图标开头:重写R.bool.leak_canary_add_launcher_icon或者调用LeakCanary.showLeakDisplayActivityLauncherIcon(false)

[](

)2.3 小结


LeakCanary利用ContentProvier进行了初始化。


ContentProvier一般会在Application.onCreate之前被加载,LeakCanary在其onCreate()方法中调用了AppWatcher.manualInstall进行初始化


这种写法虽然方便,免去了初始化的步骤,但是可能会带来启动耗时的问题,用户不能控制初始化的时机,这也是谷歌推出StartUp的原因


不过对于LeakCanary这个问题并不严重,因为它只在Debug阶段被依赖


[](


)3.LeakCanary如何检测内存泄漏?



[](

)3.1 首先我们来看下初始化时做了什么?


当我们初始化时,调用了AppWatcher.manualInstall,下面来看看这个方法,都安装了什么东西


@JvmOverloads


fun manualInstall(


application: Application,


retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),


watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)


) {


....


watchersToInstall.forEach {


it.install()


}


}


fun appDefaultWatchers(


application: Application,


reachabilityWatcher: ReachabilityWatcher = objectWatcher


): List<InstallableWatcher> {


return listOf(


ActivityWatcher(application, reachabilityWatcher),


FragmentAndViewModelWatcher(application, reachabilityWatcher),


RootViewWatcher(reachabilityWatcher),


ServiceWatcher(reachabilityWatcher)


)


}


可以看出,初始化时即安装了一些Watcher,即在默认情况下,我们只会观察Activity,Fragment,RootView,Service这些对象是否泄漏


如果需要观察其他对象,需要手动添加并处理

[](

)3.2 LeakCanary如何触发检测?


如上文所述,在初始化时会安装一些Watcher,我们以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)


}


}


可以看到在Activity.onDestory时,就会触发检测内存泄漏

[](

)3.3 LeakCanary如何检测可能泄漏的对象?


从上面可以看出,Activity关闭后会调用到ObjectWatcher.expectWeaklyReachable


@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)


}


}


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)


}


可以看出


1.传入的观察对象都会被存储在watchedObjects


2.会为每个watchedObject生成一个KeyedWeakReference弱引用对象并与一个queue关联,当对象被回收时,该弱引用对象将进入queue当中


3.在检测过程中,我们会调用多次removeWeaklyReachableObjects,将已回收对象从watchedObjects中移除


4.如果watchedObjects中没有移除对象,证明它没有被回收,那么就会调用moveToRetained

[](

)3.4 LeakCanary触发堆快照,生成hprof文件


moveToRetained之后会调用到HeapDumpTrigger.checkRetainedInstances方法


checkRetainedInstances() 方法是确定泄露的最后一个方法了。


这里会确认引用是否真的泄露,如果真的泄露,则发起 heap dump,分析 dump 文件,找到引用链


private fun checkRetainedObjects() {


var retainedReferenceCount = objectWatcher.retainedObjectCount


if (retainedReferenceCount > 0) {


gcTrigger.runGc()


retainedReferenceCount = objectWatcher.retainedObjectCount


}


if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThr


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


eshold)) return


val now = SystemClock.uptimeMillis()


val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis


if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {


onRetainInstanceListener.onEvent(DumpHappenedRecently)


....


return


}


dismissRetainedCountNotification()


val visibility = if (applicationVisible) "visible" else "not visible"


dumpHeap(


retainedReferenceCount = retainedReferenceCount,


retry = true,


reason = "visibility"


)


}


private fun dumpHeap(


retainedReferenceCount: Int,


retry: Boolean,


reason: String


) {


....


heapDumper.dumpHeap()


....


lastDisplayedRetainedObjectCount = 0


lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()


objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)


HeapAnalyzerService.runAnalysis(


context = application,


heapDumpFile = heapDumpResult.file,


heapDumpDurationMillis = heapDumpResult.durationMillis,


heapDumpReason = reason


)


}


}


1.如果retainedObjectCount数量大于 0,则进行一次GC,避免额外的Dump


2.默认情况下,如果retainedReferenceCount<5,不会进行Dump,节省资源


3.如果两次Dump之间时间少于 60s,也会直接返回,避免频繁Dump


4.调用heapDumper.dumpHeap()进行真正的Dump操作


5.Dump之后,要删除已经处理过了的引用


6.调用HeapAnalyzerService.runAnalysis对结果进行分析

[](

)3.5 LeakCanary如何分析hprof文件


分析hprof文件的工作主要是在HeapAnalyzerService类中完成的


关于Hprof文件的解析细节,就需要牵扯到Hprof二进制文件协议,通过阅读协议文档,hprof的二进制文件结构大概如下:



解析流程如下所示:



简要说下流程:


1.解析文件头信息,得到解析开始位置


2.根据头信息创建Hprof文件对象


3.构建内存索引


4.使用hprof对象和索引构建Graph对象

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
【带着问题学】关于LeakCanary2,2021百度Android岗面试真题收录解析