0.前言
Android开发中,内存泄露是引发OOM的一大原因,为了避免内存泄露的发生,通常使用LeakCanary来检测是否发生了内存泄露,接下来就根据LeakCanary源码(版本 2.8.1 ),来看看是怎么实现内存泄露的检测的。
1.初始化
自2.0版本之后,LeakCanary都不需要在代码中主动初始化,只需要在build.gradle中引入项目即可:
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.8.1'
}
这是因为项目在自己的ContentProvider中自动初始化了
这里有一个知识点,那就是ContentProvider的onCreate()方法,会在Application的onCreate()方法之前执行。
项目中的相关源码如下:
leakcanary/leakcanary-object-watcher-android/src/main/AndroidManifest.xml
<application>
<provider
android:name="leakcanary.internal.MainProcessAppWatcherInstaller"
android:authorities="${applicationId}.leakcanary-installer"
android:enabled="@bool/leak_canary_watcher_auto_install"
android:exported="false"/>
</application>
/**
* Content providers are loaded before the application class is created. [MainProcessAppWatcherInstaller] is
* used to install [leakcanary.AppWatcher] on application start.
*
* [MainProcessAppWatcherInstaller] automatically sets up the LeakCanary code that runs in the main
* app process.
*/
internal class MainProcessAppWatcherInstaller : ContentProvider() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)
return true
}
override fun query(
uri: Uri,
projectionArg: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
): Cursor? = null
override fun getType(uri: Uri): String? = null
override fun insert(uri: Uri, contentValues: ContentValues?): Uri? = null
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int = 0
override fun update(
uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?
): Int = 0
}
可以看到,在MainProcessAppWatcherInstaller这个ContentProvider的子类中,只在onCreate()方法下初始化了LeakCanary库,其他的方法全是空实现。
2.回收监听
首先要知道,LeakCanary检测什么对象的泄露?
在官网有如下的描述:
LeakCanary automatically detects leaks of the following objects:
destroyed Activity instances
destroyed Fragment instances
destroyed fragment View instances
cleared ViewModel instances
所以,LeakCanary是在检测Activity,Fragment,Fragment中的View,ViewModel这几种对象是否泄露,接下来看看是怎么监听对象的。
初始化方法为 AppWatcher.manualInstall(application),源码如下:
@JvmOverloads
fun manualInstall(
application: Application,
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
)
}
check(retainedDelayMillis >= 0) {
"retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
}
installCause = RuntimeException("manualInstall() first called here")
this.retainedDelayMillis = retainedDelayMillis
if (application.isDebuggableBuild) {
LogcatSharkLog.install()
}
// Requires AppWatcher.objectWatcher to be set
LeakCanaryDelegate.loadLeakCanary(application)
watchersToInstall.forEach {
it.install()
}
}
这里有两个关键信息,一个是retained时间默认5秒(后文会详细说retained是什么),另一个是默认观察者是appDefaultWatchers,appDefaultWatchers的源码如下:
/**
* Creates a new list of default app [InstallableWatcher], created with the passed in
* [reachabilityWatcher] (which defaults to [objectWatcher]). Once installed,
* these watchers will pass in to [reachabilityWatcher] objects that they expect to become
* weakly reachable.
*
* The passed in [reachabilityWatcher] should probably delegate to [objectWatcher] but can
* be used to filter out specific instances.
*/
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
看到这里,已经豁然开朗,这些观察者就是在对不同的对象进行监听,除了上文说到的4个外,还可以监听Service,查了一下更新日志,这是在Version 2.6 - Christmas Release 🎄 (2020-12-24)版本新增的功能,应该是官网首页没有及时更新显示内容。
再跟踪ActivityWatcher方法,源码如下:
/**
* Expects activities to become weakly reachable soon after they receive the [Activity.onDestroy]
* callback.
*/
class ActivityWatcher(
private val application: Application,
private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
//注释1
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
override fun install() {
application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
}
override fun uninstall() {
application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
}
}
监听activity的生命周期,当发生destory后,触发注释1处的检测,最后是由ObjectWatcher.expectWeaklyReachable()方法处理,其他几个的监听和activity类似,最终都交给ObjectWatcher.expectWeaklyReachable()方法处理。
3.回收检测
上文说到,当监听到对象的destory后,触发ObjectWatcher.expectWeaklyReachable()方法执行回收检测,源码如下:
@Synchronized override fun expectWeaklyReachable(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
removeWeaklyReachableObjects()
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
val reference =
KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
SharkLog.d {
"Watching " +
(if (watchedObject is Class<*>) watchedObject.toString() else "instance of ${watchedObject.javaClass.name}") +
(if (description.isNotEmpty()) " ($description)" else "") +
" with key $key"
}
watchedObjects[key] = reference
checkRetainedExecutor.execute {
moveToRetained(key)
}
}
先根据当前对象,生成一个弱应用对象,然后放到队列中,5秒后检测队列里面的弱引用对象是否已经回收。
使用removeWeaklyReachableObjects和moveToRetained方法实现,
removeWeaklyReachableObjects是移除所有已经回收的引用,
moveToRetained是在此5秒后执行(5秒就是上文提到的retained时间),再次移除所有回收的引用,如果这个时候还有引用存在,说明对象没用被回收,存在泄露的风险。
4.回调处理
当检测到泄露的对象,通过OnObjectRetainedListener.onObjectRetained()回调处理,处理内容包括获取内存快照,生成hprof文件,解析hprof文件,找到内存泄露对象,计算到对象到GC roots的最短路径并合并成一棵树,输出分析结果等。
关于回调处理的过程,过于复杂且不是本文的重点,不过多介绍。