Android LeakCanary檢測內(nèi)存泄露原理
以LeakCanary2.6源碼分析LeakCanary檢測內(nèi)存泄露原理,為減少篇幅長度,突出關(guān)鍵點,不粘貼大量源碼,閱讀時需搭配源碼食用。
如何獲取context
LeakCanary只需引入依賴,不需要初始化代碼,就能執(zhí)行內(nèi)存泄漏檢測了,它是通過ContentProvider獲取應用的context。這種獲取context方式在開源第三方庫中十分流行。如下AppWatcherInstaller在LeakCanary的aar包中manifest文件中注冊。
internal sealed class AppWatcherInstaller : ContentProvider() {
override fun onCreate(): Boolean {
val application = context!!.applicationContext as Application
AppWatcher.manualInstall(application)//1
return true
}
...
}
默認檢測哪些類對象的內(nèi)存泄露
(1)處的方法將調(diào)用如下方法注冊需要檢測泄露的對象:
fun appDefaultWatchers(
application: Application,
reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
return listOf(
ActivityWatcher(application, reachabilityWatcher),
FragmentAndViewModelWatcher(application, reachabilityWatcher),
RootViewWatcher(reachabilityWatcher),
ServiceWatcher(reachabilityWatcher)
)
}
可以看到LeakCanary會把Activity,F(xiàn)ragment,ViewModel,RootView和Service納入檢測,這些對象都是有明確的生命周期,而且占用內(nèi)存較高,它們的內(nèi)存泄露是需要我們重點關(guān)注的。
如何將這些生命周期對象納入監(jiān)測
(1)處的manualInstall方法將遍歷調(diào)用上述Watcher的install方法以適時將這些生命周期對象納入檢測。
ActivityWatcher
ActivityWatcher中install方法通過向application注冊Application.ActivityLifecycleCallbacks接口回調(diào)實現(xiàn)對Activity生命周期的檢測。這里有一個很棒的技巧,利用Kotlin委托與Java動態(tài)代理,將不需要關(guān)注的方法給出默認空實現(xiàn),(2)(3)處代碼提取出來,可以在平時開發(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方法是將對象納入監(jiān)測的通用方法,如其名稱所示,WeaklyReachable相較的是StronglyReachable,當一個對象不再需要時,我們希望它從WeaklyReachable變?yōu)镾tronglyReachable。
我們可以在不再需要某對象時主動調(diào)用該方法,檢測任意對象(除上節(jié)的默認對象)的內(nèi)存泄露:
AppWatcher.objectWatcher.expectWeaklyReachable(obj, "")
onActivityDestroyed回調(diào)中就通過該方式將activity納入監(jiān)測。
通過上述對Activity的納入內(nèi)存泄露源碼的分析,可以發(fā)現(xiàn)其中2個關(guān)鍵點,首先需要能獲取應用中所有待檢測對象的引用,其次需要一個待檢測對象生命周期結(jié)束的時機。而這兩點通過注冊Application.ActivityLifecycleCallbacks接口能夠同時滿足,可對于其他類對象,就沒有如此便捷的方式了。
下面介紹Fragment,ViewModel,RootView和Service這些類對象是如何納入檢測的。
FragmentAndViewModelWatcher
Fragment為了兼容在Android源碼中幾個不同包名的實現(xiàn),對它們的檢測也需要分別實現(xiàn),我們在FragmentAndViewModelWatcher中只關(guān)注AndroidXFragmentDestroyWatcher對AndroidX中Fragment的內(nèi)存泄露檢測即可,其他幾個實現(xiàn)類似。
FragmentAndViewModelWatcher先同樣通過注冊Application.ActivityLifecycleCallbacks回調(diào),適時獲取Activity引用,并在AndroidXFragmentDestroyWatcher獲取Activity的supportFragmentManager,向其注冊FragmentManager.FragmentLifecycleCallbacks。在其中的onFragmentDestroyed與onFragmentViewDestroyed回調(diào)中將Fragment和Fragment的View納入內(nèi)存泄露檢測。
對于ViewModel的檢測,則需要關(guān)注ViewModelClearedWatcher,通過用上一步獲取的Activity引用,添加名為ViewModelClearedWatcher的spy ViewModel,來獲得收到onCleared回調(diào)的能力,因為對于一個ViewModelStoreOwner(Activity,F(xiàn)ragment)來說,自己的一個ViewModel回調(diào)了onCleared,則其他ViewModel的onCleared也應該被調(diào)用。這些ViewModel是通過ViewModelStore的mMap屬性反射獲取的。在spy ViewModel的onCleared回調(diào)中,納入內(nèi)存泄露檢測。
RootViewWatcher
對于Android里Window中的RootView,即DecorView,可以通過注冊addOnAttachStateChangeListener在View的onViewDetachedFromWindow時進行檢測。而獲取待檢測對象的引用就不像Activity和Fragment一樣有回調(diào)可以依賴了。LeakCanary采取了Hook的方式在install方法對RootView的容器進行替換,具體來說就是通過反射機制將WindowManagerGlobal中的mViews(包含所有Window中的DecorView)的ArrayList容器的實現(xiàn)修改,在其add方法中獲取DecorView的引用,之后設(shè)置OnAttachStateChangeListener回調(diào)進行檢測。
ServiceWatcher
而Android中Service,無論是獲取引用還是監(jiān)測時機的確定都沒有系統(tǒng)的回調(diào)可以依賴,LeakCanary都是采用Hook的方式達到目的。首先通過反射拿到ActivityThread中的mServices,這是包含app中全部Service的一個Map。在install方法中有兩個Hook點,首先是Android 消息機制的中轉(zhuǎn)中心,名為H的Handler,系統(tǒng)側(cè)對應用側(cè)的全部回調(diào)都需要經(jīng)過它的周轉(zhuǎn)。因為Handler中mCallback執(zhí)行的優(yōu)先級大于handleMessage方法,Leakcanary替換H的mCallback實現(xiàn),當消息為STOP_SERVICE時,便從mServices取出該消息對應的Service作為待檢測Service引用。第二個Hook點為ActivityManagerService,通過動態(tài)代理修改它的serviceDoneExecuting方法,在其真正實現(xiàn)前增加內(nèi)存泄露檢測,其余方法保持不變。
這些類納入檢測納入檢測的時機,可總結(jié)為如下表格:
| 如何獲取引用 | 何時納入監(jiān)測 | |
|---|---|---|
| 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實現(xiàn),當消息為STOP_SERVICE時,從ActivityThread中的mServices獲取 | Hook ActivityManagerService,serviceDoneExecuting中檢測 |
如何確定內(nèi)存泄露的對象
在確定待檢測對象與時機后,查看ObjectWatcher的expectWeaklyReachable方法,可以得知如何實現(xiàn)將泄露對象從待檢測對象(默認即上節(jié)我們分析的那些有生命周期的類對象)挑出來的。確定內(nèi)存泄露對象的原理是我們常用的WeakReference,其雙參數(shù)構(gòu)造函數(shù)支持傳入一個ReferenceQueue,當其關(guān)聯(lián)的對象回收時,會將WeakReference加入ReferenceQueue中。LeakCanary的做法是繼承ReferenceQueue,增加一個值為UUID的屬性key,同時將每個需要監(jiān)測的對象WeakReference以此UUID作為鍵加入一個map中。這樣,在GC過后,removeWeaklyReachableObjects方法通過遍歷ReferenceQueue,通過key值刪除map中已回收的對象,剩下的對象就基本可以確定發(fā)生了內(nèi)存泄露。
如何確定從GC root到泄露對象的引用鏈
在確定內(nèi)存泄露的對象后,就需要其他手段來確定泄露對象引用鏈了,這一過程開始于checkRetainedObjects方法,跟蹤調(diào)用可以看到啟動了前臺服務HeapAnalyzerService,這在我們使用LeakCanary時可以在通知欄看到。服務中調(diào)用了HeapAnalyzer的analyze方法進行堆內(nèi)存分析,由Shark庫實現(xiàn)該功能,就不再進行追蹤。
以上就是分析LeakCanary檢測內(nèi)存泄露原理的詳細內(nèi)容,更多關(guān)于LeakCanary檢測內(nèi)存泄露的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android編程實現(xiàn)讀取工程中的txt文件功能
這篇文章主要介紹了Android編程實現(xiàn)讀取工程中的txt文件功能,結(jié)合實例形式詳細分析了Android讀取txt文件的原理、操作步驟與相關(guān)實現(xiàn)技巧,需要的朋友可以參考下2017-02-02
Android?Jetpack庫重要組件WorkManager的使用
WorkManager是Android?Jetpack的一個強大的組件,用于處理后臺耗時任務。后臺任務可以是一次性的,也可以是重復的,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-08-08
使用Android studio查看Kotlin的字節(jié)碼教程
這篇文章主要介紹了使用Android studio查看Kotlin的字節(jié)碼教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-03-03
Android Fragment滑動組件ViewPager的實例詳解
這篇文章主要介紹了Android Fragment滑動組件ViewPager的實例詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05
一文詳解Android IntentService的開發(fā)技巧
Android應用開發(fā)中,執(zhí)行后臺任務是常見需求之一,其中,IntentService是一種強大的工具,可以輕松管理異步任務,而無需擔心線程管理和生命周期問題,本文將深入探討 IntentService 的各個方面,需要的朋友可以參考下2023-11-11
Android自定義ViewGroup實現(xiàn)彈性滑動效果
這篇文章主要為大家詳細介紹了Android自定義ViewGroup實現(xiàn)彈性滑動效果,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12
Android簡單實現(xiàn)動態(tài)權(quán)限獲取相機權(quán)限及存儲空間等多權(quán)限
這篇文章主要介紹了Android簡單實現(xiàn)動態(tài)權(quán)限獲取相機權(quán)限及存儲空間等多權(quán)限,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的朋友可以參考一下2022-07-07
Android實現(xiàn)帶數(shù)字的圓形進度條(自定義進度條)
在項目開發(fā)中經(jīng)常遇到帶圓形進度條的需求,在GitHub上逛了一圈,發(fā)現(xiàn)沒有,今天小編抽空給大家分享Android實現(xiàn)帶數(shù)字的圓形進度條(自定義進度條),需要的朋友參考下2017-02-02
FlowLayout流式布局實現(xiàn)搜索清空歷史記錄
這篇文章主要為大家詳細介紹了FlowLayout流式布局實現(xiàn)搜索清空歷史記錄,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-12-12

