LeakCanary源码分析(2.10最新版) 您所在的位置:网站首页 squareup专辑配置 LeakCanary源码分析(2.10最新版)

LeakCanary源码分析(2.10最新版)

2023-09-30 22:03| 来源: 网络整理| 查看: 265

开启掘金成长之旅!这是我参与「掘金日新计划 · 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 { // 引用对象,被回收时置null volatile T referent; //保存即将被回收的reference对象 final ReferenceQueue 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文件中发现了秘密:

这里注册了一个继承自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 = 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 的方案。

图片.png

在初始过程中,对应四种场景的内存泄露监听

fun appDefaultWatchers( application: Application, reachabilityWatcher: ReachabilityWatcher = objectWatcher ): List { 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 { 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 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() { // 重写 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) -> ArrayList) { windowManagerInstance?.let { windowManagerInstance -> mViewsField?.let { mViewsField -> val mViews = mViewsField[windowManagerInstance] as ArrayList 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() 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 映射表 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 的部分结构:

图片.png

4、LeakCanary 如何判定对象泄漏

在以上步骤中,当对象的使用生命周期结束后,会交给 ObjectWatcher 监控,现在我们来具体看下它是怎么判断对象发生泄漏的。主要逻辑概括为 3 步:

第 1 步:  为被监控对象 watchedObject 创建一个 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 } // 被监控的对象映射表 private val watchedObjects = mutableMapOf() // KeyedWeakReference 关联的引用队列,用于判断对象是否泄漏 private val queue = ReferenceQueue() // 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 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 = 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 { val heapDump = inputData.asEvent() val result = SettableFuture.create() 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): 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 /*最短引用链*/ , inspectedObjectsByPath: List /*标记信息*/ , retainedSizes: Map? /*泄漏内存大小*/ ): Pair { // Application Leaks val applicationLeaksMap = mutableMapOf() // Library Leaks val libraryLeaksMap = mutableMapOf() 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)输出分析结果,并根据分析结果展示到可视化页面

图片.png



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有