如何获取上下文信息
LeakCanary 只需引入依赖,不需要初始化代码,就能执行内存泄漏检测了,原理是通过 ContentProvider 来获取应用的 Context,并在 onCreate 中执行初始化逻辑
internal sealed class AppWatcherInstaller : ContentProvider() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
}
默认检测哪些类对象的内存泄露
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
该方法用于注册需要检测的对象,可以看到 LeakCanary 会把 Activity、Fragment、FragmentView、ViewModel、RootView 和 Service 纳入检测,这些对象都有明确的生命周期,而且占用内存较高,它们的内存泄露是需要我们重点关注的
如何将这些生命周期对象纳入监测
AppWatcher.manualInstall 方法将遍历调用上述 XXXWatcher 的 install 方法以适时将这些生命周期对象纳入检测,下面逐一分析
ActivityWatcher
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
reachabilityWatcher.expectWeaklyReachable(activity, "received onDestroy")
}
}
override fun install() {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
ActivityWatcher.install 方法通过向 Application 注册 Application.ActivityLifecycleCallbacks 接口回调实现对 Activity 生命周期的检测
我们可以在不再需要某对象时主动调用该方法,检测任意对象(除上节的默认对象)的内存泄露:
AppWatcher.objectWatcher.expectWeaklyReachable(obj, "")
FragmentAndViewModelWatcher
Fragment 为了兼容在 Android 源码中几个不同包名的实现,对它们的检测也需要分别实现,我们在 FragmentAndViewModelWatcher 中只关注 AndroidXFragmentDestroyWatcher 对 AndroidX 中 Fragment 的内存泄露检测即可,其他几个实现类似
FragmentAndViewModelWatcher 同样通过注册 Application.ActivityLifecycleCallbacks 回调,适时获取 Activity 引用和对应的 supportFragmentManager,并注册 FragmentLifecycleCallbacks。在其中的 onFragmentDestroyed 与 onFragmentViewDestroyed 回调中将 Fragment 和 Fragment 的 View 纳入内存泄露检测
对于 ViewModel 的检测,则需要关注 ViewModelClearedWatcher,通过用上一步获取的 Activity 引用,添加名为 ViewModelClearedWatcher 的 spy ViewModel,来获得收到 onCleared 回调的能力,因为对于一个 ViewModelStoreOwner(Activity,Fragment)来说,自己的一个 ViewModel 回调了 onCleared,则其他 ViewModel 的 onCleared 也应该被调用。这些 ViewModel 是通过 ViewModelStore 的 mMap 属性反射获取的。在 spy ViewModel 的 onCleared 回调中,纳入内存泄露检测。另外,在 onFragmentCreated 中也需要对应添加 Fragment 的 spy ViewModel
RootViewWatcher
对于 Android 里 Window 中的 RootView,即 DecorView,可以通过注册addOnAttachStateChangeListener 在 View 的 onViewDetachedFromWindow 时进行检测。而获取待检测对象的引用就不像 Activity 和 Fragment 一样有回调可以依赖了。LeakCanary 采取了 Hook 的方式在 install 方法对 RootView 的容器进行替换,具体来说就是通过反射机制将 WindowManagerGlobal 中的 mViews 对应的 ArrayList 容器进行修改,在其 add 方法中获取 DecorView 的引用,之后设置 OnAttachStateChangeListener 回调进行检测
ServiceWatcher
而 Android 中 Service,无论是获取引用还是监测时机的确定都没有系统的回调可以依赖, LeakCanary 都是采用 hook 的方式达到目的。首先通过反射拿到 ActivityThread 中的 mServices,这是包含 app 中全部 Service 的一个 Map。在 install 方法中有两个 Hook 点,首先是 Android 消息机制的中转中心,名为 mH 的 Handler,系统侧对应用侧的全部回调都需要经过它的周转。因为 Handler 中 mCallback 执行的优先级大于 handleMessage 方法,Leakcanary 替换 mH 的 mCallback 实现,当消息为 STOP_SERVICE 时,便从 mServices 取出该消息对应的 Service 作为待检测 Service 引用。第二个 Hook 点为 ActivityManagerNative(IActivityManager),通过动态代理修改它的 serviceDoneExecuting 方法,在其真正实现前增加内存泄露检测,其余方法保持不变
如何获取引用 | 何时纳入监测 | |
Activity | ActivityLifecycleCallbacks 回调 | onActivityDestroyed |
Fragment | FragmentLifecycleCallbacks 回调 | onFragmentDestroyed |
Fragment.View | FragmentLifecycleCallbacks 回调 | onFragmentViewDestroyed |
ViewModel | 反射获取 ViewModelStore 的 mMaps | spy ViewModel的onCleared |
DecorView | Hook WindowManagerGlobal中的 mViews | onViewDetachedFromWindow |
Service |
Hook ActivityThread.mH 的 mCallback 实现,当消息为 STOP_SERVICE 时,从 ActivityThread 中的 mServices 获取 | Hook ActivityManagerNative 的 serviceDoneExecuting 方法 |
如何确定内存泄露的对象
在确定待检测对象与时机后,查看 ObjectWatcher 的 expectWeaklyReachable 方法,可以得知如何实现将泄露对象从待检测对象挑出来的。原理是我们常用的 WeakReference,其参数构造函数支持传入一个 ReferenceQueue,当其关联的对象回收时,会将 WeakReference 加入 ReferenceQueue 中。LeakCanary 的做法是继承 ReferenceQueue,增加一个值为 UUID 的属性 key,同时将每个需要监测的对象 WeakReference 以此 UUID 作为键加入一个 map 中。这样,在 GC 过后,removeWeaklyReachableObjects 方法通过遍历 ReferenceQueue,通过 key 值删除 map 中已回收的对象,剩下的对象就基本可以确定发生了内存泄露
为什么 LeakCanary 不能用于线上
- 每次内存泄漏以后,都会生成并解析 hprof 文件,容易引起手机卡顿等问题
- 多次调用 GC,可能会对线上性能产生影响
- hprof 文件较大,信息回捞成问题