一篇文章搞定《LeakCanary源码解析》
- 前言
- LeakCanary和LeakCanary2区别
- LeakCanary的快速使用
- 第一步:添加依赖
- 第二步:初始化LeakCanary
- 第三步:运行应用程序并监测内存泄漏
- LeakCanary基础铺垫
- 四大引用
- WeakReference和ReferenceQueue
- Refercence
- ReferenceQueue
- WeakReference
- LeakCanary源码解析
- 初始化
- LeakCanary1的install
- LeakCanary2的install
- AppWatcherInstaller.kt
- AppWatcher.manualInstall()
- appDefaultWatchers
- InternalLeakCanary 引擎初始化
- 注入对四种 Android 泄漏场景的监控
- Activity监听:ActivityWatcher
- Fragment 监听:FragmentAndViewModelWatcher
- RootView 监控:RootViewWatcher
- Service监听:ServiceWatcher
- 重点:内存检测如何检测的
- objectWatcher的创建
- ObjectWatcher
- LeakCanary的分析泄漏
- LeakCanary的常见问题
- IdleHandle在LeakCanay的应用
- LeakCanary的泄漏是通过两次GC定位的
- 总结
前言
首先内存泄漏前面也有讲解到,而且也是我们在工作中,常见并且一定会遇见的问题之一。
大家都知道,其实内存泄漏的原因就是:最根本的原因就是该回收的对象没有被及时回收掉,导致了内存泄露。
那么我们在处理这类问题的时候,一定会接触到我们的LeakCanary这个三方框架去帮助我们定位我们的内存泄漏问题。
本篇文章就来讲解一下LeakCanary中的源码,文章目录如下:
1、LeakCanary和LeakCanary2区别
2、LeakCanary的快速使用
3、LeakCanary基础铺垫
4、LeakCanary源码解析
5、LeakCanary的常见问题
LeakCanary和LeakCanary2区别
- LeakCanary2完全使用 Kotlin 重写
- LeakCanary2使用新的 Heap 分析工具 Shark,替换到之前的 haha,按官方的说法,内存占用减少了 10 倍
- LeakCanary2只需要Gradle依赖就可以使用,LeakCanary1还需要进行手动安:LeakCanary.install(this);
LeakCanary的快速使用
第一步:添加依赖
在项目的build.gradle文件中,添加以下依赖:
dependencies {
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
}
第二步:初始化LeakCanary
在Application的onCreate()方法中,添加以下代码进行LeakCanary的初始化:
if (LeakCanary.isInAnalyzerProcess(this)) {
// This process is dedicated to LeakCanary for heap analysis.
// You should not init your app in this process.
return;
}
LeakCanary.install(this);
第三步:运行应用程序并监测内存泄漏
现在,每当你运行你的应用程序并存在内存泄漏时,LeakCanary将会在你的设备上显示一个通知。
当你观察到内存泄漏的通知时,你可以点击通知打开LeakCanary的分析结果。这将显示与泄漏相关的类、对象引用的路径、引用链和更多详细信息。通过仔细阅读这些信息,你可以确定内存泄漏的原因以及泄漏的位置。
具体的使用参考《一篇文章搞定《Android内存泄漏》》
LeakCanary基础铺垫
四大引用
这个在《一篇文章搞定《JVM的完美图解》》已经提及过,这里就直接粘贴过来了。
强引用(Strong Reference)
在代码中普遍存在的,类似Object obj = new Object()这类引用,只要强引用还在,垃圾收集器永远不会回收掉被引用的对象。
软引用(Sofe Reference)
有用但并非必需的对象,可用SoftReference类来实现软引用。在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。
弱引用(Weak Reference)
非必需的对象,但它的强度比软引用更弱,被弱引用关联的对象只能生存到下一次垃圾收集发生之前,JDK提供了WeakReference类来实现弱引用。无论当前内存是否足够,用软引用相关联的对象都会被回收掉。(解决一些内存泄漏的问题都会利用到弱引用)
虚引用(Phantom Reference)
虚引用也称为幽灵引用或幻影引用,是最弱的一种引用关系,JDK提供了PhantomReference类来实现虚引用。为一个对象设置虚引用的唯一目的是:能在这个对象在垃圾回收器回收时收到一个系统通知。
WeakReference和ReferenceQueue
LeakCanary的核心原理是基于WeakReference(弱引用)和ReferenceQueue(引用队列)进行检测。因此在讲解LeakCanary之前,我们先来简单了解一下这些关键的字眼。
Refercence
首先我们先来说一些Reference,因为他是我们四大引用的父类。WeakReference也不例外。
Reference即引用,是一个泛型抽象类。Android中的SoftReference(软引用)、WeakReference(弱引用)、PhantomReference(虚引用)都是继承自Reference。
来看下Reference的几个主要成员变量。
public abstract class Reference<T> {
// 引用对象,被回收时置null
volatile T referent;
//保存即将被回收的reference对象
final ReferenceQueue<? super T> queue;
//在Enqueued状态下即引用加入队列时,指向下一个待处理Reference对象,默认为null
Reference queueNext;
//在Pending状态下,待入列引用,默认为null
Reference<?> pendingNext;
}
Reference 把内存分为 4 种状态,Active 、 Pending 、 Enqueued 、 Inactive。
- Active 一般说来内存一开始被分配的状态都是 Active
- Pending 快要放入队列(ReferenceQueue)的对象,也就是马上要回收的对象
- Enqueued 对象已经进入队列,已经被回收的对象。方便我们查询某个对象是否被回收
- Inactive 最终的状态,无法变成其他的状态。
ReferenceQueue
这个没什么好说的,就是存放引用的引用队列。
- 在 Reference 被回收的时候,Reference 会被添加到 ReferenceQueue 中。
- ReferenceQueue则是一个单向链表实现的队列数据结构,存储的是Reference对象。
- 包含了入列enqueue、出列poll和移除remove操作(这个大家可以自己去看一下)。
WeakReference
为什么要说WeakReference呢,因为LeakCanay就是借助WeakReference的回收特点,再去对比引用队列中的引用从而去检查内存泄漏。
看一下WeakReference的源码吧:
WeakReference.java
public class WeakReference<T> extends Reference<T> {
/**
* 创建一个指向给定对象的新弱引用。
* 新引用没有注册到任何队列。
*/
public WeakReference(T referent) {
super(referent);
}
/**
* 创建一个指向给定对象的弱引用
* 在给定队列中注册。
*/
public WeakReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
可以看的出来,WeakReference主要是依赖Reference和ReferenceQueue的功能。
LeakCanary源码解析
这里就使用kotlin的版本去帮大家讲解了(没学习过Kotlin的同学,也可以往下看,流程原理很重要)
网上大多都是对2.0之前的版本,现在已经是2.12了。(2.0之后就已经是Kotlin了,所以还是看这个吧)
初始化
先上流程图:防止有些同学看到长篇大论就想跑
首先呢在2.0之前的版本需要通过在在Application的onCreate()方法中调用LeakCanary.install(this),来进行初始化工作。
在2.0之后的版本只需要在build.gradle引入项目就完事了
LeakCanary1的install
这里还是说一下之前的install都干了些什么吧:
- 检查设备和应用程序是否满足LeakCanary的要求。例如,LeakCanary需要在Android 4.0或以上版本的设备上使用,并且需要使用Android Gradle插件版本1.5或以上。
- 在应用程序中安装一个LeakCanary.RefWatcher对象。该对象将监视应用程序中的Activity和Fragment实例,并且在这些实例没有被正确释放时,将会触发通知。
- 启动一个后台线程来检测内存泄漏。这个后台线程会定期检查堆中的对象是否泄漏,并将结果报告给开发人员。
- 在应用程序崩溃时,LeakCanary将收集关于内存泄漏的详细信息,并将这些信息存储起来,以便开发人员进行分析和调试
LeakCanary2的install
那么LeakCanary是怎么省略了这个初始化操作呢?
- 原理就是利用ContentProvider的特性,其onCreate方法会在Application的onCreate方法之前被系统调用。
- 所以只需要在AndroidManifest.xml中配置一下这个ContentProvider,然后在ContentProvider的onCreate方法中进行初始化即可。
- 这里注册了一个继承自ContentProvider的AppWatcherInstaller作为注册使用的ContentProvider
让我们来看一下代码
leakcanary-object-watcher-android/src/main/AndroidManifest.xml
<manifest>
<application>
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:exported="false"/>
</application>
</manifest>
AppWatcherInstaller.kt
这里我们需要知道两个点。
- 在onCreate中的AppWatcher.manualInstall(application)。帮助我们来注册了所以不用手动注册
- 所有的CURD都是空实现,说明这个ContentProvider就是用来初始化的,很纯粹。这也是无入侵的初始化的一种方式(三方库比较常见)。
internal sealed class AppWatcherInstaller : ContentProvider() {
internal class MainProcess : AppWatcherInstaller()
internal class LeakCanaryProcess : AppWatcherInstaller() {
override fun onCreate(): Boolean {
super.onCreate()
AppWatcher.config = AppWatcher.config.copy(enabled = false)
return true
}
}
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
override fun query(
uri: Uri,
strings: Array<String>?,
s: String?,
strings1: Array<String>?,
s1: String?
): Cursor? {
return null
}
override fun getType(uri: Uri): String? {
return null
}
override fun insert(
uri: Uri,
contentValues: ContentValues?
): Uri? {
return null
}
override fun delete(
uri: Uri,
s: String?,
strings: Array<String>?
): Int {
return 0
}
override fun update(
uri: Uri,
contentValues: ContentValues?,
s: String?,
strings: Array<String>?
): Int {
return 0
}
}
继续往下看,AppWatcherInstaller也没啥说的。
AppWatcher.manualInstall()
@JvmOverloads
fun manualInstall(
application: Application,
// 默认保持5s延迟检测
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
// 主线程判断
checkMainThread()
// 防止重复加载
if (isInstalled) {
throw IllegalStateException(
"AppWatcher already installed, see exception cause for prior install call", installCause
)
}
// 延时时间检测,必须大于0才可以,否则抛出异常
check(retainedDelayMillis >= 0) {
"retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
}
installCause = RuntimeException("manualInstall() first called here")
// 缓存延迟检测时间
this.retainedDelayMillis = retainedDelayMillis
// LogcatSharkLog只有在debug状态下才会打印
if (application.isDebuggableBuild) {
LogcatSharkLog.install()
}
// 获取InternalLeakCanary实例加载leakCanary
LeakCanaryDelegate.loadLeakCanary(application)
// 注册五种 Android 泄漏场景的监控 Hook 点
watchersToInstall.forEach {
it.install()
}
}
其中的watchersToInstall循环注册的appDefaultWatchers内容如下,后面会讲解到
appDefaultWatchers
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
//reachabilityWatcher默认值为objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
LeakCanary 的初始化工程可以概括为 2 项内容:
- 1、初始化 LeakCanary 内部分析引擎;
- 2、在 Android Framework 上注册四种 Android 泄漏场景的监控。
InternalLeakCanary 引擎初始化
这个就不细说了。
LeakCanaryDelegate.loadLeakCanary(application)
他是通过反射构建了内部引擎对象,之后主要的内容在invoke方法中。
invoke
override fun invoke(application: Application) {
_application = application
// 1. 检查是否运行在 debug 构建变体,否则抛出异常
checkRunningInDebuggableBuild()
// 2. 注册泄漏回调,在 ObjectWathcer 判定对象发生泄漏会后回调 onObjectRetained() 方法
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
// 3. 垃圾回收触发器(用于调用 Runtime.getRuntime().gc())
val gcTrigger = GcTrigger.Default
// 4. 配置提供器
val configProvider = { LeakCanary.config }
// 5. (主角) 创建 HeapDump 触发器
heapDumpTrigger = HeapDumpTrigger(...)
// 6. App 前后台切换监听
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
// 7. 前台 Activity 监听(用于发送 Heap Dump 进行中的全局 Toast)
registerResumedActivityListener(application)
// 8. 增加可视化分析报告的桌面快捷入口
addDynamicShortcut(application)
}
override fun onObjectRetained() = scheduleRetainedObjectCheck()
fun scheduleRetainedObjectCheck() {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
// App 前后台切换状态变化回调
fun onApplicationVisibilityChanged(applicationVisible: Boolean) {
if (applicationVisible) {
// App 可见
applicationInvisibleAt = -1L
} else {
// App 不可见
applicationInvisibleAt = SystemClock.uptimeMillis()
scheduleRetainedObjectCheck(delayMillis = AppWatcher.retainedDelayMillis)
}
}
fun scheduleRetainedObjectCheck(delayMillis: Long = 0L) {
// 已简化:源码此处使用时间戳拦截,避免重复 postDelayed
backgroundHandler.postDelayed({
checkScheduledAt = 0
checkRetainedObjects()
}, delayMillis)
}
简单来说:就是创建 HeapDumpTrigger 触发器,并在 Android Framework 上注册前后台切换监听、前台 Activity 监听和 ObjectWatcher 的泄漏监听。
主要就是初始化我们dump heap的相关内容。
注入对四种 Android 泄漏场景的监控
上面也提到了分别是
- ActivityWatcher(application, reachabilityWatcher)
- FragmentAndViewModelWatcher(application, reachabilityWatcher)
- RootViewWatcher(reachabilityWatcher)
- ServiceWatcher(reachabilityWatcher)
Activity监听:ActivityWatcher
class ActivityWatcher(
private val application: Application,
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
// 通过objectWatcher监视activity
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
override fun install() {
// 注册应用activity生命周期回调(application里的)
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
override fun uninstall() {
// 反注册activity生命周期回调
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
}
总结:通过install方法注册了activity的生命周期回调,并在onActivityDestroyed()方法中通过ObjectWatcher对activity进行监视
那其他的几个是否和Actvity一样呢? 接着往下看吧别猜了。整的好像你挺能似儿的。
Fragment 监听:FragmentAndViewModelWatcher
这个源码看着比ActivityWatcher多的,我们拆开来看。下面的整合到一起是FragmentAndViewModelWatcher的完整代码
第一段:注册和反注册
override fun install() {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
override fun uninstall() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
在install方法中注册Activity生命周期回调,因为Fragment是依附于Activity来添加,所以此处注册Activity生命周期回调
第二段:lifecycleCallbacks回调
private val lifecycleCallbacks =
//注册Activity生命周期回调,在Activity的onActivityCreated()方法中遍历这些watcher方法类型,
//实际调用的是对应的invoke方法
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
for (watcher in fragmentDestroyWatchers) {
watcher(activity)
}
}
}
第三段:版本判断真正的fragmentDestroyWatchers
//fragmentDestroyWatchers列表,支持不同Fragment实例的检测;
//这里的watcher都继承自(Activity)->Unit表示方法类型/函数类型,
//参数为Activity,返回值为空;因为是方法类型所以需要重写invoke方法
private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
//Android O后构建AndroidOFragmentDestroyWatcher
if (SDK_INT >= O) {
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(reachabilityWatcher)
)
}
//如果Class.for(className)能找到androidx.fragment.app.Fragment和
//leakcanary.internal.AndroidXFragmentDestroyWatcher
//则添加AndroidXFragmentDestroyWatcher则添加
getWatcherIfAvailable(
ANDROIDX_FRAGMENT_CLASS_NAME,
ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
reachabilityWatcher
)?.let {
// 添加观察者
fragmentDestroyWatchers.add(it)
}
//如果Class.for(className)能找到android.support.v4.app.Fragment和
//leakcanary.internal.AndroidSupportFragmentDestroyWatcher
//则添加AndroidSupportFragmentDestroyWatcher
getWatcherIfAvailable(
ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
reachabilityWatcher
)?.let {
// 添加观察者
fragmentDestroyWatchers.add(it)
}
fragmentDestroyWatchers
}
- 如果系统是Android O以后版本,使用AndroidOFragmentDestroyWatcher
- 如果app使用的是androidx中的fragment,则添加对应的AndroidXFragmentDestroyWatcher
- 如果使用support库中的fragment,则添加AndroidSupportFragmentDestroyWatcher。
最终在invoke方法中使用对应的fragmentManager注册Fragment的生命周期回调,在onFragmentViewDestroyed()和onFragmentDestroyed()方法中使用ObjectWatcher来检测fragment。
第四段:通过反射获取当前Activity
private fun getWatcherIfAvailable(
fragmentClassName: String,
watcherClassName: String,
reachabilityWatcher: ReachabilityWatcher
): ((Activity) -> Unit)? {
// 通过类名获取类对象
return if (classAvailable(fragmentClassName) &&
classAvailable(watcherClassName)
) {
// 获取构造方法Class.forName(watcherClassName).getDeclaredConstructor(ReachabilityWatcher::class.java)
// 通过构造方法实例化activity
val watcherConstructor =
Class.forName(watcherClassName).getDeclaredConstructor(ReachabilityWatcher::class.java)
@Suppress("UNCHECKED_CAST")
watcherConstructor.newInstance(reachabilityWatcher) as (Activity) -> Unit
} else {
null
}
}
这里获取了当前的Activity。在第三步中先判断Fragment版本。之后他当前的Activity加入到了生命周期的监听中。
这不就和之前的Activity的流程一样了吗。嘻嘻
最后都是回调相关的Fragment的Invoke上面代码的注释也有写。那看看其中的一个invoke吧。
AndroidOFragmentDestroyWatcher.invoke()
override fun invoke(activity: Activity) {
//取得对应的FragmentManager
val fragmentManager = activity.fragmentManager
//注册生命周期回调
fragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
}
其实其他版本的Fragment的invoke都是做了同样的事情,去注册生命周期的回调。
RootView 监控:RootViewWatcher
由于 Android Framework 未提供设置全局监听 RootView 从 WindowManager 中移除的方法,所以 LeakCanary 是通过 Hook 的方式实现的。
主要是利用以下两个hook步骤
- Hook WMS 服务内部的 WindowManagerGlobal.mViews RootView 列表,获取 RootView 新增和移除的时机;
- 检查 View 对应的 Window 类型,如果是 Dialog 或 DreamService 等类型,则在注册 View#addOnAttachStateChangeListener() 监听,在其中的 onViewDetachedFromWindow() 回调中将 View 对象交给 ObjectWatcher 监控。
这里我们也是分步的看一下具体的源码。
RootViewWatcher.kt 中install() uninstall和方法
override fun install() {
//注册 RootView 监听
Curtains.onRootViewsChangedListeners += listener
}
override fun uninstall() {
Curtains.onRootViewsChangedListeners -= listener
}
我们看到install方法将listener添加到了Curtains的OnRootViewsChangedListener列表中,而Curtains中onRootViewsChangedListeners记录了root views的数量并在数量发生变更后调用onRootViewsChanged方法
RootViewWatcher.kt 中listener
private val listener = OnRootViewAddedListener { rootView ->
// 根据windowType 类型不同判断trackDetached状态
val trackDetached = when(rootView.windowType) {
PHONE_WINDOW -> {
when (rootView.phoneWindow?.callback?.wrappedCallback) {
// Activities are already tracked by ActivityWatcher
is Activity -> false
is Dialog -> rootView.resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
// Probably a DreamService
else -> true
}
}
// Android widgets keep detached popup window instances around.
POPUP_WINDOW -> false
TOOLTIP, TOAST, UNKNOWN -> true
}
// trackeDetached状态为true,添加rootView的状态变更监听
if (trackDetached) {
rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
// 开启子线程检查rootView引用状态检测是否存在内存泄漏
val watchDetachedView = Runnable {
// 这里也是通过objectWatcher进行监视,前面2.1已经分析,同上
reachabilityWatcher.expectWeaklyReachable(
rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
)
}
// 根据生命周期回调通过mainHandler处理view事件
override fun onViewAttachedToWindow(v: View) {
mainHandler.removeCallbacks(watchDetachedView)
}
override fun onViewDetachedFromWindow(v: View) {
mainHandler.post(watchDetachedView)
}
})
}
}
- 根据rootView的windowType判断是否需要进行track跟踪
- 针对需要跟踪的rootView类型开启子线程检查其引用状态,判断是否存在内存泄漏
- 针对需要跟踪的rootView生命周期回调,通过mainHandler进行消息传递
Service监听:ServiceWatcher
由于 Android Framework 未提供设置 Service#onDestroy() 全局监听的方法,所以 LeakCanary 是通过 Hook 的方式实现的。
也是需要通过两步Hook去实现:
- 1、Hook 主线程消息循环的 mH.mCallback 回调,监听其中的 STOP_SERVICE 消息,将即将 Destroy 的 Service 对象暂存起来(由于 ActivityThread.H 中没有 DESTROY_SERVICE 消息,所以不能直接监听到 onDestroy() 事件,需要第 2 步)
- 2、使用动态代理 Hook AMS 与 App 通信的的 IActivityManager Binder 对象,代理其中的 serviceDoneExecuting() 方法,视为 Service#onDestroy() 的执行时机,拿到暂存的 Service 对象交给 ObjectWatcher 监控。
分布看一下源码吧:
ServiceWatcher.kt 中install()和uninstall()方法
override fun install() {
// 1、Hook mH.mCallback
swapActivityThreadHandlerCallback { mCallback /*原对象*/ ->
// uninstallActivityThreadHandlerCallback:用于取消 Hook
uninstallActivityThreadHandlerCallback = {
swapActivityThreadHandlerCallback {
mCallback
}
}
// 新对象(lambda 表达式的末行就是返回值)
Handler.Callback { msg ->
// Service#onStop() 事件
if (msg.what == STOP_SERVICE) {
val key = msg.obj as IBinder
// activityThreadServices:反射获取 ActivityThread mServices 映射表 <IBinder, CreateServiceData>
activityThreadServices[key]?.let {
// 暂存即将 Destroy 的 Service
servicesToBeDestroyed[token] = WeakReference(service)
}
}
// 继续执行 Framework 原有逻辑
mCallback?.handleMessage(msg) ?: false
}
}
// 2、Hook AMS IActivityManager
swapActivityManager { activityManagerInterface, activityManagerInstance /*原对象*/ ->
// uninstallActivityManager:用于取消 Hook
uninstallActivityManager = {
swapActivityManager { _, _ ->
activityManagerInstance
}
}
// 新对象(lambda 表达式的末行就是返回值)
Proxy.newProxyInstance(activityManagerInterface.classLoader, arrayOf(activityManagerInterface)) { _, method, args ->
// 代理 serviceDoneExecuting() 方法
if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
// 取出暂存的即将 Destroy 的 Service
val token = args!![0] as IBinder
if (servicesToBeDestroyed.containsKey(token)) {
servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
// 交给 ObjectWatcher 监控
serviceWeakReference.get()?.let { service ->
reachabilityWatcher.expectWeaklyReachable(service /*被监控对象*/, "${service::class.java.name} received Service#onDestroy() callback")
}
}
}
}
// 继续执行 Framework 原有逻辑
method.invoke(activityManagerInstance, *args)
}
}
}
总结:主要是讲当前服务通过Hook去进行监听
ServiceWatcher.kt 中onServicePreDestroy()
private fun onServicePreDestroy(
token: IBinder,
service: Service
) {
//将服务变更成弱引用,便于GC的回收操作
servicesToBeDestroyed[token] = WeakReference(service)
}
ServiceWatcher.kt 中onServiceDestroyed()进行委托给ObjectWatcher 进行监控
private fun onServiceDestroyed(token: IBinder) {
// 将service从servicesToBeDestroyed集合中移除
servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
serviceWeakReference.get()?.let { service ->
// 通过reachabilityWatcher监视内存泄漏的对象
reachabilityWatcher.expectWeaklyReachable(
service, "${service::class.java.name} received Service#onDestroy() callback"
)
}
}
}
重点:内存检测如何检测的
先上图吧兄弟们。知道兄弟们爱看图。(本篇文章只说前两个阶段)
上面的初始化和四种Android的监听已经说完了。为什么要说这么多的提前工作呢?
因为为了更加容易的理解下面的检测的过程。
上面说了,当对象的生命周期结束后,都会交给ObjectWatcher 监控(下面代码可以清晰看到)
四种监控委托给ObjectWatcher 的证据:
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
// activity的监听
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
//Fragment的监听
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
for (watcher in fragmentDestroyWatchers) {
watcher(activity)
}
}
}
val watcherConstructor =
Class.forName(watcherClassName).getDeclaredConstructor(ReachabilityWatcher::class.java)
@Suppress("UNCHECKED_CAST")
watcherConstructor.newInstance(reachabilityWatcher) as (Activity) -> Unit
//RootView的监听
val watchDetachedView = Runnable {
reachabilityWatcher.expectWeaklyReachable(
rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
)
}
//Service的监听
private fun onServiceDestroyed(token: IBinder) {
servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
serviceWeakReference.get()?.let { service ->
reachabilityWatcher.expectWeaklyReachable(
service, "${service::class.java.name} received Service#onDestroy() callback"
)
}
}
}
那么委托给ObjectWatcher ,并且调用了expectWeaklyReachable方法。他能干什么呢? 或者说他干了什么呢?
预热一下:他主要是完成了以下三步。
- 第 1 步: 为被监控对象 watchedObject 创建一个 KeyedWeakReference 弱引用,并存储到 <UUID, KeyedWeakReference> 的映射表中;
- 第 2 步: postDelay 五秒后检查引用对象是否出现在引用队列中,出现在队列则说明被监控对象未发生泄漏。随后,移除映射表中未泄露的记录,更新泄漏的引用对象的 retainedUptimeMillis 字段以标记为泄漏;
- 第 3 步: 通过回调 onObjectRetained 告知 LeakCanary 内部发生新的内存泄漏。
那就上源码吧兄弟们!
objectWatcher的创建
从AppWatcher.kt的appDefaultWatchers可以看到形参objectWatcher。找到他实例化的位置
/**
* AppWatcher用来检测保留对象的[ObjectWatcher]
* 仅当[isInstalled]为true时设置
*/
val objectWatcher = ObjectWatcher(
//1、lambda 表达式获取当前系统时间
clock = { SystemClock.uptimeMillis() },
// 2、lambda 表达式实现 Executor SAM 接口
checkRetainedExecutor = {
check(isInstalled) {
"AppWatcher not installed"
}
mainHandler.postDelayed(it, retainedDelayMillis)
},
// 3、lambda 表达式获取监控开关
isEnabled = { true }
)
可以看到我们的入参包括:
1、clock:当前的系统时间
2、checkRetainedExecutor:延时的Handler(他和系统时间配合,来根据clock时间来判断时候被回收)
3、isEnabled:监控开关
ObjectWatcher
之后让我们来看看ObjectWatcher内部做了什么,关键的核心都在这里了。
我们也来拆解开看吧
第一步:初始化成员变量
// Object的监听集合
private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()
// 被监控的对象映射表 <UUID,KeyedWeakReference>
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
// KeyedWeakReference 关联的引用队列,用于判断对象是否泄漏
private val queue = ReferenceQueue<Any>()
来看一下具体的KeyedWeakReference对象
class KeyedWeakReference(
// 被监控对象
referent: Any,
// 唯一 Key,根据此字段匹配映射表中的记录
val key: String,
// 描述信息
val description: String,
// 监控开始时间,即引用对象创建时间
val watchUptimeMillis: Long,
// 关联的引用队列
referenceQueue: ReferenceQueue<Any>
) : WeakReference<Any>(referent, referenceQueue) {
// 记录实际对象 referent 被判定为泄漏对象的时间
// -1L 表示非泄漏对象,或者还未判定完成
@Volatile
var retainedUptimeMillis = -1L
override fun clear() {
super.clear()
retainedUptimeMillis = -1L
}
companion object {
// 记录最近一次触发 Heap Dump 的时间
@Volatile
@JvmStatic var heapDumpUptimeMillis = 0L
}
}
第二步:前面讲到的四种Android的监听都调用了expectWeaklyReachable来帮我们处理生命周期改变后的,内存泄漏的处理。让我们来具体看看。
// 一、处理四种Android的监控
@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
// 移除 watchedObjects 中未泄漏的引用对象(相关方法都在下面介绍了)
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
// 新建一个 KeyedWeakReference 引用对象
val reference = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
watchedObjects[key] = reference
// 五秒后检查引用对象是否出现在引用队列中,否则判定发生泄漏
// checkRetainedExecutor 相当于 postDelay 五秒后执行 moveToRetained() 方法
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
//二、移除 watchedObjects 中未泄漏的引用对象
private fun removeWeaklyReachableObjects() {
//
var ref: KeyedWeakReference?
do {
// 循环引用队列(在 Reference 被回收的时候,Reference 会被添加到 ReferenceQueue 中)
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
// KeyedWeakReference 出现在引用队列中,说明未发生泄漏(因为被回收时的对象才添加到ReferenceQueue中)
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
}
//三、 五秒后检查引用对象是否出现在引用队列中,否则说明发生泄漏
@Synchronized private fun moveToRetained(key: String) {
// 移除 watchedObjects 中未泄漏的引用对象
removeWeaklyReachableObjects()
// 5秒后再次获取当前的引用对象KeyedWeakReference
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
// 说明还在,也就是没被回收掉
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
// 回调通知 LeakCanary 内部处理进行dump
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
第三步:那么已经泄漏的KeyedWeakReference送去LeakCanary 内部处理进行dump处理之后的对象。我们需要将他从队列中去除。不然不就重复的检查了吗。来看看是怎么操作的。
//Heap Dump 后移除所有监控时间早于 heapDumpUptimeMillis 的引用对象
@Synchronized fun clearObjectsWatchedBefore(heapDumpUptimeMillis: Long) {
val weakRefsToRemove = watchedObjects.filter { it.value.watchUptimeMillis <= heapDumpUptimeMillis }
weakRefsToRemove.values.forEach { it.clear() }
watchedObjects.keys.removeAll(weakRefsToRemove.keys)
}
第四步:这个是暴露出去进行统计的。就是泄漏队列的计数和是否有泄漏对象。提供LeakCanary内部处理使用。
// 获取是否有内存泄漏对象
val hasRetainedObjects: Boolean
@Synchronized get() {
// 移除 watchedObjects 中未泄漏的引用对象
removeWeaklyReachableObjects()
return watchedObjects.any { it.value.retainedUptimeMillis != -1L }
}
// 获取内存泄漏对象计数
val retainedObjectCount: Int
@Synchronized get() {
// 移除 watchedObjects 中未泄漏的引用对象
removeWeaklyReachableObjects()
return watchedObjects.count { it.value.retainedUptimeMillis != -1L }
}
上面的全部内容拼接起来就是我们的核心。ObjectWatcher的全部内容了。
大家都有疑问吧! 上面代码中讲到的onObjectRetainedListeners.forEach { it.onObjectRetained() }。送给LeakCanary进行内部处理。到底是怎么个内部处理,你也没说啊。别着急,下面这不就来了吗!!!!
LeakCanary的分析泄漏
上面说到了onObjectRetainedListeners.forEach { it.onObjectRetained() }。实际的实现内容在我们初始化的引擎中。InternalLeakCanary。
上面也说到了,他的作用主要就是分析和dump我们的泄漏内容。
LeakCanary 不会立即对每次发现内存泄漏对象都进行分析工作,而会进行两个拦截:
- 拦截 1:泄漏对象计数未达到阈值,或者进入后台时间未达到阈值;
- 拦截 2:计算距离上一次 HeapDump 未超过 60s。
来看看源码吧
InternalLeakCanary.kt
// 从 ObjectWatcher 回调过来 声明的scheduleRetainedObjectCheck方法
override fun onObjectRetained() = scheduleRetainedObjectCheck()
private lateinit var heapDumpTrigger: HeapDumpTrigger
fun scheduleRetainedObjectCheck() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
HeapDumpTrigger.kt
fun scheduleRetainedObjectCheck(delayMillis: Long = 0L) {
val checkCurrentlyScheduledAt = checkScheduledAt
if (checkCurrentlyScheduledAt > 0) {
return
}
// 通过时间戳拦截一下,避免重复的postDelayed
checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
backgroundHandler.postDelayed({
checkScheduledAt = 0
checkRetainedObjects()
}, delayMillis)
}
private fun checkRetainedObjects() {
val config = configProvider()
// 泄漏对象计数
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
// 主动触发 GC,并等待 100 ms
gcTrigger.runGc()
// 重新获取泄漏对象计数
retainedReferenceCount = objectWatcher.retainedObjectCount
}
// 拦截 1:泄漏对象计数未达到阈值,或者进入后台时间未达到阈值(config中的val retainedVisibleThreshold: Int = 5)
if (retainedKeysCount < retainedVisibleThreshold) {
// App 位于前台或者刚刚进入后台
if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
// 发送通知提醒
showRetainedCountNotification("App visible, waiting until %d retained objects")
// 延迟 2 秒再检查
scheduleRetainedObjectCheck(WAIT_FOR_OBJECT_THRESHOLD_MILLIS)
return;
}
}
// 拦截 2:计算距离上一次 HeapDump 未超过 60s
val now = SystemClock.uptimeMillis()
val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
// 发送通知提醒
showRetainedCountNotification("Last heap dump was less than a minute ago")
// 延迟 (60 - elapsedSinceLastDumpMillis)s 再检查
scheduleRetainedObjectCheck(WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis)
return
}
// 移除通知提醒
dismissRetainedCountNotification()
// 触发 HeapDump(此时,应用有可能在后台)
dumpHeap(...)
}
// 真正开始执行 Heap Dump
private fun dumpHeap(...) {
// 1. 获取文件存储提供器
val directoryProvider = InternalLeakCanary.createLeakDirectoryProvider(InternalLeakCanary.application)
// 2. 创建 .hprof File 文件
val heapDumpFile = directoryProvider.newHeapDumpFile()
// 3. 执行 Heap Dump
// Heap Dump 开始时间戳
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
// heapDumper.dumpHeap:最终调用 Debug.dumpHprofData(heapDumpFile.absolutePath)
configProvider().heapDumper.dumpHeap(heapDumpFile)
// 4. 清除 ObjectWatcher 中过期的监控
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
// 5. 分析堆快照
InternalLeakCanary.sendEvent(HeapDump(currentEventUniqueId!!, heapDumpFile, durationMillis, reason))
}
GcTrigger.kt
interface GcTrigger {
fun runGc()
object Default : GcTrigger {
override fun runGc() {
// Runtime.gc() 相比于 System.gc() 更有可能触发 GC
Runtime.getRuntime()
.gc()
enqueueReferences()
System.runFinalization()
}
private fun enqueueReferences() {
try {
// 暂停100ms等待 GC
Thread.sleep(100)
} catch (e: InterruptedException) {
throw AssertionError()
}
}
}
}
总结:OK了这就是全部的内存检测流程了
LeakCanary的常见问题
IdleHandle在LeakCanay的应用
其实很多人有了误解,IdleHandle只在LeakCanay1的版本在使用。在LeakCanay2就已经去除了。
去除的原因:
- 为了提高检测内存泄漏的准确性和性能。
- 之前版本的LeakCanary使用的是IdleHandler来检测内存泄漏,但是这种方式可能会导致一些不准确的结果。
- 比如在UI线程中执行一个异步任务时,IdleHandler可能无法正确地检测到内存泄漏。
那就别白说了,来看看LeakCanary1中IdleHandle的使用吧。
为啥使用:为了不影响主线程其他事件呗。 大家都知道IdleHandle是在主现场空闲的时候,来处理事件的。具体的我在《一篇文章搞定〈Handler机制〉》中有详细说哦!!!!!
我们来看看 LeakCanary 的使用,是在AndroidWatchExecutor 这个类中
public final class AndroidWatchExecutor implements WatchExecutor {
static final String LEAK_CANARY_THREAD_NAME = "LeakCanary-Heap-Dump";
private final Handler mainHandler;
private final Handler backgroundHandler;
private final long initialDelayMillis;
private final long maxBackoffFactor;
public AndroidWatchExecutor(long initialDelayMillis) {
mainHandler = new Handler(Looper.getMainLooper());
HandlerThread handlerThread = new HandlerThread(LEAK_CANARY_THREAD_NAME);
handlerThread.start();
backgroundHandler = new Handler(handlerThread.getLooper());
this.initialDelayMillis = initialDelayMillis;
maxBackoffFactor = Long.MAX_VALUE / initialDelayMillis;
}
//初始调用
@Override public void execute(Retryable retryable) {
// 最终都会切换到主线程,调用waitForIdle()方法
if (Looper.getMainLooper().getThread() == Thread.currentThread()) {
waitForIdle(retryable, 0);
} else {
postWaitForIdle(retryable, 0);
}
}
private void postWaitForIdle(final Retryable retryable, final int failedAttempts) {
mainHandler.post(new Runnable() {
@Override public void run() {
waitForIdle(retryable, failedAttempts);
}
});
}
//IdleHandler 使用
void waitForIdle(final Retryable retryable, final int failedAttempts) {
// This needs to be called from the main thread.
Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override public boolean queueIdle() {
postToBackgroundWithDelay(retryable, failedAttempts);
return false;
}
});
}
//最终的调用方法
private void postToBackgroundWithDelay(final Retryable retryable, final int failedAttempts) {
long exponentialBackoffFactor = (long) Math.min(Math.pow(2, failedAttempts), maxBackoffFactor);
long delayMillis = initialDelayMillis * exponentialBackoffFactor;
backgroundHandler.postDelayed(new Runnable() {
@Override public void run() {
Retryable.Result result = retryable.run();
if (result == RETRY) {
postWaitForIdle(retryable, failedAttempts + 1);
}
}
}, delayMillis);
}
}
再来看看 execute() 这个方法在何处被调用,我们知道 LeakCancary 是在界面销毁 onDestroy 方法中进行 refWatch.watch() 的,而watch() -> ensureGoneAsync() -> execute()
private void ensureGoneAsync(final long watchStartNanoTime, final KeyedWeakReference reference) {
watchExecutor.execute(new Retryable() {
@Override public Retryable.Result run() {
return ensureGone(reference, watchStartNanoTime);
}
});
}
而 ensureGone() 中会进行 GC 回收和一些分析等操作,所以通过这些分析后,我们可以知道 LeakCanary 进行内存泄漏检测并不是 onDestry 方法执行完成后就进行垃圾回收和一些分析的,而是利用 IdleHandler 在空闲的时候进行这些操作的,尽量不去影响主线程的操作。
LeakCanary的泄漏是通过两次GC定位的
这个其实细心阅读的同学在上面是发现的了。
答案也在上面,就不细说了。
第一次:组件的生命周期结束,ObjectWatcher为当前监听对象,创建的KeyedWeakReference弱引用加入到弱引用队列中,并发出一个5秒的延迟检测消息。(也就是LeakCanary的第一次检测)
第二次:dump文件生成之前,的一次主动GC。(可以认为给了他们最后一次机会,算是确认的过程吧)
// 泄漏对象计数
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
// 主动触发 GC,并等待 100 ms
gcTrigger.runGc()
// 重新获取泄漏对象计数
retainedReferenceCount = objectWatcher.retainedObjectCount
}
总结
源码能帮我们收获很多知识。但是前提是你有一定的基础了。
不然看起来总是不理解这里的内容是做什么的。
比如本篇文章,需要你对GC的回收、四大引用、引用队列都有一定的了解才行。
加油吧!兄弟们!