下面内容都是基于LeakCanary2.5
使用
根据官方文档的说明,只需在build.gradle中导入依赖com.squareup.leakcanary:leakcanary-android:xx即可
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
}
疑问
只导入了一个依赖是如何实现内存泄漏监听的?
内存泄漏信息是如何拿到的?
概貌
https://github.com/square/leakcanary
从源码目录中可以看到leakcanary工程中包含了多个module,并存在依赖关系
依赖分析
在导入leakcanary的工程中执行如下命令打印依赖树
./gradlew :app:dependencie
debugCompileClasspath - Compile classpath for compilation 'debug' (target (androidJvm)).
+--- com.squareup.leakcanary:leakcanary-android:2.5
| +--- com.squareup.leakcanary:leakcanary-android-core:2.5
| | +--- com.squareup.leakcanary:shark-android:2.5
| | | \--- com.squareup.leakcanary:shark:2.5
| | | \--- com.squareup.leakcanary:shark-graph:2.5
| | | \--- com.squareup.leakcanary:shark-hprof:2.5
| | | \--- com.squareup.leakcanary:shark-log:2.5
| | +--- com.squareup.leakcanary:leakcanary-object-watcher-android:2.5
| | | +--- com.squareup.leakcanary:leakcanary-object-watcher:2.5
| | | | \--- com.squareup.leakcanary:shark-log:2.5
| | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72
| | | +--- org.jetbrains.kotlin:kotlin-stdlib-common:1.3.72
| | | \--- org.jetbrains:annotations:13.0
| | +--- com.squareup.leakcanary:leakcanary-object-watcher-android-androidx:2.5
| | | +--- com.squareup.leakcanary:leakcanary-object-watcher-android:2.5 (*)
| | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 (*)
| | +--- com.squareup.leakcanary:leakcanary-object-watcher-android-support-fragments:2.5
| | | +--- com.squareup.leakcanary:leakcanary-object-watcher-android:2.5 (*)
| | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 (*)
| | +--- com.squareup.leakcanary:plumber-android:2.5
| | | +--- com.squareup.leakcanary:shark-log:2.5
| | | \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 (*)
| | \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 (*)
| \--- org.jetbrains.kotlin:kotlin-stdlib:1.3.72 (*)
...
解惑
根据依赖引用关系进行查找,在leakcanary-object-watcher-android的AndroidManifest.xml中注册了一个provider
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.squareup.leakcanary.objectwatcher" > <application> <provider android:name="leakcanary.internal.AppWatcherInstaller$MainProcess" android:authorities="${applicationId}.leakcanary-installer" android:enabled="@bool/leak_canary_watcher_auto_install" android:exported="false"/> application>manifest>
Provider的onCreate方法中调用了AppWatcher.manualInstall方法
internal sealed class AppWatcherInstaller : ContentProvider() { /** 1. [MainProcess] automatically sets up the LeakCanary code that runs in the main app process. */ internal class MainProcess : AppWatcherInstaller() ... override fun onCreate(): Boolean { val application = context!!.applicationContext as Application AppWatcher.manualInstall(application) return true }}
从AppWatcher源码中可以获得几点信息
AppWatcher是使用ObjectWatcher的入口点,AppWatcher.objectWatcher负责检测保留的对象
manualInstall方法用来初始化监听,会被provider自动初始化,也可以通过复写“leak_canary_watcher_auto_install”关闭自动初始化
/** * The entry point API for using [ObjectWatcher] in an Android app. [AppWatcher.objectWatcher] is * in charge of detecting retained objects, and [AppWatcher] is auto configured on app start to * pass it activity and fragment instances. Call [ObjectWatcher.watch] on [objectWatcher] to * watch any other object that you expect to be unreachable. */object AppWatcher { ... /** * [AppWatcher] is automatically installed in the main process on startup. You can * disable this behavior by overriding the `leak_canary_watcher_auto_install` boolean resource: * * ``` * <?xml version="1.0" encoding="utf-8"?> * <resources> * <bool name="leak_canary_watcher_auto_install">falsebool> * resources> * ``` * * If you disabled automatic install then you can call this method to install [AppWatcher]. */ fun manualInstall(application: Application) { InternalAppWatcher.install(application) }}
初始化方法中会设置对Activity、Fragment的监听,分别是ActivityDestroyWatcher、FragmentDestroyWatcher
internal object InternalAppWatcher { ... fun install(application: Application) { checkMainThread() if (this::application.isInitialized) { return } InternalAppWatcher.application = application if (isDebuggableBuild) { SharkLog.logger = DefaultCanaryLog() } val configProvider = { AppWatcher.config } ActivityDestroyWatcher.install(application, objectWatcher, configProvider) FragmentDestroyWatcher.install(application, objectWatcher, configProvider) onAppWatcherInstalled(application) }}
先看对Activity的监听,利用application的lifecycleCallback注册了destroy时机的回调,回调中使用objectWatcher.watch建立对activity的监听, 对Fragment的监听思想是一致的
internal class ActivityDestroyWatcher private constructor( private val objectWatcher: ObjectWatcher, private val configProvider: () -> Config) { private val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks by noOpDelegate() { override fun onActivityDestroyed(activity: Activity) { if (configProvider().watchActivities) { objectWatcher.watch( activity, "${activity::class.java.name} received Activity#onDestroy() callback" ) } } } companion object { fun install( application: Application, objectWatcher: ObjectWatcher, configProvider: () -> Config ) { val activityDestroyWatcher = ActivityDestroyWatcher(objectWatcher, configProvider) application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks) } }}
ObjectWatcher的watch方法内容也比较简单
class ObjectWatcher constructor( ... // 观察者集合 private val onObjectRetainedListeners = mutableSetOf() // 实例化引用队列 private val queue = ReferenceQueue() ... @Synchronized fun watch( watchedObject: Any, description: String ) { if (!isEnabled()) { return } removeWeaklyReachableObjects() // 1、生成uuid作为key val key = UUID.randomUUID() .toString() val watchUptimeMillis = clock.uptimeMillis() // 2、使用弱引用持有被观测对象 val reference = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue) ... // 3、放入被观测集合 watchedObjects[key] = reference // 4、启动线程进行观测 checkRetainedExecutor.execute { moveToRetained(key) } } ... @Synchronized private fun moveToRetained(key: String) { removeWeaklyReachableObjects() val retainedRef = watchedObjects[key] if (retainedRef != null) { retainedRef.retainedUptimeMillis = clock.uptimeMillis() // 通知观察者 onObjectRetainedListeners.forEach { it.onObjectRetained() } } } // 如果被观测者没有强引用,将被观测者从被观测集合中移除 private fun removeWeaklyReachableObjects() { // WeakReferences are enqueued as soon as the object to which they point to becomes weakly // reachable. This is before finalization or garbage collection has actually happened. var ref: KeyedWeakReference? do { ref = queue.poll() as KeyedWeakReference? if (ref != null) { watchedObjects.remove(ref.key) } } while (ref != null) } ...}
其中有如下几步操作:
生成一个uuid
实例化一个KeyedWeakReference(继承自WeakReference),以弱引用的方式持有观测对象实例(这里是Activity实例)
uuid作为key,reference作为value放入被观测集合watchedObjects(一个MutableMap)
在线程池checkRetainedExecutor中启动线程进行观测, 如果被观测对象还存在强引用,则有发生内存泄漏的嫌疑,将会通知监听者采取一定动作,具体是什么后面再说
其中涉及到一个知识点:
第2步使用watchedObject构造弱引用对象时,传入了一个引用队列queue,当watchedObject的变成弱可达时(这里可以理解为没有强引用时),会在GC、ReferenceHandler处理之后入队到queue,这里暂且称之为入队过程。
所以在queue中的对象可以认为不会发生内存泄漏,可以从观测集合中移除
如果观测对象依然是被强引用持有,那么监听者会有什么动作??
上面代码中可以看到,对仍然被强引用持有的对象会逐一调用观察者的onObjectRetained方法,那观察者又是什么?
OnObjectRetainedListener接口是被InternalLeakCanary类实现的
internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener { ... override fun onObjectRetained() = scheduleRetainedObjectCheck() fun scheduleRetainedObjectCheck() { if (this::heapDumpTrigger.isInitialized) { heapDumpTrigger.scheduleRetainedObjectCheck() } } ...}
heapDumpTrigger的scheduleRetainedObjectCheck方法会去post一个Runnable,在LeakCanary的后台线程去执行,实际执行的内容为checkRetainedObjects方法
internal class HeapDumpTrigger( ... // 发送一个延迟消息去后台执行,用于检查被观测对象 fun scheduleRetainedObjectCheck( delayMillis: Long = 0L ) { val checkCurrentlyScheduledAt = checkScheduledAt if (checkCurrentlyScheduledAt > 0) { return } checkScheduledAt = SystemClock.uptimeMillis() + delayMillis backgroundHandler.postDelayed({ checkScheduledAt = 0 checkRetainedObjects() }, delayMillis) } ... private fun checkRetainedObjects() { ... val config = configProvider() ... // 获取还存在的被观测对象数量 var retainedReferenceCount = objectWatcher.retainedObjectCount // 如果还存在被观测对象,触发一次GC,并再次统计被观测对象数量 if (retainedReferenceCount > 0) { // 触发GC,将没有强引用的被观测对象移出被观测集合 gcTrigger.runGc() // 重新计数 retainedReferenceCount = objectWatcher.retainedObjectCount } // 未达到设置条件,暂时不进行转储 if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return val now = SystemClock.uptimeMillis() val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis // 如果距离上次堆转储的时间小于60秒,则等间隔达到60秒后再次触发对被观测对象的检查 if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) { onRetainInstanceListener.onEvent(DumpHappenedRecently) showRetainedCountNotification( objectCount = retainedReferenceCount, contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait) ) // 设置延时触发,保证两次堆转储时间间隔不小于60秒 scheduleRetainedObjectCheck( delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis ) return } dismissRetainedCountNotification() // 执行堆转储 dumpHeap(retainedReferenceCount, retry = true) } ...}
过程分为如下几步:
判断被观测对象的数量大于0
触发GC,将没有强引用的被观测对象移出被观测集合
重新计数被观测对象,剩下的为有内存泄漏嫌疑的对象
判断是否达到堆转储条件
达到条件则进行堆转储
其中堆转储的条件受到内存泄漏对象个数、应用是否显示、观测期三个条件限制,其中符合转储的条件:
泄漏对象个数>=阈值(默认是5个)
0
到这里对LeakCanary的内存泄漏对象检测过程已经有个了整体的了解,后面则是拿到堆文件之后的分析工作
总结一下整个过程主要有如下几个关键点:
AndroidManifest.xml中通过Provider进行初始化
- 利用lifecycleCallback回调的destroy时机建立观测关系
- 利用WeakReference和ReferenceQueue的特点确定泄漏对象
- 通过堆转储进行进一步分析