LeakCanary是Android开发中常用的内存泄漏检测工具,
使用方法
在远古的1.X版本中,我们在build.gradle中添加完依赖之后,还需要在Application的onCreate()中调用install()进行初始化。相比起1.X版本,从2.0之后的版本只需要通过 debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.6' 添加依赖即可以在只debug包下生效。
原理
怎么做到的呢,其实是借助ContentProvider实现的。既然是四大组件之一,那么其注册一定在AndroidManifest.xml文件中,在这里我们可以通过点击下图中红色框框进行合并清单文件。
如图所示,绿色框中的便是我们的目标ContentProvider,在我鼠标选中的一行中可见关键类名AppWatcherInstaller。点进去可以看的到AppWatcherInstaller是ContentProvider的子类,并且重写了 onCreate() 方法。
AppWatcherInstaller中的onCreate()方法如下所示:
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
如果你问我为啥应用在启动的时候,会通过ActivityThread创建并初始化。我只能说,看源码去。(指路:ActivityThread->handleMessage->handleBindApplication->installContentProviders)
通过以上分析,我们可以知道再引入依赖后,依赖包中的Androidmainfest.xml文件会被合并到主AndroidMainfest.xml文件中,然后在启动应用时创建ContentProvider,并进行AppWatcher.manualInstall(application),这就是LeakCanary2.X版本无感初始化的原理。
如何检测Activity内存泄漏:
走进InternalAppWatcher.install(application),如下图所示:
首先// 检查当前是否在主线程 红色框框中分别是监听了Activity.和Fragment的onDestroy()方法
然后我们点进来ActivityDestroyWatcher,在这个类里的install()方法中会注册一个ActivityLifecycleCallbacks回调,进行Activity的生命周期监听,当Activity执行了onDestroy()方法后,就会将这个Activity对象交给ObjectWatcher。
比较有意思的是FragmentDestroyWatcher,这个类是为了检测不同系统环境下Fragment的销毁情况,有点中转站的意味,但是整体代码思路和ActivityDestroyWatcher差不多。 其中的“中转”处理有以下几部分: 1.当系统版本大于等于Android8.0(API=26)的时候,调用AndroidOFragmentDestroyWatcher进行检测。 2.如果当前项目使用的是 Support 包。调用 AndroidSupportFragmentDestroyWatcher 来检测 3.如果当前项目使用的是 AndroidX 包。调用 AndroidXFragmentDestroyWatcher 来检测。
怎么区分当前项目用的是Support还是AndroidX呢,答案是通过反射 Class.forName。
总结
- 使用kotlin重写
- 利用cp实现无感初始化,并且仅在debug包生效
- 使用自研的shark库替代haha库
- 减少了 90% 的内存占用,而且比原来快了 6 倍
- dump Heap 之前,会尝试 GC 一次,再判断剩余对象个数,若大于等于阈值(默认 5 个),则会通过 Debug.dumpHprofData 获取 hprof
- 观察者模式
使用 ContentProvider的优劣:也只是LeakCanary仅在debug生效,所以这一影响启动速度的点其实无足轻重。而如果想要控制 使用ContentProvider的优劣 初始化的顺序,则可以参考:sivanliu.github.io/2017/12/16/… 简单说,就是通过在Manifest 文件中设置initOrder 值(值越大,越先初始化)。
同时如果Library中存在多进程,也需要设置 android:multiprocess。 如果别的第三方库也只能用cp去初始化呢?我们应该怎样去控制APP的启动速度,这就涉及到cp的优化: 我们可以使用AOP方式进行插桩,通过Gradle+Transform+ASM进行修改ContentProvider的onCreate方法,提前返回,然后手动去调用初始化代码,如果这些初始化代码是私有的或者只限制包内使用的,也可以通过ASM去修改访问权限,然后在我们想初始化的地方再进行初始化,这可能涉及到一个先后的问题,需要先修改完然后再在某个地方初始化,这里只是提供一个思路。
参考链接
关于合并清单文件:developer.android.google.cn/studio/buil…
腾讯的matrix:github.com/Tencent/mat…
官网链接:square.github.io/leakcanary
(图片有点问题,明天再加~