当当当,结果一打开 app 就报错了:
? E: FATAL EXCEPTION: main
Process: com.sam.demo, PID: 28838
java.lang.Error: LeakCanary in non-debuggable build
LeakCanary should only be used in debug builds, but this APK is not debuggable.
Please follow the instructions on the “Getting started” page to only include LeakCanary in
debug builds: https://square.github.io/leakcanary/getting_started/
If you’re sure you want to include LeakCanary in a non-debuggable build, follow the
instructions here: https://square.github.io/leakcanary/recipes/#leakcanary-in-release-builds
at leakcanary.internal.InternalLeakCanary.checkRunningInDebuggableBuild(InternalLeakCanary.kt:160)
报错的地方的调用链
AppWatcher.manualInstall(application)
-> InternalAppWatcher.install(application)
-> nternalAppWatcher.onAppWatcherInstalled(application)
-> InternalLeakCanary.invoke()
-> InternalLeakCanary.checkRunningInDebuggableBuild()
private fun checkRunningInDebuggableBuild() {
if (isDebuggableBuild) {
return
}
if (!application.resources.getBoolean(R.bool.leak_canary_allow_in_non_debuggable_build)) {
throw Error(…)
}
}
所以如果你真的想作死在 release 也引入 leakCanary,只需要:
// 在 app 重写这个 bool 值
// 自己开发 SDK 时,这种配置方式学会了吗?
true
监听泄漏的时机
===================================================================
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
}
}
override fun onCleared() {
if (viewModelMap != null && configProvider().watchViewModels) {
viewModelMap.values.forEach { viewModel ->
objectWatcher.watch( viewModel, … )
}
}
}
检测一个对象是否泄露
======================================================================
一个 JVM 的基础知识:
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. 若之前有显示有泄漏,且当前已经全部回收,显示无泄漏的通知
最后其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。
rue 不继续后面的流程
// 1. 若之前有显示有泄漏,且当前已经全部回收,显示无泄漏的通知
最后其实Android开发的知识点就那么多,面试问来问去还是那么点东西。所以面试没有其他的诀窍,只看你对这些知识点准备的充分程度。so,出去面试时先看看自己复习到了哪个阶段就好。
当然我也为你们整理好了百度、阿里、腾讯、字节跳动等等互联网超级大厂的历年面试真题集锦。这也是我这些年来养成的习惯,一定要学会把好的东西,归纳整理,然后系统的消化吸收,这样才能极大的提高学习效率和成长进阶。碎片、零散化的东西,我觉得最没有价值的。就好比你给我一张扑克牌,我只会觉得它是一张废纸,但如果你给我一副扑克牌,它便有了它的价值。这和我们收集资料就要收集那些系统化的,是一个道理。
[外链图片转存中…(img-mPderhTS-1647705293455)]
网上学习 Android的资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。希望这份系统化的技术体系对大家有一个方向参考。



