开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第21天,点击查看活动详情
LeakCanary内部用到了Refercence及ReferenceQueue来实现对对象是否被回收的监听。这是LeakCanary的核心逻辑,因此在讲解LeakCanary之前,我们先来简单了解一下Refercence及ReferenceQueue。
1、Refercence及ReferenceQueue
1.1 基本概念
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有四种状态:Active、Pending、Enqueued、Inactive。声明的时候默认Active状态。
ReferenceQueue则是一个单向链表实现的队列数据结构,存储的是Reference对象。包含了入列enqueue、出列poll和移除remove操作。
1.2 对象回收监听
Reference配合ReferenceQueue就可以实现对象回收监听了,先通过一个示例来看看是怎么实现的。
//创建一个引用队列
ReferenceQueue queue = new ReferenceQueue();
//创建弱引用,并关联引用队列queue
WeakReference reference = new WeakReference(new Object(),queue);
System.out.println(reference);
System.gc();
//当reference被成功回收后,可以从queue中获取到该引用
System.out.println(queue.remove());
示例中的对象当然是可以正常回收的,所以回收后可以在关联的引用队列queue中获取到该引用。反之,若某个应该被回收的对象,GC结束后在queue中未找到该引用,则表明该引用存在内存泄漏风险,这也就是LeakCanary的基本原理了。
2、LeakCanary基本原理
为了更好的对LeakCanary源码进行分部解析,我们先对LeakCanary实现内存泄漏的整体过程做一个概括。后面在分部对整个流程的源码进行解析。
- 初始化。
- 添加相关监听对象销毁监听。LeakCanary会默认监听Activity、Fragment、Fragment的View、ViewModel是否回收。
- 收到销毁回调后,根据要回收对象创建KeyedWeakReference和ReferenceQueue,并关联。
- 延迟5秒检查相关对象是否被回收。
- 如果没有被回收就通过dump heap获取hprof文件。
- 通过Shark库解析hprof文件,获取泄漏对象,被计算泄漏对象到GC roots的最短路径。
- 合并多个泄漏路径并输出分析结果。
- 将结果展示到可视化界面。
3、LeakCanary源码解析
在2.0之后的版本只需要在build.gradle引入项目就完事了
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
3.1、入口
2.0之前的版本接入过程除了在build.gradle中引入项目外,还需要调用LeakCanary.install(this),来进行初始化工作。一般都是在Application的onCreate()方法中调用。
在2.0之后的版本只需要在build.gradle引入项目就完事了。那么问题来了:2.0之后的版本初始化工作是在哪里完成的呢?
找了许久,终于在项目工程:leakcanary-object-watcher-android
的manifest文件中发现了秘密:
<application>
<provider
android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false"/>
</application>
这里注册了一个继承自ContentProvider的MainProcessAppWatcherInstaller
。我们知道在app启动时,会先调用注册的ContentProvider的onCreate完成初始化,在MainProcessAppWatcherInstaller
onCreate
中果然找到了熟悉的install方法:
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
调用链:AppWatcher.manualInstall–>InternalAppWatcher.install。具体的初始化逻辑是在InternalAppWatcher,来看源码:
@JvmOverloads
fun manualInstall(
application: Application,
retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
//确保在主线程,否则抛出UnsupportedOperationException异常
checkMainThread()
if (isInstalled) {
throw IllegalStateException(
"AppWatcher already installed, see exception cause for prior install call", installCause
)
}
check(retainedDelayMillis >= 0) {
"retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
}
this.retainedDelayMillis = retainedDelayMillis
if (application.isDebuggableBuild) {
LogcatSharkLog.install()
}
// Requires AppWatcher.objectWatcher to be set
// 初始化 InternalLeakCanary 内部引擎
LeakCanaryDelegate.loadLeakCanary(application)
// 注册五种 Android 泄漏场景的监控 Hook 点
watchersToInstall.forEach {
it.install()
}
// Only install after we're fully done with init.
installCause = RuntimeException("manualInstall() first called here")
}
ContentProvider的核心方法CURD在AppWatcherInstaller都是空实现,只用到了onCreate。需要注意的是ContentProvider.onCreate调用时机介于Application的attachBaseContext和onCreate之间,所以不能依赖之后初始化的其他SDK。
ContentProvider 的常规用法是提供内容服务,而另一个特殊的用法是提供无侵入的初始化机制,这在第三方库中很常见,Jetpack 中提供的轻量级初始化框架 App Startup 也是基于 ContentProvider 的方案。
在初始过程中,对应四种场景的内存泄露监听
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
LeakCanary 的初始化工程可以概括为 2 项内容:
- 1、初始化 LeakCanary 内部分析引擎;
- 2、在 Android Framework 上注册四种 Android 泄漏场景的监控。
3.2、InternalLeakCanary 引擎初始化
进入源码
LeakCanaryDelegate.loadLeakCanary(application)
通过反射构建内部引擎对象
internal object LeakCanaryDelegate {
@Suppress("UNCHECKED_CAST")
val loadLeakCanary by lazy {
try {
val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
leakCanaryListener.getDeclaredField("INSTANCE")
.get(null) as (Application) -> Unit
} catch (ignored: Throwable) {
NoLeakCanary
}
}
根据kotlin特性会执行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 的泄漏监听。
3.2、注入对四种 Android 泄漏场景的监控
实现在对象的使用生命周期结束后,自动将对象交给 ObjectWatcher
进行监控。
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
Activity 回收监听
在ActivityWatcher
类中 通过 Application#registerActivityLifecycleCallbacks(…)
接口监听 Activity#onDestroy 事件,将当前 Activity 对象交给 ObjectWatcher 监控;
companion object {
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> Config
) {
//实例化ActivityDestroyWatcher
val activityDestroyWatcher =
ActivityDestroyWatcher(objectWatcher, configProvider)
//注册ActivityLifecycle监听
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
}
registerActivityLifecycleCallbacks是Android Application的一个方法,注册了该方法,可以通过回调获取app中每一个Activity的生命周期变化。再来看看ActivityDestroyWatcher对生命周期回调的处理:
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
if (configProvider().watchActivities) {
//通过objectWatcher监听改activity是否被销毁回收
objectWatcher.watch(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
}
ActivityLifecycleCallbacks生命周期回调有那么多,为什么只用重写其中一个?关键在于by noOpDelegate(),通过类委托机制将其他回调实现都交给noOpDelegate,而noOpDelegate是一个空实现的动态代理。新姿势get+1,在遇到只需要实现接口的部分方法时,就可以这么玩了,其他方法实现都委托给空实现代理类就好了。
Fragment 回收监听
在FragmentAndViewModelWatcher
中 实现,首先是通过 Application#registerActivityLifecycleCallbacks(…)
接口监听 Activity#onCreate 事件,再通过 FragmentManager#registerFragmentLifecycleCallbacks(…)
接口监听 Fragment 的生命周期:
来看一下FragmentDestroyWatcher.install的实现:
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> AppWatcher.Config
) {
//fragmentDestroyWatchers列表,支持不同Fragment实例的检测;
//这里的watcher都继承自(Activity)->Unit表示方法类型/函数类型,
//参数为Activity,返回值为空;因为是方法类型所以需要重写invoke方法
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
//Android O后构建AndroidOFragmentDestroyWatcher
if (SDK_INT >= O) {
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(objectWatcher, configProvider)
)
}
//如果Class.for(className)能找到androidx.fragment.app.Fragment和
//leakcanary.internal.AndroidXFragmentDestroyWatcher则添加AndroidXFragmentDestroyWatcher则添加
getWatcherIfAvailable(
ANDROIDX_FRAGMENT_CLASS_NAME,
ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
objectWatcher,
configProvider
)?.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,
objectWatcher,
configProvider
)?.let {
fragmentDestroyWatchers.add(it)
}
if (fragmentDestroyWatchers.size == 0) {
return
}
//注册Activity生命周期回调,在Activity的onActivityCreated()方法中遍历这些watcher方法类型,实际调用的是对应的invoke方法
application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
for (watcher in fragmentDestroyWatchers) {
//实际调用的是对应的invoke方法
watcher(activity)
}
}
})
}
如果系统是Android O以后版本,使用AndroidOFragmentDestroyWatcher,如果app使用的是androidx中的fragment,则添加对应的AndroidXFragmentDestroyWatcher,如果使用support库中的fragment,则添加AndroidSupportFragmentDestroyWatcher。最终在invoke方法中使用对应的fragmentManager注册Fragment的生命周期回调,在onFragmentViewDestroyed()和onFragmentDestroyed()方法中使用ObjectWatcher来检测fragment。下面以AndroidXFragmentDestroyWatcher为例:
先看一下AndroidXFragmentDestroyWatcher的invoke方法实现:
override fun invoke(activity: Activity) {
if (activity is FragmentActivity) {
//取得对应的FragmentManager,注册生命周期回调
val supportFragmentManager = activity.supportFragmentManager
supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
//添加了ViewModelStoreOwner为Activity的ViewModelClearedWatcher监测
ViewModelClearedWatcher.install(activity, objectWatcher, configProvider)
}
}
LeakCanary在onFragmentDestroyed回调里面来处理检查Fragment是否正常被回收的检测逻辑。
override fun onFragmentDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
if (configProvider().watchFragments) {
objectWatcher.watch(
fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
)
}
}
Fragment的View 回收监听
LeakCanary在onFragmentViewDestroyed回调里面来处理检查Fragment的View是否正常被回收的检测逻辑。
override fun onFragmentViewDestroyed(
fm: FragmentManager,
fragment: Fragment
) {
val view = fragment.view
if (view != null && configProvider().watchFragmentViews) {
objectWatcher.watch(
view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
"(references to its views should be cleared to prevent leaks)"
)
}
}
RootView 监控
由于 Android Framework 未提供设置全局监听 RootView 从 WindowManager 中移除的方法,所以 LeakCanary 是通过 Hook 的方式实现的,这一块是通过 squareup 另一个开源库 curtains
实现的。
RootView 监控这部分源码也比较复杂了,需要通过 2 步 Hook 来实现:
- 1、Hook WMS 服务内部的
WindowManagerGlobal.mViews
RootView 列表,获取 RootView 新增和移除的时机; - 2、检查 View 对应的 Window 类型,如果是 Dialog 或 DreamService 等类型,则在注册
View#addOnAttachStateChangeListener()
监听,在其中的 onViewDetachedFromWindow() 回调中将 View 对象交给 ObjectWatcher 监控。
LeakCanary 源码摘要如下:
RootViewWatcher.kt
override fun install() {
// 1. 注册 RootView 监听
Curtains.onRootViewsChangedListeners += listener
}
private val listener = OnRootViewAddedListener { rootView ->
val trackDetached = when(rootView.windowType) {
PHONE_WINDOW -> {
when (rootView.phoneWindow?.callback?.wrappedCallback) {
// Activity 类型已经在 ActivityWatcher 中监控了,不需要重复监控
is Activity -> false
is Dialog -> {
// leak_canary_watcher_watch_dismissed_dialogs:Dialog 监控开关
val resources = rootView.context.applicationContext.resources
resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
}
// DreamService 屏保等
else -> true
}
}
POPUP_WINDOW -> false
TOOLTIP, TOAST, UNKNOWN -> true
}
if (trackDetached) {
// 2. 注册 View#addOnAttachStateChangeListener 监听
rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
val watchDetachedView = Runnable {
// 3. 交给 ObjectWatcher 监控
reachabilityWatcher.expectWeaklyReachable(rootView /*被监控对象*/ , "${rootView::class.java.name} received View#onDetachedFromWindow() callback")
}
override fun onViewAttachedToWindow(v: View) {
mainHandler.removeCallbacks(watchDetachedView)
}
override fun onViewDetachedFromWindow(v: View) {
mainHandler.post(watchDetachedView)
}
})
}
}
curtains 源码摘要如下:
RootViewsSpy.kt
private val delegatingViewList = object : ArrayList<View>() {
// 重写 ArrayList#add 方法
override fun add(element: View): Boolean {
// 回调
listeners.forEach { it.onRootViewsChanged(element, true) }
return super.add(element)
}
// 重写 ArrayList#removeAt 方法
override fun removeAt(index: Int): View {
// 回调
val removedView = super.removeAt(index)
listeners.forEach { it.onRootViewsChanged(removedView, false) }
return removedView
}
}
companion object {
fun install(): RootViewsSpy {
return RootViewsSpy().apply {
WindowManagerSpy.swapWindowManagerGlobalMViews { mViews /*原对象*/ ->
// 新对象(lambda 表达式的末行就是返回值)
delegatingViewList.apply { addAll(mViews) }
}
}
}
}
WindowManageSpy.kt
// Hook WMS 服务内部的 WindowManagerGlobal.mViews RootView 列表
// swap 是一个 lambda 表达式,参数为原对象,返回值为注入的新对象
fun swapWindowManagerGlobalMViews(swap: (ArrayList<View>) -> ArrayList<View>) {
windowManagerInstance?.let { windowManagerInstance ->
mViewsField?.let { mViewsField ->
val mViews = mViewsField[windowManagerInstance] as ArrayList<View>
mViewsField[windowManagerInstance] = swap(mViews)
}
}
}
Service回收监听
由于 Android Framework 未提供设置 Service#onDestroy() 全局监听的方法,所以 LeakCanary 是通过 Hook 的方式实现的。
Service 监控这部分源码比较复杂了,需要通过 2 步 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
源码摘要如下:
private var uninstallActivityThreadHandlerCallback: (() -> Unit)? = null
// 暂存即将 Destroy 的 Service
private val servicesToBeDestroyed = WeakHashMap<IBinder, WeakReference<Service>>()
override fun install() {
// 1. Hook mH.mCallback
swapActivityThreadHandlerCallback { mCallback /*原对象*/ ->
// uninstallActivityThreadHandlerCallback:用于取消 Hook
uninstallActivityThreadHandlerCallback = {
swapActivityThreadHandlerCallback {
mCallback
}
}
// 新对象(lambda 表达式的末行就是返回值)
Handler.Callback { msg ->
// 1.1 Service#onStop() 事件
if (msg.what == STOP_SERVICE) {
val key = msg.obj as IBinder
// 1.2 activityThreadServices:反射获取 ActivityThread mServices 映射表 <IBinder, CreateServiceData>
activityThreadServices[key]?.let {
// 1.3 暂存即将 Destroy 的 Service
servicesToBeDestroyed[token] = WeakReference(service)
}
}
// 1.4 继续执行 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 ->
// 2.1 代理 serviceDoneExecuting() 方法
if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
// 2.2 取出暂存的即将 Destroy 的 Service
val token = args!![0] as IBinder
if (servicesToBeDestroyed.containsKey(token)) {
servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
// 2.3 交给 ObjectWatcher 监控
serviceWeakReference.get()?.let { service ->
reachabilityWatcher.expectWeaklyReachable(service /*被监控对象*/, "${service::class.java.name} received Service#onDestroy() callback")
}
}
}
}
// 2.4 继续执行 Framework 原有逻辑
method.invoke(activityManagerInstance, *args)
}
}
}
override fun uninstall() {
// 关闭 mH.mCallback 的 Hook
uninstallActivityManager?.invoke()
uninstallActivityThreadHandlerCallback?.invoke()
uninstallActivityManager = null
uninstallActivityThreadHandlerCallback = null
}
// 使用反射修改 ActivityThread 的主线程消息循环的 mH.mCallback
// swap 是一个 lambda 表达式,参数为原对象,返回值为注入的新对象
private fun swapActivityThreadHandlerCallback(swap: (Handler.Callback?) -> Handler.Callback?) {
val mHField = activityThreadClass.getDeclaredField("mH").apply { isAccessible = true }
val mH = mHField[activityThreadInstance] as Handler
val mCallbackField = Handler::class.java.getDeclaredField("mCallback").apply { isAccessible = true }
val mCallback = mCallbackField[mH] as Handler.Callback?
// 将 swap 的返回值作为新对象,实现 Hook
mCallbackField[mH] = swap(mCallback)
}
// 使用反射修改 AMS 与 App 通信的 IActivityManager Binder 对象
// swap 是一个 lambda 表达式,参数为 IActivityManager 的 Class 对象和接口原实现对象,返回值为注入的新对象
private fun swapActivityManager(swap: (Class<*>, Any) -> Any) {
val singletonClass = Class.forName("android.util.Singleton")
val mInstanceField = singletonClass.getDeclaredField("mInstance").apply { isAccessible = true }
val singletonGetMethod = singletonClass.getDeclaredMethod("get")
val (className, fieldName) = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
"android.app.ActivityManager" to "IActivityManagerSingleton"
} else {
"android.app.ActivityManagerNative" to "gDefault"
}
val activityManagerClass = Class.forName(className)
val activityManagerSingletonField = activityManagerClass.getDeclaredField(fieldName).apply { isAccessible = true }
val activityManagerSingletonInstance = activityManagerSingletonField[activityManagerClass]
// Calling get() instead of reading from the field directly to ensure the singleton is
// created.
val activityManagerInstance = singletonGetMethod.invoke(activityManagerSingletonInstance)
val iActivityManagerInterface = Class.forName("android.app.IActivityManager")
// 将 swap 的返回值作为新对象,实现 Hook
mInstanceField[activityManagerSingletonInstance] = swap(iActivityManagerInterface, activityManagerInstance!!)
}
至此,LeakCanary 初始化完成,并且成功在 Android Framework 的各个位置安插监控,实现对 Activity 和 Service 等对象进入无用状态的监听。我们可以用一张示意图描述 LeakCanary 的部分结构:
4、LeakCanary 如何判定对象泄漏
在以上步骤中,当对象的使用生命周期结束后,会交给 ObjectWatcher
监控,现在我们来具体看下它是怎么判断对象发生泄漏的。主要逻辑概括为 3 步:
- 第 1 步: 为被监控对象
watchedObject
创建一个KeyedWeakReference
弱引用,并存储到 <UUID, KeyedWeakReference> 的映射表中; - 第 2 步: postDelay 五秒后检查引用对象是否出现在引用队列中,出现在队列则说明被监控对象未发生泄漏。随后,移除映射表中未泄露的记录,更新泄漏的引用对象的
retainedUptimeMillis
字段以标记为泄漏; - 第 3 步: 通过回调
onObjectRetained
告知 LeakCanary 内部发生新的内存泄漏。
源码摘要如下:
AppWatcher.kt
val objectWatcher = ObjectWatcher(
// lambda 表达式获取当前系统时间
clock = { SystemClock.uptimeMillis() },
// lambda 表达式实现 Executor SAM 接口
checkRetainedExecutor = {
mainHandler.postDelayed(it, retainedDelayMillis)
},
// lambda 表达式获取监控开关
isEnabled = { true }
)
ObjectWatcher.kt
class ObjectWatcher constructor(
private val clock: Clock,
private val checkRetainedExecutor: Executor,
private val isEnabled: () -> Boolean = { true }
) : ReachabilityWatcher {
if (!isEnabled()) {
// 监控开关
return
}
// 被监控的对象映射表 <UUID,KeyedWeakReference>
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
// KeyedWeakReference 关联的引用队列,用于判断对象是否泄漏
private val queue = ReferenceQueue<Any>()
// 1. 为 watchedObject 对象增加监控
@Synchronized
override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
// 1.1 移除 watchedObjects 中未泄漏的引用对象
removeWeaklyReachableObjects()
// 1.2 新建一个 KeyedWeakReference 引用对象
val key = UUID.randomUUID().toString()
val watchUptimeMillis = clock.uptimeMillis()
watchedObjects[key] = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
// 2. 五秒后检查引用对象是否出现在引用队列中,否则判定发生泄漏
// checkRetainedExecutor 相当于 postDelay 五秒后执行 moveToRetained() 方法
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
// 2. 五秒后检查引用对象是否出现在引用队列中,否则说明发生泄漏
@Synchronized
private fun moveToRetained(key: String) {
// 2.1 移除 watchedObjects 中未泄漏的引用对象
removeWeaklyReachableObjects()
// 2.2 依然存在的引用对象被判定发生泄漏
val retainedRef = watchedObjects[key]
if (retainedRef != null) {
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
// 3. 回调通知 LeakCanary 内部处理
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
// 移除未泄漏对象对应的 KeyedWeakReference
private fun removeWeaklyReachableObjects() {
var ref: KeyedWeakReference?
do {
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
// KeyedWeakReference 出现在引用队列中,说明未发生泄漏
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
// 4. 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)
}
// 获取是否有内存泄漏对象
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 }
}
}
被监控对象 watchedObject
关联的弱引用对象:
KeyedWeakReference.kt
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
}
}
5、LeakCanary 何时进行分析
ObjectWatcher 判定被监控对象发生泄漏后,会通过接口方法 OnObjectRetainedListener#onObjectRetained()
回调到 LeakCanary 内部的管理器 InternalLeakCanary 处理(在前文 AppWatcher 初始化中提到过)。LeakCanary 不会每次发现内存泄漏对象都进行分析工作,而会进行两个拦截:
- 拦截 1:泄漏对象计数未达到阈值,或者进入后台时间未达到阈值;
- 拦截 2:计算距离上一次 HeapDump 未超过 60s。
源码摘要如下:
InternalLeakCanary.kt
// 从 ObjectWatcher 回调过来
override fun onObjectRetained() = scheduleRetainedObjectCheck()
private lateinit var heapDumpTrigger: HeapDumpTrigger
fun scheduleRetainedObjectCheck() {
if (this::heapDumpTrigger.isInitialized) {
heapDumpTrigger.scheduleRetainedObjectCheck()
}
}
HeapDumpTrigger.kt
fun scheduleRetainedObjectCheck(delayMillis: Long = 0L) {
// 已简化:源码此处使用时间戳拦截,避免重复 postDelayed
backgroundHandler.postDelayed({
checkRetainedObjects()
}, delayMillis)
}
private fun checkRetainedObjects() {
val config = configProvider()
// 泄漏对象计数
var retainedReferenceCount = objectWatcher.retainedObjectCount
if (retainedReferenceCount > 0) {
// 主动触发 GC,并等待 100 ms
gcTrigger.runGc()
// 重新获取泄漏对象计数
retainedReferenceCount = objectWatcher.retainedObjectCount
}
// 拦截 1:泄漏对象计数未达到阈值,或者进入后台时间未达到阈值
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))
}
请求 GC 的源码可以看一眼:
GcTrigger.kt
fun interface GcTrigger {
fun runGc()
object Default : GcTrigger {
override fun runGc() {
// Runtime.gc() 相比于 System.gc() 更有可能触发 GC
Runtime.getRuntime().gc()
// 暂停等待 GC
Thread.sleep(100)
System.runFinalization()
}
}
}
在前面的工作中,LeakCanary 已经成功生成 .hprof
堆快照文件,并且发送了一个 LeakCanary 内部事件 HeapDump
。那么这个事件在哪里被消费的呢?
一步步跟踪代码可以看到 LeakCanary 的配置项中设置了多个事件消费者 EventListener,其中与 HeapDump 事件有关的是 when{}
代码块中三个消费者。不过,这三个消费者并不是并存的,而是会根据 App 当前的依赖项而选择最优的执行策略:
- 策略 1 - WorkerManager 多进程分析
- 策略 2 - WorkManager 异步分析
- 策略 3 - 异步线程分析(兜底策略)
LeakCanary 配置项中的事件消费者:
LeakCanary.kt
data class Config(
val eventListeners: List<EventListener> = listOf(
LogcatEventListener,
ToastEventListener,
LazyForwardingEventListener {
if (InternalLeakCanary.formFactor == TV) TvEventListener else NotificationEventListener
},
when {
// 策略 1 - WorkerManager 多进程分析
RemoteWorkManagerHeapAnalyzer.remoteLeakCanaryServiceInClasspath ->RemoteWorkManagerHeapAnalyzer
// 策略 2 - WorkManager 异步分析
WorkManagerHeapAnalyzer.validWorkManagerInClasspath -> WorkManagerHeapAnalyzer
// 策略 3 - 异步线程分析(兜底策略)
else -> BackgroundThreadHeapAnalyzer
}
),
...
)
策略 1 - WorkerManager 多进程分析
判断是否可以类加载 RemoteLeakCanaryWorkerService
,这个类位于前文提到的 com.squareup.leakcanary:leakcanary-android-process:2.9.1
依赖中。如果可以类加载成功则视为有依赖,使用 WorkerManager 多进程分析;
RemoteWorkManagerHeapAnalyzer.kt
object RemoteWorkManagerHeapAnalyzer : EventListener {
// 通过类加载是否成功,判断是否存在依赖
internal val remoteLeakCanaryServiceInClasspath by lazy {
try {
Class.forName("leakcanary.internal.RemoteLeakCanaryWorkerService")
true
} catch (ignored: Throwable) {
false
}
}
override fun onEvent(event: Event) {
if (event is HeapDump) {
// 创建并分发 WorkManager 多进程请求
val heapAnalysisRequest = OneTimeWorkRequest.Builder(RemoteHeapAnalyzerWorker::class.java).apply {
val dataBuilder = Data.Builder()
.putString(ARGUMENT_PACKAGE_NAME, application.packageName)
.putString(ARGUMENT_CLASS_NAME, REMOTE_SERVICE_CLASS_NAME)
setInputData(event.asWorkerInputData(dataBuilder))
with(WorkManagerHeapAnalyzer) {
addExpeditedFlag()
}
}.build()
WorkManager.getInstance(application).enqueue(heapAnalysisRequest)
}
}
}
RemoteHeapAnalyzerWorker.kt
internal class RemoteHeapAnalyzerWorker(appContext: Context, workerParams: WorkerParameters) : RemoteListenableWorker(appContext, workerParams) {
override fun startRemoteWork(): ListenableFuture<Result> {
val heapDump = inputData.asEvent<HeapDump>()
val result = SettableFuture.create<Result>()
heapAnalyzerThreadHandler.post {
// 1.1 分析堆快照
val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(heapDump, isCanceled = {
result.isCancelled
}) { progressEvent ->
// 1.2 发送分析进度事件
if (!result.isCancelled) {
InternalLeakCanary.sendEvent(progressEvent)
}
}
// 1.3 发送分析完成事件
InternalLeakCanary.sendEvent(doneEvent)
result.set(Result.success())
}
return result
}
}
策略 2 - WorkManager 异步分析:
判断是否可以类加载 androidx.work.WorkManager
,如果可以,则使用 WorkManager 异步分析;
WorkManagerHeapAnalyzer.kt
internal val validWorkManagerInClasspath by lazy {
// 判断 WorkManager 依赖,代码略
}
override fun onEvent(event: Event) {
if (event is HeapDump) {
// 创建并分发 WorkManager 请求
val heapAnalysisRequest = OneTimeWorkRequest.Builder(HeapAnalyzerWorker::class.java).apply {
setInputData(event.asWorkerInputData())
addExpeditedFlag()
}.build()
val application = InternalLeakCanary.application
WorkManager.getInstance(application).enqueue(heapAnalysisRequest)
}
}
HeapAnalyzerWorker.kt
internal class HeapAnalyzerWorker(appContext: Context, workerParams: WorkerParameters) : Worker(appContext, workerParams) {
override fun doWork(): Result {
// 2.1 分析堆快照
val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(inputData.asEvent()) { event ->
// 2.2 发送分析进度事件
InternalLeakCanary.sendEvent(event)
}
// 2.3 发送分析完成事件
InternalLeakCanary.sendEvent(doneEvent)
return Result.success()
}
}
策略 3 - 异步线程分析(兜底策略):
如果以上策略未命中,则直接使用子线程兜底执行。
BackgroundThreadHeapAnalyzer.kt
object BackgroundThreadHeapAnalyzer : EventListener {
// HandlerThread
internal val heapAnalyzerThreadHandler by lazy {
val handlerThread = HandlerThread("HeapAnalyzer")
handlerThread.start()
Handler(handlerThread.looper)
}
override fun onEvent(event: Event) {
if (event is HeapDump) {
// HandlerThread 请求
heapAnalyzerThreadHandler.post {
// 3.1 分析堆快照
val doneEvent = AndroidDebugHeapAnalyzer.runAnalysisBlocking(event) { event ->
// 3.2 发送分析进度事件
InternalLeakCanary.sendEvent(event)
}
// 3.3 发送分析完成事件
InternalLeakCanary.sendEvent(doneEvent)
}
}
}
}
可以看到,不管采用那种执行策略,最终执行的逻辑都是一样的:
- 1、分析堆快照;
- 2、发送分析进度事件;
- 3、发送分析完成事件。
6、LeakCanary 如何分析堆快照
在前面的分析中,我们已经知道 LeakCanary 是通过子线程或者子进程执行 AndroidDebugHeapAnalyzer.runAnalysisBlocking
方法来分析堆快照的,并在分析过程中和分析完成后发送回调事件。现在我们来阅读 LeakCanary 的堆快照分析过程:
AndroidDebugHeapAnalyzer.kt
fun runAnalysisBlocking(
heapDumped: HeapDump,
isCanceled: () -> Boolean = { false },
progressEventListener: (HeapAnalysisProgress) -> Unit
): HeapAnalysisDone<*> {
...
// 1. .hprof 文件
val heapDumpFile = heapDumped.file
// 2. 分析堆快照
val heapAnalysis = analyzeHeap(heapDumpFile, progressListener, isCanceled)
val analysisDoneEvent = ScopedLeaksDb.writableDatabase(application) { db ->
// 3. 将分析报告持久化到 DB
val id = HeapAnalysisTable.insert(db, heapAnalysis)
// 4. 发送分析完成事件(返回到上一级进行发送:InternalLeakCanary.sendEvent(doneEvent))
val showIntent = LeakActivity.createSuccessIntent(application, id)
val leakSignatures = fullHeapAnalysis.allLeaks.map { it.signature }.toSet()
val leakSignatureStatuses = LeakTable.retrieveLeakReadStatuses(db, leakSignatures)
val unreadLeakSignatures = leakSignatureStatuses.filter { (_, read) -> !read}.keys.toSet()
HeapAnalysisSucceeded(heapDumped.uniqueId, fullHeapAnalysis, unreadLeakSignatures ,showIntent)
}
return analysisDoneEvent
}
核心分析方法是 analyzeHeap(…)
,继续往下走:
AndroidDebugHeapAnalyzer.kt
private fun analyzeHeap(
heapDumpFile: File,
progressListener: OnAnalysisProgressListener,
isCanceled: () -> Boolean
): HeapAnalysis {
...
// Shark 堆快照分析器
val heapAnalyzer = HeapAnalyzer(progressListener)
...
// 构建对象图信息
val sourceProvider = ConstantMemoryMetricsDualSourceProvider(ThrowingCancelableFileSourceProvider(heapDumpFile)
val graph = sourceProvider.openHeapGraph(proguardMapping = proguardMappingReader?.readProguardMapping())
...
// 开始分析
heapAnalyzer.analyze(
heapDumpFile = heapDumpFile,
graph = graph,
leakingObjectFinder = config.leakingObjectFinder, // 默认是 KeyedWeakReferenceFinder
referenceMatchers = config.referenceMatchers, // 默认是 AndroidReferenceMatchers
computeRetainedHeapSize = config.computeRetainedHeapSize, // 默认是 true
objectInspectors = config.objectInspectors, // 默认是 AndroidObjectInspectors
metadataExtractor = config.metadataExtractor // 默认是 AndroidMetadataExtractor
)
}
开始进入 Shark 组件:
shark.HeapAnalyzer.kt
// analyze -> analyze -> FindLeakInput.analyzeGraph
private fun FindLeakInput.analyzeGraph(
metadataExtractor: MetadataExtractor,
leakingObjectFinder: LeakingObjectFinder,
heapDumpFile: File,
analysisStartNanoTime: Long
): HeapAnalysisSuccess {
...
// 1. 在堆快照中寻找泄漏对象,默认是寻找 KeyedWeakReference 类型对象
// leakingObjectFinder 默认是 KeyedWeakReferenceFinder
val leakingObjectIds = leakingObjectFinder.findLeakingObjectIds(graph)
// 2. 分析泄漏对象的最短引用链,并按照应用链签名分类
// applicationLeaks: Application Leaks
// librbuildLeakTracesaryLeaks:Library Leaks
// unreachableObjects:LeakCanary 无法分析出强引用链,可以提 Stack Overflow
val (applicationLeaks, libraryLeaks, unreachableObjects) = findLeaks(leakingObjectIds)
// 3. 返回分析完成事件
return HeapAnalysisSuccess(...)
}
private fun FindLeakInput.findLeaks(leakingObjectIds: Set<Long>): LeaksAndUnreachableObjects {
// PathFinder:引用链分析器
val pathFinder = PathFinder(graph, listener, referenceReader, referenceMatchers)
// pathFindingResults:完整引用链
val pathFindingResults = pathFinder.findPathsFromGcRoots(leakingObjectIds, computeRetainedHeapSize)
// unreachableObjects:LeakCanary 无法分析出强引用链(相当于 LeakCanary 的 Bug)
val unreachableObjects = findUnreachableObjects(pathFindingResults, leakingObjectIds)
// shortestPaths:最短引用链
val shortestPaths = deduplicateShortestPaths(pathFindingResults.pathsToLeakingObjects)
// inspectedObjectsByPath:标记信息
val inspectedObjectsByPath = inspectObjects(shortestPaths)
// retainedSizes:泄漏内存大小
val retainedSizes = computeRetainedSizes(inspectedObjectsByPath, pathFindingResults.dominatorTree)
// 生成单个泄漏问题的分析报告,并按照应用链签名分组,按照 Application Leaks 和 Library Leaks 分类,按照 Application Leaks 和 Library Leaks 分类
// applicationLeaks: Application Leaks
// librbuildLeakTracesaryLeaks:Library Leaks
val (applicationLeaks, librbuildLeakTracesaryLeaks) = buildLeakTraces(shortestPaths, inspectedObjectsByPath, retainedSizes)
return LeaksAndUnreachableObjects(applicationLeaks, libraryLeaks, unreachableObjects)
}
可以看到,堆快照分析最终是交给 Shark 中的 HeapAnalizer 完成的,核心流程是:
- 1、在堆快照中寻找泄漏对象,默认是寻找 KeyedWeakReference 类型对象;
- 2、分析 KeyedWeakReference 对象的最短引用链,并按照引用链签名分组,按照 Application Leaks 和 Library Leaks 分类;
- 3、返回分析完成事件。
第 1 步和第 3 步不用说了,继续分析最复杂的第 2 步:
shark.HeapAnalyzer.kt
// 生成单个泄漏问题的分析报告,并按照应用链签名分组,按照 Application Leaks 和 Library Leaks 分类,按照 Application Leaks 和 Library Leaks 分类
private fun FindLeakInput.buildLeakTraces(
shortestPaths: List<ShortestPath> /*最短引用链*/ ,
inspectedObjectsByPath: List<List<InspectedObject>> /*标记信息*/ ,
retainedSizes: Map<Long, Pair<Int, Int>>? /*泄漏内存大小*/
): Pair<List<ApplicationLeak>, List<LibraryLeak>> {
// Application Leaks
val applicationLeaksMap = mutableMapOf<String, MutableList<LeakTrace>>()
// Library Leaks
val libraryLeaksMap = mutableMapOf<String, Pair<LibraryLeakReferenceMatcher, MutableList<LeakTrace>>>()
shortestPaths.forEachIndexed { pathIndex, shortestPath ->
// 标记信息
val inspectedObjects = inspectedObjectsByPath[pathIndex]
// 实例化引用链上的每个对象快照(非怀疑对象的 leakingStatus 为 NOT_LEAKING)
val leakTraceObjects = buildLeakTraceObjects(inspectedObjects, retainedSizes)
val referencePath = buildReferencePath(shortestPath, leakTraceObjects)
// 分析报告
val leakTrace = LeakTrace(
gcRootType = GcRootType.fromGcRoot(shortestPath.root.gcRoot),
referencePath = referencePath,
leakingObject = leakTraceObjects.last()
)
val firstLibraryLeakMatcher = shortestPath.firstLibraryLeakMatcher()
if (firstLibraryLeakMatcher != null) {
// Library Leaks
val signature: String = firstLibraryLeakMatcher.pattern.toString().createSHA1Hash()
libraryLeaksMap.getOrPut(signature) { firstLibraryLeakMatcher to mutableListOf() }.second += leakTrace
} else {
// Application Leaks
applicationLeaksMap.getOrPut(leakTrace.signature) { mutableListOf() } += leakTrace
}
}
val applicationLeaks = applicationLeaksMap.map { (_, leakTraces) ->
// 实例化为 ApplicationLeak 类型
ApplicationLeak(leakTraces)
}
val libraryLeaks = libraryLeaksMap.map { (_, pair) ->
// 实例化为 LibraryLeak 类型
val (matcher, leakTraces) = pair
LibraryLeak(leakTraces, matcher.pattern, matcher.description)
}
return applicationLeaks to libraryLeaks
}
LeakCanary如何筛选怀疑对象
LeakCanary 会使用 ObjectInspector 对象检索器在引用链上的节点中标记必要的信息和状态,标记信息会显示在分析报告中,并且会影响报告中的提示。而引用链 LEAKING
节点以后到第一个 NOT_LEAKING
节点中间的节点,才会用 ~~~
下划线标记为怀疑对象。
LeakCanary 通过 leakingObjectFinder
标记引用信息,leakingObjectFinder 默认是 AndroidObjectInspectors.appDefaults
,也可以在配置项中自定义。
// inspectedObjectsByPath:筛选出非怀疑对象(分析报告中 ~~~ 标记的是怀疑对象)
val inspectedObjectsByPath = inspectObjects(shortestPaths)
看一下可视化报告中相关源码:
DisplayLeakAdapter.kt
...
val reachabilityString = when (leakingStatus) {
UNKNOWN -> extra("UNKNOWN")
NOT_LEAKING -> "NO" + extra(" (${leakingStatusReason})")
LEAKING -> "YES" + extra(" (${leakingStatusReason})")
}
...
LeakTrace.kt
// 是否为怀疑对象
fun referencePathElementIsSuspect(index: Int): Boolean {
return when (referencePath[index].originObject.leakingStatus) {
UNKNOWN -> true
NOT_LEAKING -> index == referencePath.lastIndex || referencePath[index + 1].originObject.leakingStatus != NOT_LEAKING
else -> false
}
}
LeakCanary 分析完成后的处理
有两个位置处理了 HeapAnalysisSucceeded
事件:
- Logcat:打印分析报告日志;
- Notification: 发送分析成功系统通知消息。
LogcatEventListener.kt
object LogcatEventListener : EventListener {
...
SharkLog.d { "\u200B\n${LeakTraceWrapper.wrap(event.heapAnalysis.toString(), 120)}" }
...
}
NotificationEventListener.kt
object NotificationEventListener : EventListener {
...
val flags = if (Build.VERSION.SDK_INT >= 23) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT
}
// 点击通知消息打开可视化分析报告
val pendingIntent = PendingIntent.getActivity(appContext, 1, event.showIntent, flags)
showHeapAnalysisResultNotification(contentTitle,pendingIntent)
...
}
至此,LeakCanary 原理分析完毕。
7、 总结
最后来总结下LeakCanary内存泄漏分析过程吧(Activity):
(1)注册监听Activity生命周期onDestroy事件
(2)在Activity onDestroy事件回调中创建KeyedWeakReference对象,并关联ReferenceQueue
(3)延时5秒检查目标对象是否回收
(4)未回收则开启服务,dump heap获取内存快照hprof文件
(5)解析hprof文件根据KeyedWeakReference类型过滤找到内存泄漏对象
(6)计算对象到GC roots的最短路径,并合并所有最短路径为一棵树
(7)输出分析结果,并根据分析结果展示到可视化页面