内存泄漏检测工具LeakCanary源码解析
作者:小宏快跑
前言
LeakCanary
是一个简单方便的内存泄漏检测工具,它是由大名鼎鼎的Square公司出品并开源的出来的。目前大部分APP在开发阶段都会接入此工具用来检测内存泄漏问题。它让我们开发者可以在开发阶段就发现一些没有注意到或者不规范的代码导致的内存泄漏,从而就避免了因内存泄漏而最终导致OOM的问题。
使用
LeakCanary
的使用非常简单,我们只需要在项目module下的build.gradle文件里dependencies里面加上依赖就行。
dependencies { // debugImplementation because LeakCanary should only run in debug builds. debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.9.1' }
只需要加上依赖就行了,它会在应用启动的时候自动进行初始化。并且由于我们使用的是debugImplementation
,因此它只在debug
包下有效,所以不用担心这个会在release
包下造成性能影响。
源码解析
接下来将通过阅读源码,并从以下几个方面去深入Leakcanary原理,本文将以2.9.1源码为例。
- LeakCanary自动初始化
- LeakCanary初始化做了什么
- LeakCanary默认的4种监听器
- Leakcanary对象泄漏检查
LeakCanary自动初始化
我们集成的时候只需要添加依赖,不需要像其他第三方SDK那样在Application或其他地方手动调用初始化方法,其实它是借助ContentProvider
来实现的,通过源码来看一下MainProcessAppWatcherInstaller
这个类:
internal class MainProcessAppWatcherInstaller : ContentProvider() { override fun onCreate(): Boolean { val application = context!!.applicationContext as Application AppWatcher.manualInstall(application) return true } ... }
如果对app的启动流程有了解的童鞋就清楚,APP启动时,ContentProvider
和Application
的顺序是:
Application.attachBaseContext()
-> contentProvider.onCreate()
-> Application.onCreate()
通过源码来看下,在ActivityThread
类里面handleBindApplication
下:
@UnsupportedAppUsage private void handleBindApplication(AppBindData data) { ... // 这边创建application,并会调用到application的attach()方法,最终调用到attachBaseContext()方法 app = data.info.makeApplication(data.restrictedBackupMode, null); ... // don't bring up providers in restricted mode; they may depend on the // app's custom Application class if (!data.restrictedBackupMode) { if (!ArrayUtils.isEmpty(data.providers)) { //初始化Provider并且调用了Provider的onCreate()方法 installContentProviders(app, data.providers); } } ... try { //调用了Application的onCreate()方法 mInstrumentation.callApplicationOnCreate(app); } catch (Exception e) { if (!mInstrumentation.onException(app, e)) { throw new RuntimeException( "Unable to create application " + app.getClass().getName() + ": " + e.toString(), e); } } ... }
这样ContentProvider会在Application.onCreate()前初始化,就会调用到了LeakCanary的初始化方法,实现了自动初始化。
注意: 这样使用ContentProvider的进行初始化的写法虽然方便,方便了开发人员集成使用。但是可能会带来启动耗时的问题,并且无法控制初始化的时机。不过这对于LeakCanary这个工具库来说影响不大,因为这个也只在Debug阶段使用。
如何关闭自动初始化
如果我们想要关闭自动化初始化,自己选择在合适的地方进行初始化的话,可以通过覆盖资源文件里面的值来进行关闭
<?xml version="1.0" encoding="utf-8"?> <resources> <bool name="leak_canary_watcher_auto_install">false</bool> </resources>
并且在合适的位置自行调用 AppWatcher.manualInstall(application)
进行手动初始化。
LeakCanary初始化做了什么
上面我们说到LeakCanary是使用ContentProvider进行初始化的,那么我们就从MainProcessAppWatcherInstaller
开始。
internal class MainProcessAppWatcherInstaller : ContentProvider() { override fun onCreate(): Boolean { val application = context!!.applicationContext as Application //这边调用初始化的方法 AppWatcher.manualInstall(application) return true } ... }
这边会在这个MainProcessAppWatcherInstaller
的onCreate
方法里面调用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 ) } //判断设置的延迟时间是否小于0,这个时间是用来延迟检测被观察的对象是否已被释放, //也就是说如果到了这个时间,对象仍没有被释放,那么可能出现了内存泄漏 check(retainedDelayMillis >= 0) { "retainedDelayMillis $retainedDelayMillis must be at least 0 ms" } this.retainedDelayMillis = retainedDelayMillis if (application.isDebuggableBuild) { LogcatSharkLog.install() } //初始化 InternalLeakCanary,并调用了InternalLeakCanary.invoke()方法, //这个类是用来检查判断对象是否被回收 LeakCanaryDelegate.loadLeakCanary(application) //开启监听器,也就是appDefaultWatchers(application)方法里面的 watchersToInstall.forEach { it.install() } // Only install after we're fully done with init. installCause = RuntimeException("manualInstall() first called here") }
在manualInstall
方法里面先做一些判断校验合法,并执行了LeakCanaryDelegate.loadLeakCanary
,这里面会调用内部的invoke()
方法,对LeakCanary的检查判断泄漏的一些类进行初始化。接下来会对watchersToInstall
列表里面的四种观察类型的生命周期监视器调用install()
方法,开启监听。
再来看下watchersToInstall
,默认情况下使用的是 appDefaultWatchers(application)
返回的list集合。
fun appDefaultWatchers( application: Application, reachabilityWatcher: ReachabilityWatcher = objectWatcher ): List<InstallableWatcher> { return listOf( ActivityWatcher(application, reachabilityWatcher), FragmentAndViewModelWatcher(application, reachabilityWatcher), RootViewWatcher(reachabilityWatcher), ServiceWatcher(reachabilityWatcher) ) }
这边会创建ReachabilityWatcher
,也就是objectWatcher
,这边看下他的初始化:
object AppWatcher { ... /** * The [ObjectWatcher] used by AppWatcher to detect retained objects. * Only set when [isInstalled] is true. */ val objectWatcher = ObjectWatcher( clock = { SystemClock.uptimeMillis() }, checkRetainedExecutor = { check(isInstalled) { "AppWatcher not installed" } mainHandler.postDelayed(it, retainedDelayMillis) }, isEnabled = { true } ) ... }
这边重点看下checkRetainedExecutor
这个入参,它是一个Executor
,执行run时,就会调用mainHandler.postDelayed
,后面在对对象回收检查时,会调用run方法,通过postDelayed延时去检查。
接着上面的appDefaultWatchers
方法,里面会创建四种类型的生命周期监听器,分别是Activity、Fragment、RootView和Service。下面将对这4者的源码进行分析
ActivityWatcher
class ActivityWatcher( private val application: Application, private val reachabilityWatcher: ReachabilityWatcher ) : InstallableWatcher { //回调,这个是向application.registerActivityLifecycleCallbacks注册activity生命周期回调 //这边通过监听Activity的onDestroyed来观察它的回收状态 private val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks by noOpDelegate() { override fun onActivityDestroyed(activity: Activity) { //一旦activity进入到destory,则开始通过onActivityDestroyed观察它的回收状态 reachabilityWatcher.expectWeaklyReachable( activity, "${activity::class.java.name} received Activity#onDestroy() callback" ) } } override fun install() { application.registerActivityLifecycleCallbacks(lifecycleCallbacks) } override fun uninstall() { application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks) } }
Activity的监听比较简单,只需要注册一个回调即可,因为Android本身有提供全局监听Activity的生命周期的回调。只需要在onActivityDestroyed
回调里面对对象回收情况进行观察,因为一旦进入到onActivityDestroyed
就表明Activity退出了,此时可以开始观察Activity是否回收。
这边有一个知识点,就是上面的Application.ActivityLifecycleCallbacks by noOpDelegate()
,这边使用到了委托noOpDelegate()
,这个的作用是使得接口类可以只实现自己想要的方法,而不需要全部实现。
FragmentAndViewModelWatcher
Fragment的监听就比较麻烦了,需要对不同包的Fragment做适配处理,分别是:
android.app.Fragment
android.support.v4.app.Fragment
androidx.fragment.app.Fragment
private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run { val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>() //添加android.app.Fragment监听,创建AndroidOFragmentDestroyWatcher,并加入到集合中 if (SDK_INT >= O) { fragmentDestroyWatchers.add( AndroidOFragmentDestroyWatcher(reachabilityWatcher) ) } //添加 androidx.fragment.app.Fragment监听, //通过反射的方式创建AndroidXFragmentDestroyWatcher,并加入到集合中 getWatcherIfAvailable( ANDROIDX_FRAGMENT_CLASS_NAME, ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME, reachabilityWatcher )?.let { fragmentDestroyWatchers.add(it) } //添加android.support.v4.app.Fragment监听 //通过反射的方式 创建 AndroidSupportFragmentDestroyWatcher,并加入到集合中 getWatcherIfAvailable( ANDROID_SUPPORT_FRAGMENT_CLASS_NAME, ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME, reachabilityWatcher )?.let { fragmentDestroyWatchers.add(it) } fragmentDestroyWatchers }
这边会对不同包下的Fragment创建Watcher,并把它们加入到一个List中,接下来就是当外部调用install()
时,就会调用到application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
,下面来看下这个lifecycleCallbacks
private val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks by noOpDelegate() { override fun onActivityCreated( activity: Activity, savedInstanceState: Bundle? ) { for (watcher in fragmentDestroyWatchers) { watcher(activity) } } }
可以看到,这边是在onActivityCreated
的时候调用各个Watcher的invoke()
方法。然后在invoke()
方法里面通过activity.fragmentManager
或activity.supportFragmentManager
调用registerFragmentLifecycleCallbacks
去注册Fragment的生命周期回调。
这边就只看下AndroidX下的Fragment生命周期监听
internal class AndroidXFragmentDestroyWatcher( private val reachabilityWatcher: ReachabilityWatcher ) : (Activity) -> Unit { private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() { override fun onFragmentCreated( fm: FragmentManager, fragment: Fragment, savedInstanceState: Bundle? ) { ViewModelClearedWatcher.install(fragment, reachabilityWatcher) } override fun onFragmentViewDestroyed( fm: FragmentManager, fragment: Fragment ) { val view = fragment.view if (view != null) { reachabilityWatcher.expectWeaklyReachable( view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " + "(references to its views should be cleared to prevent leaks)" ) } } override fun onFragmentDestroyed( fm: FragmentManager, fragment: Fragment ) { reachabilityWatcher.expectWeaklyReachable( fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback" ) } } override fun invoke(activity: Activity) { if (activity is FragmentActivity) { val supportFragmentManager = activity.supportFragmentManager supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true) ViewModelClearedWatcher.install(activity, reachabilityWatcher) } } }
在AndroidOFragmentDestroyWatcher
类里面,会创建一个Fragment的生命周期回调,也就是FragmentManager.FragmentLifecycleCallbacks
,并在onFragmentViewDestroyed
和onFragmentDestroyed
这两个回调方法里面进行对象回收检查。
在该类里面,我们还能看到install了ViewModel,对ViewModel的生命周期进行观察。这边会区分是Activity的ViewModel还是Fragment的ViewModel。
- Activity:在invoke()方法里面调用
ViewModelClearedWatcher.install
,并传入Activity的ViewModelStoreOwner
。 - Fragment:在
onFragmentCreated
回调里面调用了ViewModelClearedWatcher.install
,传入Fragment的ViewModelStoreOwner
。
那么在ViewModelClearedWatcher
里面,又是怎么对viewModel的生命周期进行观察的呢?这边就不贴代码了:
- 在install方法里面创建一个ViewModel,也就是
ViewModelClearedWatcher
,加入到对应传入的ViewModelStoreOwner
下的ViewModelStore
里的集合里面。 - 然后在
ViewModelClearedWatcher
的onCleared()
方法里面通过反射的方式取出ViewModelStore
里面的集合,进行迭代遍历,对集合里面的所有ViewModel开启对象回收检查。
RootViewWatcher
private val listener = OnRootViewAddedListener { rootView -> val trackDetached = when(rootView.windowType) { PHONE_WINDOW -> { when (rootView.phoneWindow?.callback?.wrappedCallback) { // Activities are already tracked by ActivityWatcher // activity不需要重复注册 is Activity -> false //监听dialog,这边还需要判断下资源文件里面的配置信息是否是打开的 is Dialog -> { // Use app context resources to avoid NotFoundException // https://github.com/square/leakcanary/issues/2137 val resources = rootView.context.applicationContext.resources resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs) } // Probably a DreamService // 其他情况都打开 else -> true } } // Android widgets keep detached popup window instances around. POPUP_WINDOW -> false TOOLTIP, TOAST, UNKNOWN -> true } if (trackDetached) { rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener { val watchDetachedView = Runnable { reachabilityWatcher.expectWeaklyReachable( rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback" ) } override fun onViewAttachedToWindow(v: View) { //当显示时移除内存监听器 mainHandler.removeCallbacks(watchDetachedView) } override fun onViewDetachedFromWindow(v: View) { //当view从界面上移除时时,就执行监听,通过Handler.post() mainHandler.post(watchDetachedView) } }) } } override fun install() { //使用三方库的Curtains来对view进行状态的监听 Curtains.onRootViewsChangedListeners += listener }
RootView这边只要监听一些dialog、tooltip、toast等,代码不长,简单说下
- 创建一个
OnRootViewAddedListener
监听,使用的是第三方库Curtains,在回调里面拿到rootView; - 对这个rootView注册一个
OnAttachStateChangeListener
监听,在onViewAttachedToWindow
方法里面移除对象回收检查。在onViewDetachedFromWindow
里面开启对象回收检查;
ServiceWatcher
//存放即将stop的service private val servicesToBeDestroyed = WeakHashMap<IBinder, WeakReference<Service>>() private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread") } //通过反射的方式调用ActivityThread里面的currentActivityThread方法,也就是取到ActivityThread对象 private val activityThreadInstance by lazy { activityThreadClass.getDeclaredMethod("currentActivityThread").invoke(null)!! } //通过反射方式从ActivityThread取出mServices private val activityThreadServices by lazy { val mServicesField = activityThreadClass.getDeclaredField("mServices").apply { isAccessible = true } @Suppress("UNCHECKED_CAST") mServicesField[activityThreadInstance] as Map<IBinder, Service> } override fun install() { //做一些检查 checkMainThread() check(uninstallActivityThreadHandlerCallback == null) { "ServiceWatcher already installed" } check(uninstallActivityManager == null) { "ServiceWatcher already installed" } try { //hook ActivityThread里面的mH,也就是Handler,并替换里面的mCallBack swapActivityThreadHandlerCallback { mCallback -> //还原hook的mCallback,uninstall()时使用 uninstallActivityThreadHandlerCallback = { swapActivityThreadHandlerCallback { mCallback } } //返回需要替换的mCallback,在这里面监听handler消息, Handler.Callback { msg -> // https://github.com/square/leakcanary/issues/2114 // On some Motorola devices (Moto E5 and G6), the msg.obj returns an ActivityClientRecord // instead of an IBinder. This crashes on a ClassCastException. Adding a type check // here to prevent the crash. if (msg.obj !is IBinder) { return@Callback false } //如果收到了Service的Stop的消息,表示service要结束了, //此时调用onServicePreDestroy,将当前service加入到servicesToBeDestroyed集合里面去 if (msg.what == STOP_SERVICE) { val key = msg.obj as IBinder activityThreadServices[key]?.let { onServicePreDestroy(key, it) } } //继续处理消息 mCallback?.handleMessage(msg) ?: false } } //这边hook ActivityManagerService swapActivityManager { activityManagerInterface, activityManagerInstance -> uninstallActivityManager = { swapActivityManager { _, _ -> activityManagerInstance } } //使用动态代理的方式 Proxy.newProxyInstance( activityManagerInterface.classLoader, arrayOf(activityManagerInterface) ) { _, method, args -> //如果执行serviceDoneExecuting方法时,则调用onServiceDestroyed() //方法来观察service的内存回收情况 if (METHOD_SERVICE_DONE_EXECUTING == method.name) { val token = args!![0] as IBinder if (servicesToBeDestroyed.containsKey(token)) { onServiceDestroyed(token) } } try { if (args == null) { method.invoke(activityManagerInstance) } else { method.invoke(activityManagerInstance, *args) } } catch (invocationException: InvocationTargetException) { throw invocationException.targetException } } } } catch (ignored: Throwable) { SharkLog.d(ignored) { "Could not watch destroyed services" } } }
Service的结束监听是通过hool的方式进行的,步骤如下:
- 通过反射的方取出
ActivityThread
里面的mH
,也就是Handler
。然后再取出该Handler
里面的mCallback
,并通过反射,使用自己创建的callBack去替换,然后在Callback里面监听Handle消息,如果收到的消息是msg.what == STOP_SERVICE
,则表示Service即将结束,此时将该service加入到待观察集合里面去。 - 接下来通过hook的方式,hook住
ActivityManagerService
。使用动态代理,如果执行了serviceDoneExecuting
方法,则表示service结束,此时从待观察集合里面取出当前这个service并从待观察列表里面移除,然后观察这个service对象的回收情况。
Leakcanary对象泄漏检查
在被观察类型的生命周期的结束时,会调用到reachabilityWatcher.expectWeaklyReachable
这个方法
我们跟进去,并找到实现类,来到ObjectWatcher
里面,找到expectWeaklyReachable
方法。
ObjectWatcher: @Synchronized override fun expectWeaklyReachable( watchedObject: Any, description: String ) { //这边在前面初始化的时候默认传进来是true,因此会继续往下执行下面的代码 if (!isEnabled()) { return } //这边会先先处理下,将被回收的对象从watchedObjects这个待观察的集合里面移除 removeWeaklyReachableObjects() //创建一个随机的UUID,用来当做待观察对象的key,方便从watchedObjects这个map取值 val key = UUID.randomUUID() .toString() // 获取当前的时间,自系统开机到现在的一个时间 val watchUptimeMillis = clock.uptimeMillis() //创建弱引用对象,也就是WeakReference 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 //执行延时操作,默认延时5秒,去检查对象是否回收 checkRetainedExecutor.execute { moveToRetained(key) } }
在这个方法里面会先执行一次removeWeaklyReachableObjects()
,将已被回收的对象从待观察的集合watchedObjects
里面移除,然后创建弱引用对象,接着开启延时检查,默认等待5秒,使用的是mainHandler.postDelayed(it, retainedDelayMillis)
去做延时的。
先看下removeWeaklyReachableObjects()
这个方法:
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? //通过迭代遍历的方式,判断queue里面时候有被回收的对象 do { ref = queue.poll() as KeyedWeakReference? if (ref != null) { //如果对象被回收了 ,则讲弱引用从待观察的map里面移除 watchedObjects.remove(ref.key) } } while (ref != null) }
这个方法里面代码很少,这里面做的工作就是判断queue里面有没有被回收的对象,如果有则将改对象的弱引用从待观察的集合里面移除。
然后在来看下延时处理里面做了什么事:
@Synchronized private fun moveToRetained(key: String) { removeWeaklyReachableObjects() val retainedRef = watchedObjects[key] if (retainedRef != null) { retainedRef.retainedUptimeMillis = clock.uptimeMillis() onObjectRetainedListeners.forEach { it.onObjectRetained() } } }
可以看到里面一开始又调用了一次removeWeaklyReachableObjects()
方法。这边是第二次调用了,尝试移除这5秒内已经被回收的对象。如果此时对象仍没有被回收,也就是还在待观察集合里面,那么就开始进入到回调里面去,也就是OnObjectRetainedListener.onObjectRetained()
里面去,这边这个onObjectRetainedListeners
列表里面目前就只有一个, 它是在InternalLeakCanary
的invoke
里面调用的。
AppWatcher -> manualIstall() -> LeakCanaryDelegate.loadLeakCanary(application) -> InternalLeakCanary -> invoke()
internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener { ... override fun invoke(application: Application) { _application = application checkRunningInDebuggableBuild() //添加监听,因为InternalLeakCanary实现了OnObjectRetainedListener接口,因此直接传this AppWatcher.objectWatcher.addOnObjectRetainedListener(this) ... } ... }
因此最终是回调到InternalLeakCanary
的onObjectRetained()
方法里面,然后在调用到scheduleRetainedObjectCheck()
。最终进入到heapDumpTrigger.scheduleRetainedObjectCheck()
方法里面
internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener { ... //回调到这个方法,然后又调用了scheduleRetainedObjectCheck override fun onObjectRetained() = scheduleRetainedObjectCheck() fun scheduleRetainedObjectCheck() { //先判断heapDumpTrigger是否有初始化了, if (this::heapDumpTrigger.isInitialized) { heapDumpTrigger.scheduleRetainedObjectCheck() } } ... }
接下来在进入到heapDumpTrigger.scheduleRetainedObjectCheck()
方法里面
fun scheduleRetainedObjectCheck( delayMillis: Long = 0L ) { val checkCurrentlyScheduledAt = checkScheduledAt //如果之前的子线程任务来没开始执行则返回,也就是就是下面的backgroundHandler.postDelayed if (checkCurrentlyScheduledAt > 0) { return } checkScheduledAt = SystemClock.uptimeMillis() + delayMillis //让子线程延时去delayMillis执行,通过上面的调用链进入此方法则delayMillis为0,因此会马上执行 backgroundHandler.postDelayed({ //子线程任务开始执行,就将checkScheduledAt设置为0,以便下一次出现内存泄漏时能还能进来 checkScheduledAt = 0 checkRetainedObjects() }, delayMillis) }
接下来在进入到checkRetainedObjects()
这个方法里面去。由于上面是通过子线程Handler去post,因此该方法是运行在子线程里面
private fun checkRetainedObjects() { ... //看下此时待观察的集合里面还有多少个没有被分析的弱引用对象 //也就是retainedUptimeMillis != -1L 的对象的个数 //并且在调用retainedObjectCount.get()的时候,还会再调用一次removeWeaklyReachableObjects(),尝试再次移除一遍 var retainedReferenceCount = objectWatcher.retainedObjectCount //如果此时还有泄漏对象,则调用gc,并重新再获取一次数量 if (retainedReferenceCount > 0) { gcTrigger.runGc() retainedReferenceCount = objectWatcher.retainedObjectCount } //这边判断应用是出于前台还是后台, //如果是在前台,则个数达到5个时才dump,如果应用在后台则只要有一个都会进行dump if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return val now = SystemClock.uptimeMillis() val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis //如果这个时间与上次dump的时间过短,则会重新调用scheduleRetainedObjectCheck, //并延迟执行,这个延迟的时间 = 60s - 时间差,避免两次dump的时间过短,影响使用 if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) { onRetainInstanceListener.onEvent(DumpHappenedRecently) showRetainedCountNotification( objectCount = retainedReferenceCount, contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait) ) scheduleRetainedObjectCheck( delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis ) return } dismissRetainedCountNotification() val visibility = if (applicationVisible) "visible" else "not visible" //这边开始进行dump dumpHeap( retainedReferenceCount = retainedReferenceCount, retry = true, reason = "$retainedReferenceCount retained objects, app is $visibility" ) }
在这个checkRetainedObjects()
方法里面执行的步骤:
- 先获取待观察集合里面被还没有被分析的疑似泄漏的弱引用对象的个数,通过
retainedUptimeMillis != -1L
这个判断,因为当对象被通过dump分析之后,会将retainedUptimeMillis这个时间改为-1。而且在objectWatcher.retainedObjectCount
这个get的方法里面还会执行一次removeWeaklyReachableObjects()
,尝试再次移除被回收的弱引用对象。 - 如果此时还是有泄漏对象,也就是retainedReferenceCount > 0时,则会调用一次
gcTrigger.runGc()
进行一次GC,然后在重新获取下个数。 - 接着判断应用是在前台还是后台,如果应用此时是在前台,则只有当个数达到5个时才进行dump。如果是在后台,则只要有一个就会进行dump。
- 接下来在判断下当前时间距离上一次dump的时间,如果时间过短(小于60S),则不进行dump,而是重新调用
scheduleRetainedObjectCheck
并延时执行,延迟时间为:60秒 - 时间差。并且这边会返回,不再执行之后的dump - 最后执行dump,将内存泄漏的案发现场保存下来,并解析。
接下来来看下dumpHeap这个方法,这边就不分析dump的源码了,主要看下dump后执行操作。
private fun dumpHeap( retainedReferenceCount: Int, retry: Boolean, reason: String ) { ... val heapDumpUptimeMillis = SystemClock.uptimeMillis() ... //执行dump,并计算dump的时间 durationMillis = measureDurationMillis { //执行dump,这边就不分析了,感兴趣可以自行点进去看下 configProvider().heapDumper.dumpHeap(heapDumpFile) } ... //dump之后处理以分析的对象 objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis) }
这边注意下:objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
这个方法,上面我们说到当对象被通过dump分析之后,会将retainedUptimeMillis这个时间改为-1。现在我们来看下这个方法里面的源码:
/** * Clears all [KeyedWeakReference] that were created before [heapDumpUptimeMillis] (based on * [clock] [Clock.uptimeMillis]) */ @Synchronized fun clearObjectsWatchedBefore(heapDumpUptimeMillis: Long) { //从待观察集合里面过滤出时间小于等于当前dump时间的弱引用 val weakRefsToRemove = watchedObjects.filter { it.value.watchUptimeMillis <= heapDumpUptimeMillis } //遍历执行clear,将时间设置为-1,这个在下次触发执行checkRetainedObjects,里面不会重复判断 weakRefsToRemove.values.forEach { it.clear() } //然后从待观察列表里面移除 watchedObjects.keys.removeAll(weakRefsToRemove.keys) }
这个方法里面会过滤出已经被dump过的弱引用对象,因为当它的watchUptimeMillis <= heapDumpUptimeMillis时,dump会将内存中当前时间点之前的疑似泄漏的对象都列举出来,所以只要是在当前dump时间点之前加入的都可以认为是已经dump了。
然后接下调用过滤出来的弱引用对象的claer()
方法,将watchUptimeMillis
时间设置为-1,并且从待观察集合里面移除。
总结
经过了上面的源码分析,接下来来总结下:
- 使用ContentProvider进行自动初始化,在ContentProvider的onCreate()方法里面调用
AppWatcher.manualInstall(application)
进行LeakCanary的初始化; - 在
AppWatcher.manualInstall(application)
里面先做了一些合法性校验,然后初始化初始化了InternalLeakCanary
和构建了4种生命周期监听器Watcher,分别是Activity、Fragment、Service和RootView。 - 在构建4种Watcher时,顺便创建了
objectWatcher
,用于之后延时检查对象是否被回收。当被被观察的类型在生命周期进入结束时,会调用到objectWatcher.expectWeaklyReachable()
方法里面。 - 在
objectWatcher.expectWeaklyReachable()
方法里面会先将被回收的对象移除待观察集合,然后创建一个弱引用放入到待观察列表里面,最后在开启延时检查对象是否被回收,默认延时5秒。 - 延时计时到了之后会调用
objectWatcher.moveToRetained
方法,在该方法内会再次尝试移除这5秒内已经被回收的对象,然后调用InternalLeakCanary.onObjectRetained()
方法,跟着调用栈进入到heapDumpTrigger.scheduleRetainedObjectCheck()
方法,最终到了checkRetainedObjects()
这个方法里面。 - 在
checkRetainedObjects()
这个方法里面,会先判断待观察结合里面是否还有疑似泄漏的弱引用对象,如果有则尝试进行一次GC操作。然后判断应用是在前台还是后台,如果实在前台则当疑似泄漏的弱引用对象数量达到5个时才进行dump。如果是在后台,则只要有一个就会进行dump。 - 然后判断当前时间是否距离上次dump的时间小于60秒,如果小于60秒则重新进入到
heapDumpTrigger.scheduleRetainedObjectCheck()
方法,并延时开启任务,延时时间为:60秒 - 两次dump的时间差。 - 最后进行dump,和dump文件的数据分析。dump完成后,会将待观察列表里面的已被分析过的弱引用对象的
watchUptimeMillis
时间设置为-1,并移除。
以上就是LeakCanary 2.9.1的源码解析,更多关于内存泄漏检测LeakCanary的资料请关注脚本之家其它相关文章!