LeakCanary是一个内存泄漏检测的工具。那么,内存泄漏是如何定义的,通常有哪些情况呢?
一,在基于 Java 的运行时中,内存泄漏是一种编程错误,它导致应用程序保留对不再需要的对象的引用。因此,为该对象分配的内存无法回收,最终导致OutOfMemoryError (OOM)崩溃。
二,大多数内存泄漏是由与对象生命周期相关的错误引起的。以下是一些常见的 Android 错误:
- 将
Fragment
实例添加到 backstack 而不清除该 Fragment 的视图字段Fragment.onDestroyView()
(在此 StackOverflow 答案中的更多详细信息)。 - 将
Activity
实例作为Context
字段存储在由于配置更改而在活动重新创建中幸存下来的对象中。 - 注册一个侦听器、广播接收器或 RxJava 订阅,这些订阅引用具有生命周期的对象,并且在生命周期结束时忘记注销。
LeakCanary2和LeakCanary的区别
LeakCanary 2.0使用非常简单,只需要在build.gradle中配置一下,无需在项目中添加任何代码。
dependencies { // debugImplementation because LeakCanary should only run in debug builds. debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-3' }
原理就是利用ContentProvider的特性,其onCreate方法会在Application的onCreate方法之前被系统调用。所以只需要在AndroidManifest.xml中配置一下这个ContentProvider,然后在onCreate方法中进行初始化即可。
ContentProvider除了在onCreate方法中进行了初始化处理,其他方法都是空实现。在上面的第26行,onCreate方法中初始化代码为InternalAppWatcher.install(application)
。
InternalAppWatcher.install(Application)
方法完成了LeakCanary的初始化操作,里面涉及到协作的一些类的定义。
leakcanary-object-watcher-android/src/main/java/leakcanary/internal/InternalAppWatcher.kt
internal object InternalAppWatcher { // 通过判断lateinit修饰的Application是否已经初始化,来判断LeakCanary是否已经安装 val isInstalled get() = ::application.isInitialized // LeakCanary安装完成的回调,实际上对应的是leakcanary.internal.InternalLeakCanary这个类 private val onAppWatcherInstalled: (Application) -> Unit val isDebuggableBuild by lazy { (application.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0 } lateinit var application: Application // ObjectWatcher参数1 private val clock = object : Clock { override fun uptimeMillis(): Long { return SystemClock.uptimeMillis() } } private val mainHandler = Handler(Looper.getMainLooper()) init { // 这里为什么要这么费劲的获取该object的对象呢? // 因为InternalLeakCanary类是上层模块的,这里没有办法直接引用 val internalLeakCanary = try { val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary") leakCanaryListener.getDeclaredField("INSTANCE") .get(null) } catch (ignored: Throwable) { NoLeakCanary } @kotlin.Suppress("UNCHECKED_CAST") onAppWatcherInstalled = internalLeakCanary as (Application) -> Unit } // 主线程抛出任务,delay 5s private val checkRetainedExecutor = Executor { mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis) } val objectWatcher = ObjectWatcher( clock = clock, checkRetainedExecutor = checkRetainedExecutor, isEnabled = { AppWatcher.config.enabled } ) fun install(application: Application) { ... checkMainThread() // lateinit修饰的对象可以通过::<field>.isInitialized来判断有没有初始化 // 如果已经初始化了,则不需要再次install if (this::application.isInitialized) { return } InternalAppWatcher.application = application // 安装ActivityDestroyWatcher、FragmentDestroyWatcher val configProvider = { AppWatcher.config } ActivityDestroyWatcher.install(application, objectWatcher, configProvider) FragmentDestroyWatcher.install(application, objectWatcher, configProvider) // 通知上层模块的InternalLeakCanary.invoke方法 onAppWatcherInstalled(application) } ... }
在上面的这个install方法中,干了三件事:1. 初始化一个ObjectWatcher
对象:其clock
就是SystemClock.uptimeMillis()
,checkRetainedExecutor
是利用主线程Handler执行的任务 2. Activity、Fragment销毁的观察者的安装 3. 通知InternalLeakCanary,watcher已经安装完成
由于Activity、Fragment的销毁不会立刻发生,所以我们先看看第3点中,InternalLeakCanary
做了什么工作。InternalLeakCanary
的部分声明为object InternalLeakCanary : (Application) -> Unit
,所以它到底实现了什么呢。这其实对应kotlin中Functions.kt文件的Function1接口,P1为Application,R为Unit即void:
/** A function that takes 1 argument. */ public interface Function1<in P1, out R> : Function<R> { /** Invokes the function with the specified argument. */ public operator fun invoke(p1: P1): R } 所以接着看InternalLeakCanary.invoke方法即可,该方法也是完成了一些初始化操作,如下: InternalLeakCanary.kt override fun invoke(application: Application) { this.application = application // 内存泄漏时回调该类的方法 AppWatcher.objectWatcher.addOnObjectRetainedListener(this) val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider) // gcTrigger使用的默认的 val gcTrigger = GcTrigger.Default val configProvider = { LeakCanary.config } // HandlerThread + Handler 来处理后台任务 val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME) handlerThread.start() val backgroundHandler = Handler(handlerThread.looper) heapDumpTrigger = HeapDumpTrigger( application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper, configProvider ) // 自定义的扩展方法来检测App是否可见(处于前台) application.registerVisibilityListener { applicationVisible -> this.applicationVisible = applicationVisible heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible) } // 动态添加Shortcut addDynamicShortcut(application) disableDumpHeapInInstrumentationTests() }
其实上面这些初始化的变量,大部分我们都不会在检测内存泄漏时遇到,这些变量大部分都与dump heap有关。
这里展开说说其中的“自定义的扩展方法来检测App是否可见(处于前台)”这里面的实现原理,这个需求在日常开发中也会用到,也是通过向Application注册Activity生命周期回调,通过计算start-stop的Activity个数来实现的:
leakcanary-android-core/src/main/java/leakcanary/internal/VisibilityTracker.kt
internal class VisibilityTracker( private val listener: (Boolean) -> Unit ) : Application.ActivityLifecycleCallbacks by noOpDelegate() { private var startedActivityCount = 0 /** * Visible activities are any activity started but not stopped yet. An activity can be paused * yet visible: this will happen when another activity shows on top with a transparent background * and the activity behind won't get touch inputs but still need to render / animate. */ private var hasVisibleActivities: Boolean = false override fun onActivityStarted(activity: Activity) { startedActivityCount++ if (!hasVisibleActivities && startedActivityCount == 1) { hasVisibleActivities = true listener.invoke(true) } } override fun onActivityStopped(activity: Activity) { // This could happen if the callbacks were registered after some activities were already // started. In that case we effectively considers those past activities as not visible. if (startedActivityCount > 0) { startedActivityCount-- } if (hasVisibleActivities && startedActivityCount == 0 && !activity.isChangingConfigurations) { hasVisibleActivities = false listener.invoke(false) } } } internal fun Application.registerVisibilityListener(listener: (Boolean) -> Unit) { registerActivityLifecycleCallbacks(VisibilityTracker(listener)) }
上面就是LeakCanary的初始化代码了,接下来说说Activity与Fragment是如何检测内存泄漏的。
如何观察Activity、Fragment
在上一节LeakCanary2初始化中,我们还没有讲解下面两行代码:
ActivityDestroyWatcher.install(application, objectWatcher, configProvider) FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
这两行代码都是通过向Application注册registerActivityLifecycleCallbacks
从而获取每个启动的Activity,然后
- 对于Activity而言,直接观察Activity即可
- 对于Fragment而言,由于Fragment需要依附于Activity,且需要从Activity中获取FragmentManager,然后通过其
registerFragmentLifecycleCallbacks
方法观察Fragment
上面两行代码就是这个实现逻辑。先看看Activity的观察逻辑:
leakcanary-object-watcher-android/src/main/java/leakcanary/internal/ActivityDestroyWatcher.kt
internal class ActivityDestroyWatcher private constructor( private val objectWatcher: ObjectWatcher, private val configProvider: () -> Config ) { private val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks by noOpDelegate() { // noOpDelegate()是一个动态代理的实现,不过里面没有写任何逻辑 所以是no op // 此处相当于一个适配器方法 override fun onActivityDestroyed(activity: Activity) { if (configProvider().watchActivities) { objectWatcher.watch(activity) } } } companion object { fun install( application: Application, objectWatcher: ObjectWatcher, configProvider: () -> Config ) { val activityDestroyWatcher = ActivityDestroyWatcher(objectWatcher, configProvider) application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks) } } }
上面的逻辑很简单,就是对于每个Activity,在其onDestroy
方法调用之后,调用objectWatcher.watch
观察这个Activity。该方法的逻辑我们下一节再说。
然后我们看看Fragment里面的观察逻辑,由于Fragment有两种:1. android.app.Fragment
2. support包里面的androidx.fragment.app.Fragment
前者的FragmentLifecycleCallbacks有API Level限制,限制为O;后者则没有API限制了,但是有androidx限制。所以FragmentDestroyWatcher会判断这两个的是否满足条件,满足条件后才会进行观察。