內(nèi)存泄漏檢測工具LeakCanary源碼解析
前言
LeakCanary
是一個簡單方便的內(nèi)存泄漏檢測工具,它是由大名鼎鼎的Square公司出品并開源的出來的。目前大部分APP在開發(fā)階段都會接入此工具用來檢測內(nèi)存泄漏問題。它讓我們開發(fā)者可以在開發(fā)階段就發(fā)現(xiàn)一些沒有注意到或者不規(guī)范的代碼導(dǎo)致的內(nèi)存泄漏,從而就避免了因內(nèi)存泄漏而最終導(dǎo)致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' }
只需要加上依賴就行了,它會在應(yīng)用啟動的時候自動進行初始化。并且由于我們使用的是debugImplementation
,因此它只在debug
包下有效,所以不用擔(dān)心這個會在release
包下造成性能影響。
源碼解析
接下來將通過閱讀源碼,并從以下幾個方面去深入Leakcanary原理,本文將以2.9.1源碼為例。
- LeakCanary自動初始化
- LeakCanary初始化做了什么
- LeakCanary默認的4種監(jiān)聽器
- Leakcanary對象泄漏檢查
LeakCanary自動初始化
我們集成的時候只需要添加依賴,不需要像其他第三方SDK那樣在Application或其他地方手動調(diào)用初始化方法,其實它是借助ContentProvider
來實現(xiàn)的,通過源碼來看一下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) { ... // 這邊創(chuàng)建application,并會調(diào)用到application的attach()方法,最終調(diào)用到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并且調(diào)用了Provider的onCreate()方法 installContentProviders(app, data.providers); } } ... try { //調(diào)用了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()前初始化,就會調(diào)用到了LeakCanary的初始化方法,實現(xiàn)了自動初始化。
注意: 這樣使用ContentProvider的進行初始化的寫法雖然方便,方便了開發(fā)人員集成使用。但是可能會帶來啟動耗時的問題,并且無法控制初始化的時機。不過這對于LeakCanary這個工具庫來說影響不大,因為這個也只在Debug階段使用。
如何關(guān)閉自動初始化
如果我們想要關(guān)閉自動化初始化,自己選擇在合適的地方進行初始化的話,可以通過覆蓋資源文件里面的值來進行關(guān)閉
<?xml version="1.0" encoding="utf-8"?> <resources> <bool name="leak_canary_watcher_auto_install">false</bool> </resources>
并且在合適的位置自行調(diào)用 AppWatcher.manualInstall(application)
進行手動初始化。
LeakCanary初始化做了什么
上面我們說到LeakCanary是使用ContentProvider進行初始化的,那么我們就從MainProcessAppWatcherInstaller
開始。
internal class MainProcessAppWatcherInstaller : ContentProvider() { override fun onCreate(): Boolean { val application = context!!.applicationContext as Application //這邊調(diào)用初始化的方法 AppWatcher.manualInstall(application) return true } ... }
這邊會在這個MainProcessAppWatcherInstaller
的onCreate
方法里面調(diào)用AppWatcher.manualInstall(application)
,進行初始化。
@JvmOverloads fun manualInstall( application: Application, retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5), watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application) ) { //先判斷是否是在主線程,如果不是在主線程則拋出異常 checkMainThread() //判斷是否已經(jīng)初始化了,如果已經(jīng)初始化了則拋出異常,避免重復(fù)初始化 if (isInstalled) { throw IllegalStateException( "AppWatcher already installed, see exception cause for prior install call", installCause ) } //判斷設(shè)置的延遲時間是否小于0,這個時間是用來延遲檢測被觀察的對象是否已被釋放, //也就是說如果到了這個時間,對象仍沒有被釋放,那么可能出現(xiàn)了內(nèi)存泄漏 check(retainedDelayMillis >= 0) { "retainedDelayMillis $retainedDelayMillis must be at least 0 ms" } this.retainedDelayMillis = retainedDelayMillis if (application.isDebuggableBuild) { LogcatSharkLog.install() } //初始化 InternalLeakCanary,并調(diào)用了InternalLeakCanary.invoke()方法, //這個類是用來檢查判斷對象是否被回收 LeakCanaryDelegate.loadLeakCanary(application) //開啟監(jiān)聽器,也就是appDefaultWatchers(application)方法里面的 watchersToInstall.forEach { it.install() } // Only install after we're fully done with init. installCause = RuntimeException("manualInstall() first called here") }
在manualInstall
方法里面先做一些判斷校驗合法,并執(zhí)行了LeakCanaryDelegate.loadLeakCanary
,這里面會調(diào)用內(nèi)部的invoke()
方法,對LeakCanary的檢查判斷泄漏的一些類進行初始化。接下來會對watchersToInstall
列表里面的四種觀察類型的生命周期監(jiān)視器調(diào)用install()
方法,開啟監(jiān)聽。
再來看下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) ) }
這邊會創(chuàng)建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
這個入?yún)?,它是一個Executor
,執(zhí)行run時,就會調(diào)用mainHandler.postDelayed
,后面在對對象回收檢查時,會調(diào)用run方法,通過postDelayed延時去檢查。
接著上面的appDefaultWatchers
方法,里面會創(chuàng)建四種類型的生命周期監(jiān)聽器,分別是Activity、Fragment、RootView和Service。下面將對這4者的源碼進行分析
ActivityWatcher
class ActivityWatcher( private val application: Application, private val reachabilityWatcher: ReachabilityWatcher ) : InstallableWatcher { //回調(diào),這個是向application.registerActivityLifecycleCallbacks注冊activity生命周期回調(diào) //這邊通過監(jiān)聽Activity的onDestroyed來觀察它的回收狀態(tài) private val lifecycleCallbacks = object : Application.ActivityLifecycleCallbacks by noOpDelegate() { override fun onActivityDestroyed(activity: Activity) { //一旦activity進入到destory,則開始通過onActivityDestroyed觀察它的回收狀態(tài) reachabilityWatcher.expectWeaklyReachable( activity, "${activity::class.java.name} received Activity#onDestroy() callback" ) } } override fun install() { application.registerActivityLifecycleCallbacks(lifecycleCallbacks) } override fun uninstall() { application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks) } }
Activity的監(jiān)聽比較簡單,只需要注冊一個回調(diào)即可,因為Android本身有提供全局監(jiān)聽Activity的生命周期的回調(diào)。只需要在onActivityDestroyed
回調(diào)里面對對象回收情況進行觀察,因為一旦進入到onActivityDestroyed
就表明Activity退出了,此時可以開始觀察Activity是否回收。
這邊有一個知識點,就是上面的Application.ActivityLifecycleCallbacks by noOpDelegate()
,這邊使用到了委托noOpDelegate()
,這個的作用是使得接口類可以只實現(xiàn)自己想要的方法,而不需要全部實現(xiàn)。
FragmentAndViewModelWatcher
Fragment的監(jiān)聽就比較麻煩了,需要對不同包的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監(jiān)聽,創(chuàng)建AndroidOFragmentDestroyWatcher,并加入到集合中 if (SDK_INT >= O) { fragmentDestroyWatchers.add( AndroidOFragmentDestroyWatcher(reachabilityWatcher) ) } //添加 androidx.fragment.app.Fragment監(jiān)聽, //通過反射的方式創(chuàng)建AndroidXFragmentDestroyWatcher,并加入到集合中 getWatcherIfAvailable( ANDROIDX_FRAGMENT_CLASS_NAME, ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME, reachabilityWatcher )?.let { fragmentDestroyWatchers.add(it) } //添加android.support.v4.app.Fragment監(jiān)聽 //通過反射的方式 創(chuàng)建 AndroidSupportFragmentDestroyWatcher,并加入到集合中 getWatcherIfAvailable( ANDROID_SUPPORT_FRAGMENT_CLASS_NAME, ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME, reachabilityWatcher )?.let { fragmentDestroyWatchers.add(it) } fragmentDestroyWatchers }
這邊會對不同包下的Fragment創(chuàng)建Watcher,并把它們加入到一個List中,接下來就是當(dāng)外部調(diào)用install()
時,就會調(diào)用到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
的時候調(diào)用各個Watcher的invoke()
方法。然后在invoke()
方法里面通過activity.fragmentManager
或activity.supportFragmentManager
調(diào)用registerFragmentLifecycleCallbacks
去注冊Fragment的生命周期回調(diào)。
這邊就只看下AndroidX下的Fragment生命周期監(jiān)聽
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
類里面,會創(chuàng)建一個Fragment的生命周期回調(diào),也就是FragmentManager.FragmentLifecycleCallbacks
,并在onFragmentViewDestroyed
和onFragmentDestroyed
這兩個回調(diào)方法里面進行對象回收檢查。
在該類里面,我們還能看到install了ViewModel,對ViewModel的生命周期進行觀察。這邊會區(qū)分是Activity的ViewModel還是Fragment的ViewModel。
- Activity:在invoke()方法里面調(diào)用
ViewModelClearedWatcher.install
,并傳入Activity的ViewModelStoreOwner
。 - Fragment:在
onFragmentCreated
回調(diào)里面調(diào)用了ViewModelClearedWatcher.install
,傳入Fragment的ViewModelStoreOwner
。
那么在ViewModelClearedWatcher
里面,又是怎么對viewModel的生命周期進行觀察的呢?這邊就不貼代碼了:
- 在install方法里面創(chuàng)建一個ViewModel,也就是
ViewModelClearedWatcher
,加入到對應(yīng)傳入的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不需要重復(fù)注冊 is Activity -> false //監(jiān)聽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) { //當(dāng)顯示時移除內(nèi)存監(jiān)聽器 mainHandler.removeCallbacks(watchDetachedView) } override fun onViewDetachedFromWindow(v: View) { //當(dāng)view從界面上移除時時,就執(zhí)行監(jiān)聽,通過Handler.post() mainHandler.post(watchDetachedView) } }) } } override fun install() { //使用三方庫的Curtains來對view進行狀態(tài)的監(jiān)聽 Curtains.onRootViewsChangedListeners += listener }
RootView這邊只要監(jiān)聽一些dialog、tooltip、toast等,代碼不長,簡單說下
- 創(chuàng)建一個
OnRootViewAddedListener
監(jiān)聽,使用的是第三方庫Curtains,在回調(diào)里面拿到rootView; - 對這個rootView注冊一個
OnAttachStateChangeListener
監(jiān)聽,在onViewAttachedToWindow
方法里面移除對象回收檢查。在onViewDetachedFromWindow
里面開啟對象回收檢查;
ServiceWatcher
//存放即將stop的service private val servicesToBeDestroyed = WeakHashMap<IBinder, WeakReference<Service>>() private val activityThreadClass by lazy { Class.forName("android.app.ActivityThread") } //通過反射的方式調(diào)用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 -> //還原h(huán)ook的mCallback,uninstall()時使用 uninstallActivityThreadHandlerCallback = { swapActivityThreadHandlerCallback { mCallback } } //返回需要替換的mCallback,在這里面監(jiān)聽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要結(jié)束了, //此時調(diào)用onServicePreDestroy,將當(dāng)前service加入到servicesToBeDestroyed集合里面去 if (msg.what == STOP_SERVICE) { val key = msg.obj as IBinder activityThreadServices[key]?.let { onServicePreDestroy(key, it) } } //繼續(xù)處理消息 mCallback?.handleMessage(msg) ?: false } } //這邊hook ActivityManagerService swapActivityManager { activityManagerInterface, activityManagerInstance -> uninstallActivityManager = { swapActivityManager { _, _ -> activityManagerInstance } } //使用動態(tài)代理的方式 Proxy.newProxyInstance( activityManagerInterface.classLoader, arrayOf(activityManagerInterface) ) { _, method, args -> //如果執(zhí)行serviceDoneExecuting方法時,則調(diào)用onServiceDestroyed() //方法來觀察service的內(nèi)存回收情況 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的結(jié)束監(jiān)聽是通過hool的方式進行的,步驟如下:
- 通過反射的方取出
ActivityThread
里面的mH
,也就是Handler
。然后再取出該Handler
里面的mCallback
,并通過反射,使用自己創(chuàng)建的callBack去替換,然后在Callback里面監(jiān)聽Handle消息,如果收到的消息是msg.what == STOP_SERVICE
,則表示Service即將結(jié)束,此時將該service加入到待觀察集合里面去。 - 接下來通過hook的方式,hook住
ActivityManagerService
。使用動態(tài)代理,如果執(zhí)行了serviceDoneExecuting
方法,則表示service結(jié)束,此時從待觀察集合里面取出當(dāng)前這個service并從待觀察列表里面移除,然后觀察這個service對象的回收情況。
Leakcanary對象泄漏檢查
在被觀察類型的生命周期的結(jié)束時,會調(diào)用到reachabilityWatcher.expectWeaklyReachable
這個方法
我們跟進去,并找到實現(xiàn)類,來到ObjectWatcher
里面,找到expectWeaklyReachable
方法。
ObjectWatcher: @Synchronized override fun expectWeaklyReachable( watchedObject: Any, description: String ) { //這邊在前面初始化的時候默認傳進來是true,因此會繼續(xù)往下執(zhí)行下面的代碼 if (!isEnabled()) { return } //這邊會先先處理下,將被回收的對象從watchedObjects這個待觀察的集合里面移除 removeWeaklyReachableObjects() //創(chuàng)建一個隨機的UUID,用來當(dāng)做待觀察對象的key,方便從watchedObjects這個map取值 val key = UUID.randomUUID() .toString() // 獲取當(dāng)前的時間,自系統(tǒng)開機到現(xiàn)在的一個時間 val watchUptimeMillis = clock.uptimeMillis() //創(chuàng)建弱引用對象,也就是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 //執(zhí)行延時操作,默認延時5秒,去檢查對象是否回收 checkRetainedExecutor.execute { moveToRetained(key) } }
在這個方法里面會先執(zhí)行一次removeWeaklyReachableObjects()
,將已被回收的對象從待觀察的集合watchedObjects
里面移除,然后創(chuàng)建弱引用對象,接著開啟延時檢查,默認等待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() } } }
可以看到里面一開始又調(diào)用了一次removeWeaklyReachableObjects()
方法。這邊是第二次調(diào)用了,嘗試移除這5秒內(nèi)已經(jīng)被回收的對象。如果此時對象仍沒有被回收,也就是還在待觀察集合里面,那么就開始進入到回調(diào)里面去,也就是OnObjectRetainedListener.onObjectRetained()
里面去,這邊這個onObjectRetainedListeners
列表里面目前就只有一個, 它是在InternalLeakCanary
的invoke
里面調(diào)用的。
AppWatcher -> manualIstall() -> LeakCanaryDelegate.loadLeakCanary(application) -> InternalLeakCanary -> invoke()
internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener { ... override fun invoke(application: Application) { _application = application checkRunningInDebuggableBuild() //添加監(jiān)聽,因為InternalLeakCanary實現(xiàn)了OnObjectRetainedListener接口,因此直接傳this AppWatcher.objectWatcher.addOnObjectRetainedListener(this) ... } ... }
因此最終是回調(diào)到InternalLeakCanary
的onObjectRetained()
方法里面,然后在調(diào)用到scheduleRetainedObjectCheck()
。最終進入到heapDumpTrigger.scheduleRetainedObjectCheck()
方法里面
internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener { ... //回調(diào)到這個方法,然后又調(diào)用了scheduleRetainedObjectCheck override fun onObjectRetained() = scheduleRetainedObjectCheck() fun scheduleRetainedObjectCheck() { //先判斷heapDumpTrigger是否有初始化了, if (this::heapDumpTrigger.isInitialized) { heapDumpTrigger.scheduleRetainedObjectCheck() } } ... }
接下來在進入到heapDumpTrigger.scheduleRetainedObjectCheck()
方法里面
fun scheduleRetainedObjectCheck( delayMillis: Long = 0L ) { val checkCurrentlyScheduledAt = checkScheduledAt //如果之前的子線程任務(wù)來沒開始執(zhí)行則返回,也就是就是下面的backgroundHandler.postDelayed if (checkCurrentlyScheduledAt > 0) { return } checkScheduledAt = SystemClock.uptimeMillis() + delayMillis //讓子線程延時去delayMillis執(zhí)行,通過上面的調(diào)用鏈進入此方法則delayMillis為0,因此會馬上執(zhí)行 backgroundHandler.postDelayed({ //子線程任務(wù)開始執(zhí)行,就將checkScheduledAt設(shè)置為0,以便下一次出現(xiàn)內(nèi)存泄漏時能還能進來 checkScheduledAt = 0 checkRetainedObjects() }, delayMillis) }
接下來在進入到checkRetainedObjects()
這個方法里面去。由于上面是通過子線程Handler去post,因此該方法是運行在子線程里面
private fun checkRetainedObjects() { ... //看下此時待觀察的集合里面還有多少個沒有被分析的弱引用對象 //也就是retainedUptimeMillis != -1L 的對象的個數(shù) //并且在調(diào)用retainedObjectCount.get()的時候,還會再調(diào)用一次removeWeaklyReachableObjects(),嘗試再次移除一遍 var retainedReferenceCount = objectWatcher.retainedObjectCount //如果此時還有泄漏對象,則調(diào)用gc,并重新再獲取一次數(shù)量 if (retainedReferenceCount > 0) { gcTrigger.runGc() retainedReferenceCount = objectWatcher.retainedObjectCount } //這邊判斷應(yīng)用是出于前臺還是后臺, //如果是在前臺,則個數(shù)達到5個時才dump,如果應(yīng)用在后臺則只要有一個都會進行dump if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return val now = SystemClock.uptimeMillis() val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis //如果這個時間與上次dump的時間過短,則會重新調(diào)用scheduleRetainedObjectCheck, //并延遲執(zhí)行,這個延遲的時間 = 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()
方法里面執(zhí)行的步驟:
- 先獲取待觀察集合里面被還沒有被分析的疑似泄漏的弱引用對象的個數(shù),通過
retainedUptimeMillis != -1L
這個判斷,因為當(dāng)對象被通過dump分析之后,會將retainedUptimeMillis這個時間改為-1。而且在objectWatcher.retainedObjectCount
這個get的方法里面還會執(zhí)行一次removeWeaklyReachableObjects()
,嘗試再次移除被回收的弱引用對象。 - 如果此時還是有泄漏對象,也就是retainedReferenceCount > 0時,則會調(diào)用一次
gcTrigger.runGc()
進行一次GC,然后在重新獲取下個數(shù)。 - 接著判斷應(yīng)用是在前臺還是后臺,如果應(yīng)用此時是在前臺,則只有當(dāng)個數(shù)達到5個時才進行dump。如果是在后臺,則只要有一個就會進行dump。
- 接下來在判斷下當(dāng)前時間距離上一次dump的時間,如果時間過短(小于60S),則不進行dump,而是重新調(diào)用
scheduleRetainedObjectCheck
并延時執(zhí)行,延遲時間為:60秒 - 時間差。并且這邊會返回,不再執(zhí)行之后的dump - 最后執(zhí)行dump,將內(nèi)存泄漏的案發(fā)現(xiàn)場保存下來,并解析。
接下來來看下dumpHeap這個方法,這邊就不分析dump的源碼了,主要看下dump后執(zhí)行操作。
private fun dumpHeap( retainedReferenceCount: Int, retry: Boolean, reason: String ) { ... val heapDumpUptimeMillis = SystemClock.uptimeMillis() ... //執(zhí)行dump,并計算dump的時間 durationMillis = measureDurationMillis { //執(zhí)行dump,這邊就不分析了,感興趣可以自行點進去看下 configProvider().heapDumper.dumpHeap(heapDumpFile) } ... //dump之后處理以分析的對象 objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis) }
這邊注意下:objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
這個方法,上面我們說到當(dāng)對象被通過dump分析之后,會將retainedUptimeMillis這個時間改為-1。現(xiàn)在我們來看下這個方法里面的源碼:
/** * Clears all [KeyedWeakReference] that were created before [heapDumpUptimeMillis] (based on * [clock] [Clock.uptimeMillis]) */ @Synchronized fun clearObjectsWatchedBefore(heapDumpUptimeMillis: Long) { //從待觀察集合里面過濾出時間小于等于當(dāng)前dump時間的弱引用 val weakRefsToRemove = watchedObjects.filter { it.value.watchUptimeMillis <= heapDumpUptimeMillis } //遍歷執(zhí)行clear,將時間設(shè)置為-1,這個在下次觸發(fā)執(zhí)行checkRetainedObjects,里面不會重復(fù)判斷 weakRefsToRemove.values.forEach { it.clear() } //然后從待觀察列表里面移除 watchedObjects.keys.removeAll(weakRefsToRemove.keys) }
這個方法里面會過濾出已經(jīng)被dump過的弱引用對象,因為當(dāng)它的watchUptimeMillis <= heapDumpUptimeMillis時,dump會將內(nèi)存中當(dāng)前時間點之前的疑似泄漏的對象都列舉出來,所以只要是在當(dāng)前dump時間點之前加入的都可以認為是已經(jīng)dump了。
然后接下調(diào)用過濾出來的弱引用對象的claer()
方法,將watchUptimeMillis
時間設(shè)置為-1,并且從待觀察集合里面移除。
總結(jié)
經(jīng)過了上面的源碼分析,接下來來總結(jié)下:
- 使用ContentProvider進行自動初始化,在ContentProvider的onCreate()方法里面調(diào)用
AppWatcher.manualInstall(application)
進行LeakCanary的初始化; - 在
AppWatcher.manualInstall(application)
里面先做了一些合法性校驗,然后初始化初始化了InternalLeakCanary
和構(gòu)建了4種生命周期監(jiān)聽器Watcher,分別是Activity、Fragment、Service和RootView。 - 在構(gòu)建4種Watcher時,順便創(chuàng)建了
objectWatcher
,用于之后延時檢查對象是否被回收。當(dāng)被被觀察的類型在生命周期進入結(jié)束時,會調(diào)用到objectWatcher.expectWeaklyReachable()
方法里面。 - 在
objectWatcher.expectWeaklyReachable()
方法里面會先將被回收的對象移除待觀察集合,然后創(chuàng)建一個弱引用放入到待觀察列表里面,最后在開啟延時檢查對象是否被回收,默認延時5秒。 - 延時計時到了之后會調(diào)用
objectWatcher.moveToRetained
方法,在該方法內(nèi)會再次嘗試移除這5秒內(nèi)已經(jīng)被回收的對象,然后調(diào)用InternalLeakCanary.onObjectRetained()
方法,跟著調(diào)用棧進入到heapDumpTrigger.scheduleRetainedObjectCheck()
方法,最終到了checkRetainedObjects()
這個方法里面。 - 在
checkRetainedObjects()
這個方法里面,會先判斷待觀察結(jié)合里面是否還有疑似泄漏的弱引用對象,如果有則嘗試進行一次GC操作。然后判斷應(yīng)用是在前臺還是后臺,如果實在前臺則當(dāng)疑似泄漏的弱引用對象數(shù)量達到5個時才進行dump。如果是在后臺,則只要有一個就會進行dump。 - 然后判斷當(dāng)前時間是否距離上次dump的時間小于60秒,如果小于60秒則重新進入到
heapDumpTrigger.scheduleRetainedObjectCheck()
方法,并延時開啟任務(wù),延時時間為:60秒 - 兩次dump的時間差。 - 最后進行dump,和dump文件的數(shù)據(jù)分析。dump完成后,會將待觀察列表里面的已被分析過的弱引用對象的
watchUptimeMillis
時間設(shè)置為-1,并移除。
以上就是LeakCanary 2.9.1的源碼解析,更多關(guān)于內(nèi)存泄漏檢測LeakCanary的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
關(guān)于Touch Panel AA區(qū)要做外擴的原因解析
今天小編就為大家分享一篇關(guān)于Touch Panel AA區(qū)要做外擴的原因解析,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-12-12Android實用小技巧之利用Lifecycle寫出更好維護的代碼
lifecycle是一個類,用于存儲有關(guān)組件(如Activity或Fragment)的生命周期狀態(tài)的信息,并允許其他對象觀察此狀態(tài),下面這篇文章主要給大家介紹了關(guān)于Android實用小技巧之利用Lifecycle寫出更好維護的代碼的相關(guān)資料,需要的朋友可以參考下2022-05-05Android app開發(fā)中Retrofit框架的初步上手使用
這篇文章主要介紹了Android app開發(fā)中Retrofit框架的初步上手使用,Retrofit 2.0發(fā)布以來獲得了巨大的人氣增長,并且經(jīng)常被開發(fā)者們拿來與Volley比較,需要的朋友可以參考下2016-02-02Android ListView列表控件的介紹和性能優(yōu)化
這篇文章主要介紹了Android ListView列表控件的介紹和性能優(yōu)化,需要的朋友可以參考下2017-06-06Android編程實現(xiàn)動態(tài)支持多語言的方法
這篇文章主要介紹了Android編程實現(xiàn)動態(tài)支持多語言的方法,涉及Android資源、控件及屬性相關(guān)操作技巧,需要的朋友可以參考下2017-06-06