LeakCanary原始碼分析

LeakCanary使用

LeakCanary是一個用於Android的記憶體洩漏檢測庫。本文從如下四點分析原始碼

檢查哪些記憶體洩漏

檢查記憶體洩漏的時機

如何判定記憶體洩漏

如何分析記憶體洩漏(只有一點點,可能跟沒有一樣)

記憶體洩漏誤報

1。檢查哪些記憶體洩漏

LeakCanary原始碼分析

AppWatcherInstaller繼承於ContentProvider,呼叫時機是介於Application的attachBaseContext(Context)和 onCreate() 之間。透過這種方式初始化。

方法2manualInstall實現了預設引數watchersToInstall,透過這個方法我們看到Activity,FragmentAndViewModel,RootView,Service四個觀察者

fun appDefaultWatchers( application: Application, reachabilityWatcher: ReachabilityWatcher = objectWatcher): List { return listOf( ActivityWatcher(application, reachabilityWatcher), FragmentAndViewModelWatcher(application, reachabilityWatcher), RootViewWatcher(reachabilityWatcher), ServiceWatcher(reachabilityWatcher) )}

2。檢查記憶體洩漏的時機

2。1 ActivityWatcher

activity觸發OnDestory檢查是否回收Activity例項

private val lifecycleCallbacks = object : Application。ActivityLifecycleCallbacks by noOpDelegate() { override fun onActivityDestroyed(activity: Activity) { reachabilityWatcher。expectWeaklyReachable( activity, “${activity::class。java。name} received Activity#onDestroy() callback” ) } }

2。2 FragmentAndViewModelWatcher

fragment觸發onFragmentDestroyed或onFragmentViewDestroyed檢查是否可以回收Fragment例項

viewModel觸發onClear檢查是否可以回收ViewModel例項

LeakCanary原始碼分析

2。2。1 檢查哪些Fragment

由於Android現在有三種Fragment

androidx。fragment。app

android。app。fragment

android。support。v4。app。Fragment

leakCanary透過反射先去檢查是否引入上面三種Fragment,如果有就反射建立對應的watcher加入到 fragmentDestroyWatchers中

private fun getWatcherIfAvailable( fragmentClassName: String, watcherClassName: String, reachabilityWatcher: ReachabilityWatcher): ((Activity) -> Unit)? { return if (classAvailable(fragmentClassName) && classAvailable(watcherClassName) ) { val watcherConstructor = Class。forName(watcherClassName)。getDeclaredConstructor(ReachabilityWatcher::class。java) @Suppress(“UNCHECKED_CAST”) watcherConstructor。newInstance(reachabilityWatcher) as (Activity) -> Unit } else { null }}

2。2。2 Fragment記憶體洩漏檢查時機

(1)application註冊activity生命週期回撥

(2)當監聽到ctivity被建立時,獲取該activity的對應的fragmentManager建立fragment的生命週期觀察者

(3)當onFragmentViewDestroyed/onFragmentDestroyed觸發時,遍歷集合然後檢查是否可以回收Fragment例項

private val fragmentLifecycleCallbacks = object : FragmentManager。FragmentLifecycleCallbacks() { 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” ) }}

2。2。3 檢查哪些ViewModel記憶體洩漏

既然fragment/activity被銷燬了,fragment/activity物件被回收了,那麼fragment/activity繫結的所有viewmodel例項也應該銷燬,所以leakCanary增加了viewmodel的記憶體檢查

(1)監聽當activity被建立時,繫結一個間諜viewmodel例項

//AndroidXFragmentDestroyWatcheroverride fun invoke(activity: Activity) { if (activity is FragmentActivity) { val supportFragmentManager = activity。supportFragmentManager supportFragmentManager。registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true) ViewModelClearedWatcher。install(activity, reachabilityWatcher) }}

(2)監聽當fragment被建立時,繫結一個間諜viewmodel例項

//AndroidXFragmentDestroyWatcher##fragmentLifecycleCallbacksoverride fun onFragmentCreated( fm: FragmentManager, fragment: Fragment, savedInstanceState: Bundle?) { ViewModelClearedWatcher。install(fragment, reachabilityWatcher)}

2。2。4 ViewModel記憶體洩漏檢查時機

(1)利用反射獲得fragment/activity繫結的viewModel集合

(2)當leakcanary繫結的viewmodel生命週期走到onCleared時,就去檢查所有viewmodel例項是否可以回收(這邊就是為啥作者取名叫spy)

//ViewModelClearedWatcheroverride fun onCleared() { viewModelMap?。values?。forEach { viewModel -> reachabilityWatcher。expectWeaklyReachable( viewModel, “${viewModel::class。java。name} received ViewModel#onCleared() callback” ) }}

2。3 RootViewWatcher

view觸發onViewDetachedFromWindow檢查是否回收View例項

利用Curtains獲得檢視變化,檢查所有被新增到phoneWindow上面的,windowLayoutParams。title為Toast或者是Tooltip,或者除PopupWindow之外的所有view。

//RootViewWatcherrootView。addOnAttachStateChangeListener(object : OnAttachStateChangeListener { val watchDetachedView = Runnable { reachabilityWatcher。expectWeaklyReachable( rootView, “${rootView::class。java。name} received View#onDetachedFromWindow() callback” ) } override fun onViewAttachedToWindow(v: View) { WindowManager。LayoutParams。TYPE_PHONE mainHandler。removeCallbacks(watchDetachedView) } override fun onViewDetachedFromWindow(v: View) { mainHandler。post(watchDetachedView) }})

2。4 ServiceWatcher

service觸發onDestroy檢查是否回收Service例項

private fun onServiceDestroyed(token: IBinder) { servicesToBeDestroyed。remove(token)?。also { serviceWeakReference -> serviceWeakReference。get()?。let { service -> reachabilityWatcher。expectWeaklyReachable( service, “${service::class。java。name} received Service#onDestroy() callback” ) } }}

3。如何判定記憶體洩漏

LeakCanary原始碼分析

ReferenceQueue : 引用佇列,在檢測到適當的可到達性更改後,垃圾回收器將已註冊的引用物件新增到該佇列中

(1)將待檢查物件加入到weakReference和watchedObjects中

@Synchronized override fun expectWeaklyReachable( watchedObject: Any, description: String) { if (!isEnabled()) { return } removeWeaklyReachableObjects() val key = UUID。randomUUID() 。toString() val watchUptimeMillis = clock。uptimeMillis() 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 checkRetainedExecutor。execute { moveToRetained(key) }}

(6)執行GC後,遍歷ReferenceQueue,刪除watchedObjects集合中儲存的物件

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? do { ref = queue。poll() as KeyedWeakReference? if (ref != null) { watchedObjects。remove(ref。key) } } while (ref != null)}

(3)判斷watchedObjects長度是否發生改變,如果改變就認為記憶體洩漏

private fun checkRetainedCount( retainedKeysCount: Int, retainedVisibleThreshold: Int, nopeReason: String? = null): Boolean { val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount 。。。 if (retainedKeysCount < retainedVisibleThreshold) { if (applicationVisible || applicationInvisibleLessThanWatchPeriod) { if (countChanged) { onRetainInstanceListener。onEvent(BelowThreshold(retainedKeysCount)) } showRetainedCountNotification( objectCount = retainedKeysCount, contentText = application。getString( R。string。leak_canary_notification_retained_visible, retainedVisibleThreshold ) ) scheduleRetainedObjectCheck( delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS ) return true } } return false}

(10) 當檢查到5次記憶體洩漏就會生成hprof檔案

override fun dumpHeap(): DumpHeapResult {。。。val durationMillis = measureDurationMillis { Debug。dumpHprofData(heapDumpFile。absolutePath)}。。。}

4。如何分析記憶體洩漏

LeakCanary原始碼分析

利用Shark分析工具分析hprof檔案

(8)這裡透過解析hprof檔案生成heapAnalysis物件。SharkLog列印並存入資料庫

override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) { SharkLog。d { “\u200B\n${LeakTraceWrapper。wrap(heapAnalysis。toString(), 120)}” } val db = LeaksDbHelper(application)。writableDatabase val id = HeapAnalysisTable。insert(db, heapAnalysis) db。releaseReference()。。。}

5。記憶體洩漏誤報

Java虛擬機器的主流垃圾回收器採取的是可達性分析演算法, 可達性演算法是透過從GC root往外遍歷,如果從root節點無法遍歷該節點表明該節點對應的物件處於可回收狀態。 反之不會回收。

public class MainActivity2 extends FragmentActivity { Fragment mFragmentA; Fragment mFragmentB; @Override protected void onCreate(Bundle savedInstanceState) { super。onCreate(savedInstanceState); setContentView(R。layout。activity_main2); mFragmentA = new FragmentA(); mFragmentB = new FragmentB(); findViewById(R。id。buttona)。setOnClickListener(new View。OnClickListener() { @Override public void onClick(View v) { replaceFragment(mFragmentA); } }); findViewById(R。id。buttonb)。setOnClickListener(new View。OnClickListener() { @Override public void onClick(View v) { replaceFragment(mFragmentB); } }); } private void replaceFragment(Fragment fragment) { getSupportFragmentManager()。beginTransaction() 。replace(R。id。container, fragment)。commit(); }}

以fragment為例,leakcanary認為fragment走onDestory了,就應該釋放fragment。但是這種情況真的是記憶體洩漏麼?

├─ com。example。MainActivity2 instance │ Leaking: NO (Activity#mDestroyed is false) │ ↓ MainActivity2。mFragmentA │ ~~~~~~~~~~ ╰→ com。example。FragmentA instance Leaking: YES (ObjectWatcher was watching this because com。example。FragmentA received Fragment#onDestroy() callback and Fragment#mFragmentManager is null) ​ key = 216c8cf8-2cdb-4509-84e9-8404afefffeb ​ watchDurationMillis = 3804 ​ retainedDurationMillis = -1 ​ key = eaa41c88-bccb-47ac-8fb7-46b27dec0356 ​ watchDurationMillis = 6113 ​ retainedDurationMillis = 1112 ​ key = 77d5f271-382b-42ec-904b-1e8a6d4ab097 ​ watchDurationMillis = 7423 ​ retainedDurationMillis = 2423 ​ key = 8d79952f-a300-4830-b513-62e40cda8bba ​ watchDurationMillis = 15771 ​ retainedDurationMillis = 10765 13858 bytes retained by leaking objects Signature: f1d17d3f6aa4713d4de15a4f465f97003aa7

根據堆疊資訊,leakcanary認為fragmentA走了onDestory應該要回收這個fragmentA物件,但是發現還被MainActivity2物件持有無法回收,然後判定是記憶體洩漏。 放在我們這個邏輯裡面,fragment不釋放是對的。 只不過這種實現不是記憶體最佳罷了。

作者|no_one|掘金