===================================================================
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移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
**要想成为高级安卓工程师,必须掌握许多基础的知识。**在工作中,这些原理可以极大的帮助我们理解技术,在面试中,更是可以帮助我们应对大厂面试官的刁难。
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!**
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
**要想成为高级安卓工程师,必须掌握许多基础的知识。**在工作中,这些原理可以极大的帮助我们理解技术,在面试中,更是可以帮助我们应对大厂面试官的刁难。
[外链图片转存中…(img-t7jjcOk8-1712781918249)]
[外链图片转存中…(img-p3gjDYx8-1712781918249)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!