0
点赞
收藏
分享

微信扫一扫

LeakCanary 分析:一只优雅的金丝雀,真香定律

慕容冲_a4b8 2022-03-11 阅读 160
面试

dependencies {

// debugImplementation because LeakCanary should only run in debug builds.

debugImplementation ‘com.squareup.leakcanary:leakcanary-android:2.4’

}

「That’s it, there is no code change needed!」

为什么一行就能搞定呢。

class AppWatcherInstaller : ContentProvider() {

override fun onCreate(): Boolean {

val application = context!!.applicationContext as Application

AppWatcher.manualInstall(application) // auto install

return true

}

}

应用主进程启动时会自动初始化定义在 Manifest 里的 ContentProvider,也就能自动调用 AppWatcher.manualInstall。是不是觉得这样很优雅,每个第三方库都整个 ContentProvider 岂不是重复劳动?Jetpack 的 App Startup 了解一下,基于这个原理的封装。https://developer.android.google.cn/topic/libraries/app-startup

那我就是想自己手动初始化(比如启动速度优化),要怎么处理?

答案就在定义这个 ContentProvider 的地方:

<provider

android:name=“leakcanary.internal.AppWatcherInstaller$MainProcess”

android:authorities=“com.sam.demo.leakcanary-installer”

android:enabled="@bool/leak_canary_watcher_auto_install" // here

android:exported=“false” />

// 我们在 app 重写这个 bool 值就可以取消自动安装

false

// 然后手动在合适的地方调用 AppWatcher.manualInstall 初始化

不过话又说回来,这个库是在 debug 环境下运行的,谁会闲着没事去优化 Debug 版本呢,净装X。

emmm,那问题来了,我在 release 使用会怎样呢?做如下修改,在 Build Variants 选择 release 跑起来看看:

dependencies {

implementation ‘com.squareup.leakcanary:leakcanary-android:2.4’

}

当当当,结果一打开 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<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)

}

最后

对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长。而不成体系的学习效果低效漫长且无助。时间久了,付出巨大的时间成本和努力,没有看到应有的效果,会气馁是再正常不过的。

所以学习一定要找到最适合自己的方式,有一个思路方法,不然不止浪费时间,更可能把未来发展都一起耽误了。

如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。以上知识笔记全部免费分享,如有需要获取知识笔记的朋友,可以点击我的GitHub免费领取。

举报

相关推荐

0 条评论