文章目錄
概述
LeakCanary是Android開發中常用的用來檢測內存泄漏的框架,它能夠幫助開發者快速發現是否發生內存泄漏,並可視化的給予提示。
這裏基於LeakCanary 2.3版本進行分析。
只需要在工程中進行簡單的集成配置,就能自動對Activity、Fragment、fragment View進行自動檢測。也可以通過ObjectWatcher#watch方法對指定對象進行檢測。
dependencies {
// debugImplementation because LeakCanary should only run in debug builds.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.3'
}
僅需添加一行依賴,就完成了接入工作,當buildTypes爲debug時,運行APP就會自動啓動LeakCanary。
核心流程拆解
LeakCanary的核心功能,大致可以分爲兩塊:
- 檢查對象是否被釋放,若未被及時回收,則可能發生內存泄漏
- dump heap並解析hprof文件,查找檢測的對象生成引用鏈信息
檢測泄漏功能的主要流程如下:
-
SDK自初始化
在1.X是通過手動調用方法初始化,2.X實現自動初始化。 -
自動對Android特定對象進行檢測
例如自動監測Activity、Fragment、fragment View生命週期銷燬。 -
檢測判斷對象是否發生內存泄漏
檢查一個對象應該被回收,可能產生內存泄漏而沒有被回收。 -
dump heap至hprof文件並解析文件,生成泄漏引用鏈
依賴另一個專門分析hprof的庫來解析文件和生成分析結果。在1.X是依賴haha庫,2.X改成依賴shark庫。 -
在通知欄和Activity界面顯示泄漏信息
把泄漏分析結果發送到通知欄和Activity界面中顯示可能導致泄漏的對象引用鏈。
源碼探究
SDK自動初始化
前面看到LeakCanary只需要添加一行依賴,不需要開發者手動調用它的方法,那麼它是如何自己把自己啓動起來呢?可以猜測通過註冊靜態廣播監聽系統廣播事件或者註冊ContentProvider。
查找LeakCanary開源庫中的AndroidManifest.xml,可以發現沒有符合的靜態註冊廣播,而存在ContentProvider:
<provider
android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
android:authorities="${applicationId}.leakcanary-installer"
android:exported="false"/>
在AppWatcherInstaller中會進行LeakCanary的初始化。
關於ContentProvider初始化
清單中註冊的ContentProvider在應用啓動的時候就會進行初始化:
[ActivityThread#handleBindApplication]
private void handleBindApplication(AppBindData data) {
// ···
Application app;
// ···
// 反射實例化Application,並會調用其attachBaseContext方法
app = data.info.makeApplication(data.restrictedBackupMode, null);
// ···
// 初始化註冊的ContentProvider
installContentProviders(app, data.providers);
// ···
// 觸發Application的onCreate回調
mInstrumentation.callApplicationOnCreate(app);
// ···
}
ContentProvider啓動後就會回調onCreate方法。
AppWatcherInstaller
AppWatcherInstaller繼承ContentProvider,在其onCreate回調中,調用InternalAppWatcher#install方法進行初始化。
InternalAppWatcher
InternalAppWatcher負責SDK的初始化。
// 初始化ObjectWatcher,用於檢測傳入的對象是否未被及時回收
val objectWatcher = ObjectWatcher(
clock = clock,
checkRetainedExecutor = checkRetainedExecutor,
isEnabled = { AppWatcher.config.enabled }
)
接着看它的init代碼塊:
init {
// 通過反射獲取InternalLeakCanary實例
val internalLeakCanary = try {
val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
leakCanaryListener.getDeclaredField("INSTANCE")
.get(null)
} catch (ignored: Throwable) {
NoLeakCanary
}
@kotlin.Suppress("UNCHECKED_CAST")
// onAppWatcherInstalled指向InternalLeakCanary#invoke(Application)方法
onAppWatcherInstalled = internalLeakCanary as (Application) -> Unit
}
接着看install方法:
fun install(application: Application) {
SharkLog.logger = DefaultCanaryLog()
// 檢查是否在主線程,不在主線程則拋異常
checkMainThread()
if (this::application.isInitialized) {
return
}
InternalAppWatcher.application = application
val configProvider = { AppWatcher.config }
// Activity自動監測相關初始化
ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
// Fragment自動監測相關初始化
FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
// 在init代碼塊中,onAppWatcherInstalled被賦值爲InternalLeakCanary#invoke
onAppWatcherInstalled(application)
}
該方法中進行了Activity和Fragment自動監測功能的初始化,最後初始化InternalLeakCanary。
接着看InternalLeakCanary的invoke方法:
override fun invoke(application: Application) {
this.application = application
// 檢查是否在DEBUGGABLE啓動,若不是則拋異常(除非開啓設置)
checkRunningInDebuggableBuild()
// 添加OnObjectRetainedListener監聽,單發生對象未及時回收時,會回調onObjectRetained
AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
// 用於dump heap
val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)
// 用於主動觸發gc
val gcTrigger = GcTrigger.Default
val configProvider = { LeakCanary.config }
// 開闢子線程,創建子線程通信handler
val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
handlerThread.start()
val backgroundHandler = Handler(handlerThread.looper)
// 用於調度dump heap
heapDumpTrigger = HeapDumpTrigger(
application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
configProvider
)
// 監聽應用前臺後臺切換(通過Application#registerActivityLifecycleCallbacks監聽)
application.registerVisibilityListener { applicationVisible ->
this.applicationVisible = applicationVisible
heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
}
// 監聽當前Resumed的Activity(通過Application#registerActivityLifecycleCallbacks監聽)
registerResumedActivityListener(application)
// 初始化Shortcuts(查看泄漏信息的Activity的快捷方式)
addDynamicShortcut(application)
disableDumpHeapInTests()
}
該方法中初始化了核心功能模塊的類和各種Activity生命週期監聽。
小結
LeakCanary通過註冊ContentProvider,實現應用啓動時自動初始化,完成核心功能模塊的工具類的初始化和Activity、Fragment生命週期的監聽註冊。
自動監測Android特定對象
Activity的監測
LeakCanary在初始化時調用ActivityDestroyWatcher#install:
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> Config
) {
val activityDestroyWatcher =
ActivityDestroyWatcher(objectWatcher, configProvider)
// 向Application註冊ActivityLifecycleCallbacks
application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
}
該方法中就是註冊Activity生命週期監聽。
接着看這個ActivityLifecycleCallbacks的實現:
private val lifecycleCallbacks =
object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityDestroyed(activity: Activity) {
// watchActivities配置是否支持檢測Activity,默認是true
if (configProvider().watchActivities) {
// 通過ObjectWatcher#watch進行檢測
objectWatcher.watch(
activity, "${activity::class.java.name} received Activity#onDestroy() callback"
)
}
}
}
當Activity銷燬時,觸發onActivityDestroyed回調,在這裏調用ObjectWatcher#watch傳入Activity實例和一個描述字符串,進行內存泄漏檢查檢查。
Fragment的監測
在初始化完Activity的自動監測後,接着調用FragmentDestroyWatcher#install進行Fragment的監測初始化:
fun install(
application: Application,
objectWatcher: ObjectWatcher,
configProvider: () -> AppWatcher.Config
) {
val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
if (SDK_INT >= O) {
// 添加對android.app.Fragment的支持
fragmentDestroyWatchers.add(
AndroidOFragmentDestroyWatcher(objectWatcher, configProvider)
)
}
// 如果使用了AndroidX庫,添加對androidx.fragment.app.Fragment的支持
getWatcherIfAvailable(
ANDROIDX_FRAGMENT_CLASS_NAME,
ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
objectWatcher,
configProvider
)?.let {
fragmentDestroyWatchers.add(it)
}
// 如果使用support庫,添加對support.v4.app.Fragment的支持
getWatcherIfAvailable(
ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
objectWatcher,
configProvider
)?.let {
fragmentDestroyWatchers.add(it)
}
if (fragmentDestroyWatchers.size == 0) {
return
}
// 註冊Activity生命週期監聽
application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
override fun onActivityCreated(
activity: Activity,
savedInstanceState: Bundle?
) {
// 在onActivityCreated回調中,依次對各Watcher進行初始化
for (watcher in fragmentDestroyWatchers) {
watcher(activity)
}
}
})
}
該方法中會將不同庫的Fragment的Watcher處理類收集到集合中保存,並註冊Activity生命週期監聽,當觸發onActivityCreated回調後,對各Watcher進行初始化。
android Fragment的Watcher初始化
調用AndroidOFragmentDestroyWatcher#invoke方法傳入activity,向該activity的FragmentManager註冊FragmentLifecycleCallbacks。
當觸發onFragmentViewDestroyed回調時,通過ObjectWatcher#watch檢查fragment.mView對象(即Fragment#onCreateView創建的View)。
當觸發onFragmentDestroyed回調時,檢查fragment對象是否泄漏。
androidx Fragment的Watcher初始化
在getWatcherIfAvailable方法中會通過反射創建AndroidXFragmentDestroyWatcher實例,通過supportFragmentManager註冊LifecycleCallbacks監聽Fragment生命週期。
同android Fragment的監聽一樣,在onFragmentViewDestroyed檢測View,在onFragmentDestroyed檢測Fragment。
有一點不同的是,AndroidXFragmentDestroyWatcher在onFragmentCreated回調中,還會初始化ViewModel的監聽。當ViewModel執行clear觸發onCleared回調時,會檢測ViewModel是否泄漏。
support Fragment的Watcher初始化
反射創建AndroidSupportFragmentDestroyWatcher進行監聽,同樣通過註冊LifecycleCallbacks監聽Fragment生命週期,在銷燬時檢測View和Fragment。
小結
LeakCanary自動監測Activity和Fragment都是通過註冊Lifecycle監聽生命週期回調,在銷燬時檢測對象是否被及時回收。正常情況下,生命週期銷燬後,對象仍沒有被釋放,則可能產生了泄漏。
檢測對象內存泄漏
檢測一個對象是否泄漏,是LeakCanary的核心功能,是所有流程中的關鍵流程。LeakCanary中調用ObjectWatcher#watch檢測一個對象是否期望應該被釋放,但仍然被持有,從而判定其產生泄漏。
在查看watch方法前,先看ObjectWatcher中的兩個成員:
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
private val queue = ReferenceQueue<Any>()
- watchedObjects:緩存待檢測對象。以隨機UUID作爲key,以KeyedWeakReference作爲value。
- queue:引用隊列。當我們用Reference包裝一個對象且該對象僅被這個Reference持有時,那麼該對象可被GC回收。對象回收後,之前包裝用的Reference即會被加入引用隊列中。
KeyedWeakReference繼承自WeakReference,增加了幾個成員,其中有一個key,持有UUID,可通過它定位自身在watchedObjects中的位置。
檢測前準備
接下來進入watch方法:
@Synchronized fun watch(
watchedObject: Any,
description: String
) {
if (!isEnabled()) {
return
}
// 首先清除watchedObjects和queue中緩存
removeWeaklyReachableObjects()
// 生成UUID作爲key
val key = UUID.randomUUID()
.toString()
val watchUptimeMillis = clock.uptimeMillis()
// 創建KeyedWeakReference,傳入key、待檢測對象和queue
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集合中
watchedObjects[key] = reference
checkRetainedExecutor.execute {
// 執行檢測
moveToRetained(key)
}
}
該方法首先清除watchedObjects和queue中緩存,之後生成key和KeyedWeakReference,緩存至watchedObjects。
注意,KeyedWeakReference不僅保存了待檢測對象和key,還保存了引用隊列。當Reference的引用隊列不爲空時,當它包裝的對象被回收時,會將自身加入引用隊列中。
進入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?
do {
// queue中元素不斷移出隊列
ref = queue.poll() as KeyedWeakReference?
if (ref != null) {
// 通過持有的key,從watchedObjects移除對應的KeyedWeakReference
watchedObjects.remove(ref.key)
}
} while (ref != null)
}
回到ObjectWatcher#watch中,方法最後通過checkRetainedExecutor提交任務執行,通過它提交的任務會延遲5s執行:
private val checkRetainedExecutor = Executor {
// 通過主線程handler發送延遲5s的消息(watchDurationMillis默認是5s)
mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
}
初步檢測
當延遲5s後將會在主線程執行moveToRetained方法,接下來進入這個方法:
@Synchronized private fun moveToRetained(key: String) {
// 清理queue和watchedObjects緩存
removeWeaklyReachableObjects()
val retainedRef = watchedObjects[key]
// 判斷取出的元素不爲空,若不爲空則可能存在泄漏
if (retainedRef != null) {
// 記錄當前時間
retainedRef.retainedUptimeMillis = clock.uptimeMillis()
// 回調OnObjectRetainedListener接口,在InternalLeakCanary初始化時註冊
onObjectRetainedListeners.forEach { it.onObjectRetained() }
}
}
該方法中首先清理queue和watchedObjects緩存,若對象已經被回收,則對應的KeyedWeakReference會加入queue中,從而也會將watchedObjects中對應元素移除。若在清理後仍能從watchedObjects中查找到元素,說明對象未被回收,可能發生泄漏。
若對象仍未被回收,則回調執行InternalLeakCanary#onObjectRetained方法,在該方法中又會調用HeapDumpTrigger#onObjectRetained方法:
fun onObjectRetained() {
scheduleRetainedObjectCheck(
reason = "found new object retained",
rescheduling = false
)
}
private fun scheduleRetainedObjectCheck(
reason: String,
rescheduling: Boolean,
delayMillis: Long = 0L
) {
// 省略避免發送handler消息的判斷 ···
backgroundHandler.postDelayed({
checkScheduledAt = 0
// 檢查對象是否仍被持有
checkRetainedObjects(reason)
}, delayMillis)
}
最終判定
5s後對象仍未被回收,則通過handler切換到子線程再次檢查,進入HeapDumpTrigger#checkRetainedObjects方法:
private fun checkRetainedObjects(reason: String) {
val config = configProvider()
// A tick will be rescheduled when this is turned back on.
if (!config.dumpHeap) {
SharkLog.d { "Ignoring check for retained objects scheduled because $reason: LeakCanary.Config.dumpHeap is false" }
return
}
// 先再清理一次集合緩存,然後獲取watchedObjects中經過初步檢測後,仍存在的元素個數
var retainedReferenceCount = objectWatcher.retainedObjectCount
// 判斷是否存在泄漏對象個數
if (retainedReferenceCount > 0) {
// 通過Runtime.getRuntime().gc()立即觸發GC,然後sleep 100ms
gcTrigger.runGc()
// GC後再次查詢未被回收的對象個數
retainedReferenceCount = objectWatcher.retainedObjectCount
}
// 判斷泄漏對象個數是否達到閾值(閾值默認5個)
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
// 省略調試模式判斷部分 ···
// 省略兩次dump時間間隔判斷部分 ···
SharkLog.d { "Check for retained objects found $retainedReferenceCount objects, dumping the heap" }
// cancel通知
dismissRetainedCountNotification()
// 執行dump heap
dumpHeap(retainedReferenceCount, retry = true)
}
該方法中會通過Runtime.getRuntime().gc()立即觸發一次GC,然後等100ms再次檢查對象是否被釋放。若仍存在對象未被釋放,則判斷個數是否達到閾值(5個)、是否不處於調試中、距離前一次dump是否超過60s,都滿足的話就會進行dump heap。
小結
LeakCanary檢查一個期望不再被引用的對象是否泄漏是通過WeakReference和ReferenceQueue配合使用。
- 首先創建WeakReference包裝對象(需要傳入引用隊列),然後將WeakReference緩存至集合中。
- 延遲5s後進行初次判斷。若期間發生GC,WeakReference包裝的對象不再被引用即會被回收,同時WeakReference自身加入引用隊列。此時通過獲取引用隊列中的WeakReference,去移除WeakReference集合中的對應元素。若WeakReference集合還殘留元素,則說明對應WeakReference沒有加入引用隊列,也意味着WeakReference沒有被回收。
- 切換子線程再最終判定。主動觸發一次GC,等待100ms後,再次檢查集合。若仍發現對象被引用未被釋放,則判定這些緩存集合中的對象存在內存泄漏,將進行dump heap操作。
dump heap並解析
dump heap
當判定存在內存泄漏後,會調用HeapDumpTrigger#dumpHeap方法:
private fun dumpHeap(
retainedReferenceCount: Int,
retry: Boolean
) {
saveResourceIdNamesToMemory()
val heapDumpUptimeMillis = SystemClock.uptimeMillis()
KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
// 生成hprof文件
val heapDumpFile = heapDumper.dumpHeap()
if (heapDumpFile == null) {
// 省略dump失敗的重試和提醒部分 ···
}
lastDisplayedRetainedObjectCount = 0
lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
// 清除watchedObjects集合中早於heapDumpUptimeMillis的元素
objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
// 發送文件到HeapAnalyzerService解析
HeapAnalyzerService.runAnalysis(application, heapDumpFile)
}
該方法中將通過AndroidHeapDumper#dumpHeap生成hprof文件:
override fun dumpHeap(): File? {
// 創建目標存儲文件(有讀寫權限會存在sdcard,否則存在app目錄下,文件命名規則yyyy-MM-dd_HH-mm-ss_SSS.hprof)
val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return null
// ···
return try {
// dump heap
Debug.dumpHprofData(heapDumpFile.absolutePath)
if (heapDumpFile.length() == 0L) {
SharkLog.d { "Dumped heap file is 0 byte length" }
null
} else {
// 寫入成功返回hprof文件
heapDumpFile
}
} catch (e: Exception) {
SharkLog.d(e) { "Could not dump heap" }
// Abort heap dump
null
} finally {
cancelToast(toast)
notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
}
}
該方法中調用系統Debug#dumpHprofData方法進行堆轉儲,保存在yyyy-MM-dd_HH-mm-ss_SSS.hprof命名規則的文件中,最後成功寫入返回文件。
解析hprof
關於hprof協議格式可以參考hprof manual、堆轉儲HPROF協議。
hprof文件中分爲header和records兩部分,其中records由多個Record組成。
而Record又由4部分組成:
- TAG:Record類型
- TIME:時間戳
- LENGTH: BODY的字節長度
- BODY:存儲的數據,例如trace、object、class、thread等信息
解析hprof文件主要就是根據TAG,創建對應的集合保存信息在內存中。
當生成hprof文件後,便會啓動HeapAnalyzerService進行解析。HeapAnalyzerService繼承自IntentService,在它的onHandleIntent方法中又會調用onHandleIntentInForeground方法:
override fun onHandleIntentInForeground(intent: Intent?) {
if (intent == null || !intent.hasExtra(HEAPDUMP_FILE_EXTRA)) {
SharkLog.d { "HeapAnalyzerService received a null or empty intent, ignoring." }
return
}
// Since we're running in the main process we should be careful not to impact it.
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)
// 取出傳遞過來的文件
val heapDumpFile = intent.getSerializableExtra(HEAPDUMP_FILE_EXTRA) as File
val config = LeakCanary.config
// 解析並將結果保存在HeapAnalysis中
val heapAnalysis = if (heapDumpFile.exists()) {
analyzeHeap(heapDumpFile, config)
} else {
missingFileFailure(heapDumpFile)
}
onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
// 將解析結果回調出去
config.onHeapAnalyzedListener.onHeapAnalyzed(heapAnalysis)
}
該方法中從intent取出hprof文件後,調用analyzeHeap方法進行解析,結果將會保存在HeapAnalysis中,最後通過接口回調出去。
在analyzeHeap方法中,會創建HeapAnalyzer,執行真正的解析操作。(若apk有混淆,還可以應用混淆mapping文件)。
看HeapAnalyzer#analyze方法:
fun analyze(
heapDumpFile: File,
leakingObjectFinder: LeakingObjectFinder,
referenceMatchers: List<ReferenceMatcher> = emptyList(),
computeRetainedHeapSize: Boolean = false,
objectInspectors: List<ObjectInspector> = emptyList(),
metadataExtractor: MetadataExtractor = MetadataExtractor.NO_OP,
proguardMapping: ProguardMapping? = null
): HeapAnalysis {
val analysisStartNanoTime = System.nanoTime()
// ···
return try {
listener.onAnalysisProgress(PARSING_HEAP_DUMP)
// 打開文件、讀取文件頭
Hprof.open(heapDumpFile)
.use { hprof ->
// 生成hprof文件中Record關係圖,用HprofHeapGraph對象保存
val graph = HprofHeapGraph.indexHprof(hprof, proguardMapping)
val helpers =
FindLeakInput(graph, referenceMatchers, computeRetainedHeapSize, objectInspectors)
// 構建從GC Roots到檢測對象的最短引用路徑,並返回結果
helpers.analyzeGraph(
metadataExtractor, leakingObjectFinder, heapDumpFile, analysisStartNanoTime
)
}
} catch (exception: Throwable) {
// 異常,返回失敗結果
HeapAnalysisFailure(
heapDumpFile, System.currentTimeMillis(), since(analysisStartNanoTime),
HeapAnalysisException(exception)
)
}
}
該方法中首先打開hprof文件,讀取文件頭信息。之後解析文件,創建HprofHeapGraph保存指定TAG的Record信息到gcRoots(GC Roots信息)、objects(內存中對象信息)、classes(類信息)、instances(實例信息)集合中。
之後匹配待檢測對象和HprofHeapGraph.objects找到對應對象ID。然後從GC Roots開始通過BFS查找到檢測對象的調用路徑,期間會根據LeakCanaryCore自帶的白名單剔除名單中的類。最後從衆多路徑中找到一條最短引用路徑,將結果保存在HeapAnalysisSuccess中返回。
HeapAnalysisSuccess中有applicationLeaks和libraryLeaks兩個集合,分別保存應用代碼中的泄漏路徑和依賴庫代碼中的泄漏路徑。
小結
首先調用系統Debug#dumpHprofData方法生成hprof文件,之後通過shark庫進行解析,構建GC Roots到檢測未被釋放對象的最短引用路徑。
通知和顯示結果
回到HeapAnalyzerService#onHandleIntentInForeground中,當執行完analyzeHeap方法,拿到結果後(成功返回HeapAnalysisSuccess,失敗返回HeapAnalysisFailure),傳給onHeapAnalyzedListener接口的onHeapAnalyzed回調方法。
onHeapAnalyzedListener的默認實現類是DefaultOnHeapAnalyzedListener,看它的onHeapAnalyzed方法:
override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
SharkLog.d { "$heapAnalysis" }
// 將結果寫入數據庫
val id = LeaksDbHelper(application).writableDatabase.use { db ->
HeapAnalysisTable.insert(db, heapAnalysis)
}
val (contentTitle, screenToShow) = when (heapAnalysis) {
// 創建失敗結果界面
is HeapAnalysisFailure -> application.getString(
R.string.leak_canary_analysis_failed
) to HeapAnalysisFailureScreen(id)
// 創建成功結果界面
is HeapAnalysisSuccess -> {
val retainedObjectCount = heapAnalysis.allLeaks.sumBy { it.leakTraces.size }
val leakTypeCount = heapAnalysis.applicationLeaks.size + heapAnalysis.libraryLeaks.size
application.getString(
R.string.leak_canary_analysis_success_notification, retainedObjectCount, leakTypeCount
) to HeapDumpScreen(id)
}
}
if (InternalLeakCanary.formFactor == TV) {
showToast(heapAnalysis)
printIntentInfo()
} else {
// 通知欄提示
showNotification(screenToShow, contentTitle)
}
}
該方法中根據解析結果創建對應的Screen,Screen包含解析結果和渲染View的邏輯。之後創建跳轉LeakActivity的PendingIntent,顯示通知欄提示。當點擊通知欄時打開LeakActivity,通過Screen來渲染結果視圖。
總結
LeakCanaryCore的核心原理就是通過WeakReference和ReferenceQueue的組合,來檢測期望被回收的對象。當期望被回收的對象沒有被釋放,就會dump heap生成hprof文件,藉由shark庫進行解析,構建泄漏對象最短引用路徑。最後將結果在通知欄和Activity進行渲染顯示。