LeakCanary 分析:一只优雅的金丝雀,【秋招面试专题解析】

本文详细介绍了如何在Android应用中通过监听Activity、Fragment和ViewModel的生命周期,以及使用WeakReference和KeyedWeakReference来检测内存泄漏。重点讲解了LeakCanary库的ObjectWatcher组件,以及如何结合GC、ReferenceQueue和HeapDump进行内存泄漏的精确检测和分析。
摘要由CSDN通过智能技术生成

监听泄漏的时机

===================================================================

Activity

没啥好说的,通过 registerActivityLifecycleCallbacks 监听 Activity 生命周期回调,在 onActivityDestroyed 时,objectWatcher.watch(activity, …)

Fragment、fragment.view

则是尝试从不同的 fragmentManager 加监听(emmm 策略模式)

  • O(奥利奥)以上 -> activity.fragmentManager (AndroidOFragmentDestroyWatcher.kt)

  • androidX -> activity.supportFragmentManager (AndroidXFragmentDestroyWatcher.kt)

  • support 包 -> activity.supportFragmentManager (AndroidSupportFragmentDestroyWatcher.kt)

调用 fragmentManager.registerFragmentLifecycleCallbacks 监听。

而上面用到的 activity 也是通过 registerActivityLifecycleCallbacks 的 onActivityCreated 拿到的

AndroidOFragmentDestroyWatcher.kt

override fun onFragmentViewDestroyed(

fm: FragmentManager,

fragment: Fragment

) {

val view = fragment.view

// 观察 view 是否回收

objectWatcher.watch(view,…)

}

override fun onFragmentDestroyed(

fm: FragmentManager,

fragment: Fragment

) {

// 观察 fragment 对象是否回收

objectWatcher.watch(fragment,…)

}

ViewModel

在前面讲到的 AndroidXFragmentDestroyWatcher.kt 里,还会额外监听

override fun onFragmentCreated(

fm: FragmentManager,

fragment: Fragment,

savedInstanceState: Bundle?

) {

ViewModelClearedWatcher.install(fragment, objectWatcher, configProvider)

}

install 的具体实现是在这个 fragment 的 ViewModelProvider 取一个 ViewModelClearedWatcher。这也是一个 ViewModel , 在它被回收时会回调 onCleared 方法将所有 ViewModel 加入观察

init {

// We could call ViewModelStore#keys with a package spy in androidx.lifecycle instead,

// however that was added in 2.1.0 and we support AndroidX first stable release. viewmodel-2.0.0

// does not have ViewModelStore#keys. All versions currently have the mMap field.

// 通过反射获取以这个 fragment 为 onwer 所有的 viewModel

viewModelMap = try {

val mMapField = ViewModelStore::class.java.getDeclaredField(“mMap”)

mMapField.isAccessible = true

@Suppress(“UNCHECKED_CAST”)

mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>

}

}

override fun onCleared() {

if (viewModelMap != null && configProvider().watchViewModels) {

viewModelMap.values.forEach { viewModel ->

objectWatcher.watch( viewModel, … )

}

}

}

检测一个对象是否泄露

======================================================================

一个 JVM 的基础知识:

/**

  • Suppose that the garbage collector determines at a certain point in time

  • that an object is weakly

  • reachable. At that time it will atomically clear all weak references to

  • that object and all weak references to any other weakly-reachable objects

  • from which that object is reachable through a chain of strong and soft

  • references. At the same time it will declare all of the formerly

  • weakly-reachable objects to be finalizable. At the same time or at some

  • later time it will enqueue those newly-cleared weak references that are

  • registered with reference queues.

*/

public class WeakReference extends Reference {

public WeakReference(T referent, ReferenceQueue<? super T> q) {

super(referent, q);

}

}

Java 中的 WeakReference 是弱引用类型,每当发生 GC 时,它所持有的对象如果没有被其他强引用所持有,那么它所引用的对象就会被回收,同时或者稍后的时间这个 WeakReference 会被入队到 ReferenceQueue. LeakCanary 中对内存泄露的检测正是基于这个原理。

实现要点:

  • 当一个 Object 需要被回收时,对应生成一个 key ,封装到自定义的 KeyedWeakReference 中,并且在 KeyedWeakReference 的构造器中传入自定义的 ReferenceQueue。

  • 同时将这个 KeyedWeakReference 缓存一份到 Map 中( ObjectWatcher.watchedObjects )

  • 最后主动触发 GC,遍历自定义 ReferenceQueue 中所有的记录,并根据获取的 KeyedWeakReference 里 key 的值,移除 Map 中相应的项。

「经过上面 3 步之后,还保留在 Map 中的就是:应当被 GC 回收,但是实际还保留在内存中的对象,也就是发生泄漏了的对象。」

我们来看下具体的代码:

ObjectWatcher.kt

