【带着问题学】关于 LeakCanary2,2021 百度 Android 岗面试真题收录解析
ContentProvider
会在Application.onCreate
前初始化,这样就调用到了LeakCanary
的初始化方法
实现了免手动初始化
[](
)2.1 跨进程初始化
注意,AppWatcherInstaller
有两个子类,MainProcess
与LeakCanaryProcess
其中默认使用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
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
对象
评论