Android LeakCanary檢測(cè)內(nèi)存泄露原理
以L(fǎng)eakCanary2.6源碼分析LeakCanary檢測(cè)內(nèi)存泄露原理,為減少篇幅長(zhǎng)度,突出關(guān)鍵點(diǎn),不粘貼大量源碼,閱讀時(shí)需搭配源碼食用。
如何獲取context
LeakCanary只需引入依賴(lài),不需要初始化代碼,就能執(zhí)行內(nèi)存泄漏檢測(cè)了,它是通過(guò)ContentProvider獲取應(yīng)用的context。這種獲取context方式在開(kāi)源第三方庫(kù)中十分流行。如下AppWatcherInstaller在LeakCanary的aar包中manifest文件中注冊(cè)。
internal sealed class AppWatcherInstaller : ContentProvider() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)//1
return true
}
...
}
默認(rèn)檢測(cè)哪些類(lèi)對(duì)象的內(nèi)存泄露
(1)處的方法將調(diào)用如下方法注冊(cè)需要檢測(cè)泄露的對(duì)象:
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
可以看到LeakCanary會(huì)把Activity,F(xiàn)ragment,ViewModel,RootView和Service納入檢測(cè),這些對(duì)象都是有明確的生命周期,而且占用內(nèi)存較高,它們的內(nèi)存泄露是需要我們重點(diǎn)關(guān)注的。
如何將這些生命周期對(duì)象納入監(jiān)測(cè)
(1)處的manualInstall方法將遍歷調(diào)用上述Watcher的install方法以適時(shí)將這些生命周期對(duì)象納入檢測(cè)。
ActivityWatcher
ActivityWatcher中install方法通過(guò)向application注冊(cè)Application.ActivityLifecycleCallbacks接口回調(diào)實(shí)現(xiàn)對(duì)Activity生命周期的檢測(cè)。這里有一個(gè)很棒的技巧,利用Kotlin委托與Java動(dòng)態(tài)代理,將不需要關(guān)注的方法給出默認(rèn)空實(shí)現(xiàn),(2)(3)處代碼提取出來(lái),可以在平時(shí)開(kāi)發(fā)中有需求的地方使用。
//ActivityWatcher
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
reachabilityWatcher.expectWeaklyReachable(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)//4
}
}
internal inline fun <reified T : Any> noOpDelegate(): T {
val javaClass = T::class.java
return Proxy.newProxyInstance(
javaClass.classLoader, arrayOf(javaClass), NO_OP_HANDLER
) as T
}//2
private val NO_OP_HANDLER = InvocationHandler { _, _, _ ->
// no op
}//3
(4)調(diào)用的objectWatcher.expectWeaklyReachable方法是將對(duì)象納入監(jiān)測(cè)的通用方法,如其名稱(chēng)所示,WeaklyReachable相較的是StronglyReachable,當(dāng)一個(gè)對(duì)象不再需要時(shí),我們希望它從WeaklyReachable變?yōu)镾tronglyReachable。
我們可以在不再需要某對(duì)象時(shí)主動(dòng)調(diào)用該方法,檢測(cè)任意對(duì)象(除上節(jié)的默認(rèn)對(duì)象)的內(nèi)存泄露:
AppWatcher.objectWatcher.expectWeaklyReachable(obj, "")
onActivityDestroyed回調(diào)中就通過(guò)該方式將activity納入監(jiān)測(cè)。
通過(guò)上述對(duì)Activity的納入內(nèi)存泄露源碼的分析,可以發(fā)現(xiàn)其中2個(gè)關(guān)鍵點(diǎn),首先需要能獲取應(yīng)用中所有待檢測(cè)對(duì)象的引用,其次需要一個(gè)待檢測(cè)對(duì)象生命周期結(jié)束的時(shí)機(jī)。而這兩點(diǎn)通過(guò)注冊(cè)Application.ActivityLifecycleCallbacks接口能夠同時(shí)滿(mǎn)足,可對(duì)于其他類(lèi)對(duì)象,就沒(méi)有如此便捷的方式了。
下面介紹Fragment,ViewModel,RootView和Service這些類(lèi)對(duì)象是如何納入檢測(cè)的。
FragmentAndViewModelWatcher
Fragment為了兼容在Android源碼中幾個(gè)不同包名的實(shí)現(xiàn),對(duì)它們的檢測(cè)也需要分別實(shí)現(xiàn),我們?cè)贔ragmentAndViewModelWatcher中只關(guān)注AndroidXFragmentDestroyWatcher對(duì)AndroidX中Fragment的內(nèi)存泄露檢測(cè)即可,其他幾個(gè)實(shí)現(xiàn)類(lèi)似。
FragmentAndViewModelWatcher先同樣通過(guò)注冊(cè)Application.ActivityLifecycleCallbacks回調(diào),適時(shí)獲取Activity引用,并在AndroidXFragmentDestroyWatcher獲取Activity的supportFragmentManager,向其注冊(cè)FragmentManager.FragmentLifecycleCallbacks。在其中的onFragmentDestroyed與onFragmentViewDestroyed回調(diào)中將Fragment和Fragment的View納入內(nèi)存泄露檢測(cè)。
對(duì)于ViewModel的檢測(cè),則需要關(guān)注ViewModelClearedWatcher,通過(guò)用上一步獲取的Activity引用,添加名為ViewModelClearedWatcher的spy ViewModel,來(lái)獲得收到onCleared回調(diào)的能力,因?yàn)閷?duì)于一個(gè)ViewModelStoreOwner(Activity,F(xiàn)ragment)來(lái)說(shuō),自己的一個(gè)ViewModel回調(diào)了onCleared,則其他ViewModel的onCleared也應(yīng)該被調(diào)用。這些ViewModel是通過(guò)ViewModelStore的mMap屬性反射獲取的。在spy ViewModel的onCleared回調(diào)中,納入內(nèi)存泄露檢測(cè)。
RootViewWatcher
對(duì)于Android里Window中的RootView,即DecorView,可以通過(guò)注冊(cè)addOnAttachStateChangeListener在View的onViewDetachedFromWindow時(shí)進(jìn)行檢測(cè)。而獲取待檢測(cè)對(duì)象的引用就不像Activity和Fragment一樣有回調(diào)可以依賴(lài)了。LeakCanary采取了Hook的方式在install方法對(duì)RootView的容器進(jìn)行替換,具體來(lái)說(shuō)就是通過(guò)反射機(jī)制將WindowManagerGlobal中的mViews(包含所有Window中的DecorView)的ArrayList容器的實(shí)現(xiàn)修改,在其add方法中獲取DecorView的引用,之后設(shè)置OnAttachStateChangeListener回調(diào)進(jìn)行檢測(cè)。
ServiceWatcher
而Android中Service,無(wú)論是獲取引用還是監(jiān)測(cè)時(shí)機(jī)的確定都沒(méi)有系統(tǒng)的回調(diào)可以依賴(lài),LeakCanary都是采用Hook的方式達(dá)到目的。首先通過(guò)反射拿到ActivityThread中的mServices,這是包含app中全部Service的一個(gè)Map。在install方法中有兩個(gè)Hook點(diǎn),首先是Android 消息機(jī)制的中轉(zhuǎn)中心,名為H的Handler,系統(tǒng)側(cè)對(duì)應(yīng)用側(cè)的全部回調(diào)都需要經(jīng)過(guò)它的周轉(zhuǎn)。因?yàn)镠andler中mCallback執(zhí)行的優(yōu)先級(jí)大于handleMessage方法,Leakcanary替換H的mCallback實(shí)現(xiàn),當(dāng)消息為STOP_SERVICE時(shí),便從mServices取出該消息對(duì)應(yīng)的Service作為待檢測(cè)Service引用。第二個(gè)Hook點(diǎn)為ActivityManagerService,通過(guò)動(dòng)態(tài)代理修改它的serviceDoneExecuting方法,在其真正實(shí)現(xiàn)前增加內(nèi)存泄露檢測(cè),其余方法保持不變。
這些類(lèi)納入檢測(cè)納入檢測(cè)的時(shí)機(jī),可總結(jié)為如下表格:
| 如何獲取引用 | 何時(shí)納入監(jiān)測(cè) | |
|---|---|---|
| Activity | ActivityLifecycleCallbacks回調(diào) | onActivityDestroyed |
| Fragment | FragmentLifecycleCallbacks回調(diào) | onFragmentDestroyed |
| Fragment中的View | FragmentLifecycleCallbacks回調(diào) | onFragmentViewDestroyed |
| ViewModel | 反射獲取ViewModelStore的mMap | spy ViewModel的onCleared |
| Window中的DecorView | Hook WindowManagerGlobal中的mViews | onViewDetachedFromWindow |
| Service | Hook H的mCallback實(shí)現(xiàn),當(dāng)消息為STOP_SERVICE時(shí),從ActivityThread中的mServices獲取 | Hook ActivityManagerService,serviceDoneExecuting中檢測(cè) |
如何確定內(nèi)存泄露的對(duì)象
在確定待檢測(cè)對(duì)象與時(shí)機(jī)后,查看ObjectWatcher的expectWeaklyReachable方法,可以得知如何實(shí)現(xiàn)將泄露對(duì)象從待檢測(cè)對(duì)象(默認(rèn)即上節(jié)我們分析的那些有生命周期的類(lèi)對(duì)象)挑出來(lái)的。確定內(nèi)存泄露對(duì)象的原理是我們常用的WeakReference,其雙參數(shù)構(gòu)造函數(shù)支持傳入一個(gè)ReferenceQueue,當(dāng)其關(guān)聯(lián)的對(duì)象回收時(shí),會(huì)將WeakReference加入ReferenceQueue中。LeakCanary的做法是繼承ReferenceQueue,增加一個(gè)值為UUID的屬性key,同時(shí)將每個(gè)需要監(jiān)測(cè)的對(duì)象WeakReference以此UUID作為鍵加入一個(gè)map中。這樣,在GC過(guò)后,removeWeaklyReachableObjects方法通過(guò)遍歷ReferenceQueue,通過(guò)key值刪除map中已回收的對(duì)象,剩下的對(duì)象就基本可以確定發(fā)生了內(nèi)存泄露。
如何確定從GC root到泄露對(duì)象的引用鏈
在確定內(nèi)存泄露的對(duì)象后,就需要其他手段來(lái)確定泄露對(duì)象引用鏈了,這一過(guò)程開(kāi)始于checkRetainedObjects方法,跟蹤調(diào)用可以看到啟動(dòng)了前臺(tái)服務(wù)HeapAnalyzerService,這在我們使用LeakCanary時(shí)可以在通知欄看到。服務(wù)中調(diào)用了HeapAnalyzer的analyze方法進(jìn)行堆內(nèi)存分析,由Shark庫(kù)實(shí)現(xiàn)該功能,就不再進(jìn)行追蹤。
以上就是分析LeakCanary檢測(cè)內(nèi)存泄露原理的詳細(xì)內(nèi)容,更多關(guān)于LeakCanary檢測(cè)內(nèi)存泄露的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android JSON數(shù)據(jù)與實(shí)體類(lèi)之間的相互轉(zhuǎn)化(GSON的用法)
這篇文章主要介紹了Android JSON數(shù)據(jù)與實(shí)體類(lèi)之間的相互轉(zhuǎn)化(GSON的用法),非常具有實(shí)用價(jià)值,需要的朋友可以參考下。2017-01-01
flutter 動(dòng)手?jǐn)]一個(gè)城市選擇citypicker功能
在一些項(xiàng)目開(kāi)發(fā)中經(jīng)常會(huì)用到城市選擇器功能,今天小編動(dòng)手?jǐn)]一個(gè)基于flutter 城市選擇citypicker功能,具體實(shí)現(xiàn)過(guò)程跟隨小編一起看看吧2021-08-08
android編程之menu按鍵功能實(shí)現(xiàn)方法
這篇文章主要介紹了android編程之menu按鍵功能實(shí)現(xiàn)方法,實(shí)例分析了Android實(shí)現(xiàn)menu的相關(guān)技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
okhttp3.4.1+retrofit2.1.0實(shí)現(xiàn)離線(xiàn)緩存的示例
本篇文章主要介紹了okhttp3.4.1+retrofit2.1.0實(shí)現(xiàn)離線(xiàn)緩存的示例,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-12-12
Android中使用Service實(shí)現(xiàn)后臺(tái)發(fā)送郵件功能實(shí)例
這篇文章主要介紹了Android中使用Service實(shí)現(xiàn)后臺(tái)發(fā)送郵件功能的方法,結(jié)合實(shí)例形式分析了Service實(shí)現(xiàn)郵件的發(fā)送、接收及權(quán)限控制相關(guān)技巧,需要的朋友可以參考下2016-01-01
Android Http實(shí)現(xiàn)文件的上傳和下載
這篇文章主要為大家詳細(xì)介紹了Android Http實(shí)現(xiàn)文件的上傳和下載,感興趣的小伙伴們可以參考一下2016-08-08
講解Android中的Widget及AppWidget小工具的創(chuàng)建實(shí)例
這篇文章主要介紹了講解Android中的Widget及Widget的創(chuàng)建實(shí)例,文中的例子展示了通過(guò)RemoteView來(lái)溝通AppWidgetProvider與AppWidgetHostView的方法,需要的朋友可以參考下2016-03-03
Android中實(shí)現(xiàn)TCP和UDP傳輸實(shí)例
這篇文章主要介紹了Android中實(shí)現(xiàn)TCP和UDP傳輸實(shí)例,本文給出了TCP服務(wù)器端代碼、TCP客戶(hù)端代碼、UDP服務(wù)器端代碼、UDP客戶(hù)端代碼等代碼實(shí)例,需要的朋友可以參考下2015-03-03
Compose?for?Desktop?鼠標(biāo)事件示例demo
這篇文章主要為大家介紹了Compose?for?Desktop?鼠標(biāo)事件示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
android自動(dòng)工具類(lèi)TextUtils使用詳解
這篇文章主要介紹了android自動(dòng)工具類(lèi)TextUtils的使用方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10