fun watch(

watchedObject: Any,

description: String

) {

// 遍历 queue ,从 watchedObjects 移除相应项

removeWeaklyReachableObjects()

val key = UUID.randomUUID().toString()

val watchUptimeMillis = clock.uptimeMillis()

val reference =

KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)

}

watchedObjects[key] = reference

checkRetainedExecutor.execute {

// checkRetainedExecutor 通过 Handler.postDelayed 实现

// 默认延迟 5s , 去保留的对象里查看一下这个 key 是否还在

moveToRetained(key)

}

}

fun moveToRetained(key: String) {

removeWeaklyReachableObjects() // 再检查一遍是否已经回收

val retainedRef = watchedObjects[key]

if (retainedRef != null) {

retainedRef.retainedUptimeMillis = clock.uptimeMillis()

onObjectRetainedListeners.forEach { it.onObjectRetained() }

}

}

5S 后检查对象还在的话,调用 onObjectRetained 方法通知处理,调用到的是 InternalLeakCanary 的实现

override fun onObjectRetained() {

if (this::heapDumpTrigger.isInitialized) {

// 看名字就知道,这是触发 heap dump 相关的逻辑

heapDumpTrigger.onObjectRetained()

}

}

接着看 HeapDumpTrigger 里的相关调用:

onObjectRetained()

-> scheduleRetainedObjectCheck(…)

-> checkRetainedObjects(reason)

ivate fun checkRetainedObjects(reason: String) {

val config = configProvider()

var retainedReferenceCount = objectWatcher.retainedObjectCount

if (retainedReferenceCount > 0) {

// 执行一次 GC ,再来看还剩下多少对象未被回收

// 小细节:GC 之后还 sleep(100) 等回收的引用入队

gcTrigger.runGc()

retainedReferenceCount = objectWatcher.retainedObjectCount

}

// checkRetainedCount 以下两种情况 return true 不继续后面的流程

// 1. 若之前有显示有泄漏,且当前已经全部回收,显示无泄漏的通知

// 2. 存留的对象超过 5 个(默认,可配)且 (app 可见或不可见超过 5s),

// 延迟 2s 再进行检查(避免应用卡频繁卡)

// 判断 app 是否可见代码:VisibilityTracker.kt

if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {

// 如果正在调试,可能影响检测结果,还是晚点再试吧

scheduleRetainedObjectCheck(

reason = “debugger is attached”,

rescheduling = true,

delayMillis = WAIT_FOR_DEBUG_MILLIS

)

return

}

val now = SystemClock.uptimeMillis()

val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis

if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {

// 一分钟之内才 dump 过,等离上次 dump 1分钟了再来吧

scheduleRetainedObjectCheck(

reason = “previous heap dump was ${xx}ms ago (< ${xx}ms)”,

rescheduling = true,

delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis

)

return

}

// 终极操作

// 通过 Debug.dumpHprofData(filePath) dump heap

// objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis) 清除这次 dump 开始以前的所有引用

// HeapAnalyzerService.runAnalysis 通过一个 IntentService 去分析 heap

dumpHeap(retainedReferenceCount, retry = true)

}

HeapAnalyzerService 里调用的是 Shark 库对 heap 进行分析,分析的结果再返回到 DefaultOnHeapAnalyzedListener.onHeapAnalyzed 进行分析结果入库、发送通知消息。

Shark :Shark is the heap analyzer that powers LeakCanary 2. It’s a Kotlin standalone heap analysis library that runs at 「high speed」 with a 「low memory footprint」.

还记得比较早的版本,用的是 Square 公司出品叫 「haha」 库,库写这么好就算了,起名字都这么会起,真是厉害了。

彩蛋环节

================================================================

鲨鱼咬着金丝雀,爱雀人士表示强烈谴责(手动狗头)

^`. .=“”=.

^_ \ \ / _ _ \

\ \ { \ | d b |

{ \ / `~~~–__ \ /\ /

{ ___----’ `-_/‘-=/=-’,

\ /// a `~. \ \

/ /~~~~-, ,__. , /// __,) \ |

/ / ~~~; ,---~~-_/ \ / /

/ / ‘. .’

‘._.’ |~~|

/|\ /|\

又有彩蛋,我都爱上看源码了,出自:https://github.com/square/leakcanary/blob/main/docs/shark.md

总结

==============================================================

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

**要想成为高级安卓工程师,必须掌握许多基础的知识。**在工作中,这些原理可以极大的帮助我们理解技术,在面试中,更是可以帮助我们应对大厂面试官的刁难。


《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!**

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

**要想成为高级安卓工程师,必须掌握许多基础的知识。**在工作中,这些原理可以极大的帮助我们理解技术,在面试中,更是可以帮助我们应对大厂面试官的刁难。


[外链图片转存中…(img-t7jjcOk8-1712781918249)]

[外链图片转存中…(img-p3gjDYx8-1712781918249)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值