LeakCanary2.3 核心原理淺析

概述

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文件,查找檢測的對象生成引用鏈信息

檢測泄漏功能的主要流程如下:

  1. SDK自初始化
    在1.X是通過手動調用方法初始化,2.X實現自動初始化。

  2. 自動對Android特定對象進行檢測
    例如自動監測Activity、Fragment、fragment View生命週期銷燬。

  3. 檢測判斷對象是否發生內存泄漏
    檢查一個對象應該被回收,可能產生內存泄漏而沒有被回收。

  4. dump heap至hprof文件並解析文件,生成泄漏引用鏈
    依賴另一個專門分析hprof的庫來解析文件和生成分析結果。在1.X是依賴haha庫,2.X改成依賴shark庫。

  5. 在通知欄和Activity界面顯示泄漏信息
    把泄漏分析結果發送到通知欄和Activity界面中顯示可能導致泄漏的對象引用鏈。

源碼探究

SDK自動初始化

前面看到LeakCanary只需要添加一行依賴,不需要開發者手動調用它的方法,那麼它是如何自己把自己啓動起來呢?可以猜測通過註冊靜態廣播監聽系統廣播事件或者註冊ContentProvider。

查找LeakCanary開源庫中的AndroidManifest.xml,可以發現沒有符合的靜態註冊廣播,而存在ContentProvider:

<provider
    android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
    android:authorities="${applicationId}.leakcanary-installer"
    android:exported="false"/>

位於AndroidManifest.xml

在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生命週期,在銷燬時檢測ViewFragment

小結

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檢查一個期望不再被引用的對象是否泄漏是通過WeakReferenceReferenceQueue配合使用。

  • 首先創建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的核心原理就是通過WeakReferenceReferenceQueue的組合,來檢測期望被回收的對象。當期望被回收的對象沒有被釋放,就會dump heap生成hprof文件,藉由shark庫進行解析,構建泄漏對象最短引用路徑。最後將結果在通知欄和Activity進行渲染顯示。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章