源碼淺析Android中內存泄漏檢測工具Leakcanary的使用

前言

本博客將分析一下大名鼎鼎的 Leakcanary 想必作為 Android 開發都多多少少接觸過,新版本的 Leakcanary 也用 Kotlin 重寫瞭一遍,最近詳細查看瞭下源碼,分享一下。

tips:本來是隻想分析下內存泄漏檢測部分,但寫著寫著就跑偏瞭,因為內存泄漏的檢測難點在於對對象生命周期的把控, Leakcanary 對於 Service 生命周期的把控我覺得非常值得我們學習,並且在項目中也會用到。外加 Leakcanary 用 Kotlin 重寫,一些語法糖我平時也沒用過,就順便寫瞭下,整體讀下來有點囉嗦。

源碼版本

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'

內存泄漏

本博客著重分析 leakcanary 源碼實現原理以及一些優秀設計,對於內存泄漏的解釋就簡單用我自己的理解來解釋下:短生命周期對象持有長生命周期對象,當短生命周期對象需要被回收時因其持有長生命周期對象導致無法正常回收的情況;

源碼淺析

在瞭解其優秀設計之前先來簡單分析下其源碼以及實現原理。

檢測原理

Leakcanary 檢測內存泄漏的原理很簡單,就是利用弱引用 WeakReference 的雙參數構造方法

WeakReference(T referent, ReferenceQueue<? super T> q)

來檢測被弱引用的對象是否被正常回收、釋放,舉個例子:

// 定義類
class A
// 檢測的目標對象
val obj = A()
val queue = ReferenceQueue<A>()
val weakObj = WeakReference(obj, queue)
// 觸發gc回收(註意:這樣的操作不一定可以觸發gc,具體如何觸發gc 在下面的源碼分析中有提到 leakcanary 是如何觸發gc的)
System.gc()

val tmp = queue.poll()
if (tmp === obj) {
    // 被回收
} else {
    // 未回收
}

Android 開發中的 Activity、Fragment、Service、自定義 View 都是容易發生內存泄漏的對象,Leakcanary 所做的工作就是在合適的時機(一般是在回收時,如 Activity 的 onDestory 後)對這些對象進行弱引用並且關聯引用隊列,根據其是否被添加到引用隊列來判斷是否發生泄漏。

關於判斷一個對象是否發生泄漏的原理上面的示例代碼已經簡單演示,下面我們就順著源碼來看看 Leakcanary 的實現細節。

初始化

Leakcanary 僅需引入依賴即可完成初始化,放到現在這也不算多麼神奇的技巧瞭,這是利用瞭 ContentProvider。

ContentProvider 的初始化時機在 Application 的 onCreate 之前,並且在 ContentProvider 的 onCreate 方法中可以獲取到 context、applicationContext。

當項目引入 Leakcanary 後打包出的 apk 的清單文件中可以找到註冊瞭MainProcessAppWatcherInstaller,其關鍵源碼部分如下:

internal class MainProcessAppWatcherInstaller : ContentProvider() {
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application)
    return true
  }
  //..
}

可以看出調用瞭 AppWatcher.manualInstall() 進行瞭初始化,其源碼如下:

AppWatcher.kt

fun manualInstall(
  application: Application,
  retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5), // 默認 5s
  watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application) // 獲取默認Watchers 下面詳細分析
) {
  // 檢查是否在主線程
  // 原理:Looper.getMainLooper().thread === Thread.currentThread()
  checkMainThread()
  // ...
  this.retainedDelayMillis = retainedDelayMillis
  // 初始化 Shark 庫
  if (application.isDebuggableBuild) {
    LogcatSharkLog.install()
  }
  // 這行代碼下面詳細分析
  LeakCanaryDelegate.loadLeakCanary(application)
  // 對 watchers 遍歷調用 install 
  watchersToInstall.forEach {
    it.install()
  }
  // 給 installCause 賦值,代表已經初始化
  installCause = RuntimeException("manualInstall() first called here")
}

appDefaultWatchers(application)

上述初始化方法中第三個參數 watchersToInstall 被賦予瞭默認值,通過 appDefaultWatchers 獲取瞭一個 List<InstallableWatcher>,先看下 InstallableWatcher 源碼:

interface InstallableWatcher {
  fun install()
  fun uninstall()
}

是一個接口,定義瞭兩個方法看命名也能明白是安裝和卸載,接著看下 appDefaultWatchers 方法返回瞭什麼:

fun appDefaultWatchers(
  application: Application,
  reachabilityWatcher: ReachabilityWatcher = objectWatcher // 註意這個 objectWatcher 很重要
): List<InstallableWatcher> {
  return listOf(
    ActivityWatcher(application, reachabilityWatcher), // 用於監控activity內存泄漏
    FragmentAndViewModelWatcher(application, reachabilityWatcher),// 用於監控fragment,viewmodel 內存泄漏
    RootViewWatcher(reachabilityWatcher),// 用於監聽 rootview 內存泄漏
    ServiceWatcher(reachabilityWatcher) // 用於監聽 service 內存泄漏
  )
}

註意上述方法的第二個參數 reachabilityWatcher 默認賦值瞭 objectWatcher:

val objectWatcher = ObjectWatcher(...)

先記住他是 ObjectWatcher 類的實例,並且將其傳遞給瞭用於檢測的各個 InstallableWatcher 實現類。

LeakCanaryDelegate.loadLeakCanary(application)

接著再回過頭來看一下 LeakCanaryDelegate.loadLeakCanary(application) 這句代碼,loadLeakCanary 作為 LeakCanaryDelegate 類中的一個函數類型變量,所以可以直接調用,看一下其源碼:

internal object LeakCanaryDelegate {
  // 延遲初始化
  val loadLeakCanary by lazy {
    try {
      // 默認加載 InternalLeakCanary 獲取其 INSTANCE 字段
      // InternalLeakCanary 是一個 object class,編譯為 java 後會自動生成 INSTANCE
      // 就是一個單例類 這裡是獲取其單例對象
      val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
      leakCanaryListener.getDeclaredField("INSTANCE")
        .get(null) as (Application) -> Unit
    } catch (ignored: Throwable) {
      // 出現異常時返回 NoLeakCanary
      NoLeakCanary
    }
  }
  // (Application) -> Unit 函數類型變量,接受一個 application 作為參數
  // 內部都是空實現
  object NoLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
    override fun invoke(application: Application) {}
    override fun onObjectRetained() {}
  }
}

一般情況下 LeakCanaryDelegate.loadLeakCanary(application) 就相當於調用瞭 InternalLeakCanary,當然 InternalLeakCanary 也是 (Application) -> Unit 的實現瞭,對你沒看錯,函數類型不僅可以聲明變量,也可以定義實現類,但需要實現 invoke 方法,看下其源碼:

internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
    override fun invoke(application: Application) {
      _application = application
      checkRunningInDebuggableBuild() // 檢查是否是 debug 模式
      // 對 AppWatcher 的 objectWatcher 添加 listener
      AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
      // 這個用於觸發 gc 回收,下面會分析一下
      val gcTrigger = GcTrigger.Default
      // 獲取相關配置
      val configProvider = { LeakCanary.config }
      // 啟動瞭一個 HandlerThread
      val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
      handlerThread.start()
      val backgroundHandler = Handler(handlerThread.looper)
      // 初始化堆轉儲對象,上面定義的一些變量也都傳入瞭進去
      heapDumpTrigger = HeapDumpTrigger(
        application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger,
        configProvider
      )
      // Leakcanary 對 Application 的一個擴展方法
      // 這裡的作用是在 App 前後臺切換時執行閉包中的邏輯 也就是調用堆轉儲的 onApplicationVisibilityChanged 方法
      application.registerVisibilityListener { applicationVisible ->
        this.applicationVisible = applicationVisible
        heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
      }
      // 這個方法代碼比較簡單利用 Application.registerActivityLifecycleCallbacks
      // 獲取處於激活狀態的 Activity 賦值給 resumedActivity
      registerResumedActivityListener(application)
      // 添加桌面快捷方式,也就是Leakcanary小黃鳥app圖標,感興趣的可以看一下 這裡就不詳細分析這個方法瞭
      addDynamicShortcut(application)
      // ...
    }

}

實現細節

從初始化部分源碼中可以看出對於內存泄漏監控的核心部分就是 appDefaultWatchers 方法中返回的四個InstallableWatcher 的實現類,下面我們依次來分析。

ObjectWatcher

在上述分析中四個實現類在初始化時都傳入瞭 objectWatcher,所以有必要先來初步瞭解下:

AppWatcher.kt

val objectWatcher = ObjectWatcher(
  // 返回一個 Clock 對象
  clock = { SystemClock.uptimeMillis() },
  // Executor 對象
  checkRetainedExecutor = {
    // 切換到主線程延遲執行, retainedDelayMillis 默認是 5s
    mainHandler.postDelayed(it, retainedDelayMillis)
  },
  // 這個參數是 () -> Boolean 函數類型,默認是直接返回一個 true
  isEnabled = { true }
)

我第一次看到這個clock = { SystemClock.uptimeMillis() } 是有點沒看懂,所以先來分析下這個寫法,先看看這個 Clock 源碼:

// 註意這個 fun interface 是 kotlin 在 1.4 引入的 函數式接口
// 接口中隻能有一個未實現的方法
fun interface Clock {
  fun uptimeMillis(): Long
  // ...
}

clock = { SystemClock.uptimeMillis() } 就等價於以下代碼:

clock = object : Clock{
    override fun uptimeMillis(): Long {
        SystemClock.uptimeMillis()
    }
}

大概先瞭解下 objectWatcher 的參數部分,具體方法待下面用到時具體分析。

ActivityWatcher

先來看看我們最熟的 Activity 是如何進行內存泄漏監控的,ActivityWatcher 源碼如下:

class ActivityWatcher(
  private val application: Application,
  // 註意這裡,傳進來的是 AppWatcher 的 objectWatcher
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        // 核心就在這一行代碼裡 這裡也就是調用瞭 objectWatcher 的 expectWeaklyReachable 方法
        reachabilityWatcher.expectWeaklyReachable(
          activity, "${activity::class.java.name} received Activity#onDestroy() callback"
        )
      }
    }

  override fun install() {
    // 利用 Application 監聽 Activity onDestory 生命周期方法
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
}

直接查看 ObjectWatcher 的 expectWeaklyReachable 方法源碼:

ObjectWatcher.kt

// 保存監控對象的 map
private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()
// 用於弱引用關聯隊列
private val queue = ReferenceQueue<Any>()

@Synchronized override fun expectWeaklyReachable(
  watchedObject: Any, // 傳入的 activity(已經 onDestory)
  description: String // 描述
) {
  if (!isEnabled()) { // 默認 isEnabled 返回 true,不會進入這個 if
    return
  }
  // 這個代碼比較簡單 我就簡單說下作用
  // 當 watchedObject 成功被回收會添加進 queue 中,這個方法就是將添加到 queue 中的對象
  // 從 watchedObjects 中移除,被移除的說明沒有發生內存泄漏
  removeWeaklyReachableObjects()
  // 生成id (相當於對象的身份證)
  val key = UUID.randomUUID()
    .toString()
  // 獲取時間戳
  val watchUptimeMillis = clock.uptimeMillis()
  // 將對象以及一些信息包裝為 KeyedWeakReference
  val reference =
    KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
  // ...
  // 包裝好的對象放入 watchedObjects 這個 map 中
  watchedObjects[key] = reference
  // checkRetainedExecutor 別忘瞭,就是切換到主線程延遲 5s 執行
  checkRetainedExecutor.execute {
    // 主線程調用
    moveToRetained(key)
  }
}

KeyedWeakReference

class KeyedWeakReference(
  referent: Any, // 監控對象,這裡是 onDestory 的 Activity
  val key: String, // UUID 身份證
  val description: String, // 描述
  val watchUptimeMillis: Long, // 時間
  referenceQueue: ReferenceQueue<Any> // 當 referent 被回收會添加到這個隊列中
) : WeakReference<Any>(referent, referenceQueue)
//最後調用瞭 WeakReference 的雙參數構造器

這個包裝相當於對 WeakReference 做瞭一些擴展,可以通過 key 從前面的 watchedObjects 中移除或者獲取。

moveToRetained

再來看看 moveToRetained 方法源碼:

@Synchronized private fun moveToRetained(key: String) {
  // 再次嘗試從 queue 中獲取回收成功的對象,從 watcherObjects 中移除
  removeWeaklyReachableObjects()
  val retainedRef = watchedObjects[key]
  if (retainedRef != null) { // 如果還可以獲取到代表回收失敗 發生瞭內存泄漏
    retainedRef.retainedUptimeMillis = clock.uptimeMillis()
    // 遍歷 onObjectRetainedListeners 對象調用其 onObjectRetained 方法
    onObjectRetainedListeners.forEach { it.onObjectRetained() }
  }
}

註意這個 onObjectRetainedListeners,在初始化的第二小節我們分析的 InternalLeakCanary 源碼中有一處調用: AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

這裡的遍歷調用一般情況下就相當於調用瞭 AppWatcher 的 onObjectRetained 方法,看下其源碼部分:

InternalLeakCanary.kt

// 又調用瞭 scheduleRetainedObjectCheck 方法
override fun onObjectRetained() = scheduleRetainedObjectCheck()

fun scheduleRetainedObjectCheck() {
  if (this::heapDumpTrigger.isInitialized) {
    // 又調用到瞭 堆轉儲 對象裡的 scheduleRetainedObjectCheck 方法
    heapDumpTrigger.scheduleRetainedObjectCheck()
  }
}

繼續跟蹤源碼,查看 HeapDumpTrigger 類中的 scheduleRetainedObjectCheck 方法:

HeapDumpTrigger.kt

fun scheduleRetainedObjectCheck(
  delayMillis: Long = 0L
) {
  val checkCurrentlyScheduledAt = checkScheduledAt
  if (checkCurrentlyScheduledAt > 0) {
    return
  }
  checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
  // backgroundHandler 前面看過瞭 是一個 HandlerThread 這裡是放到子線程執行
  backgroundHandler.postDelayed({
    checkScheduledAt = 0
    // 重點是這個方法
    checkRetainedObjects()
  }, delayMillis)
}

接著查看 checkRetainedObjects 方法源碼:

private fun checkRetainedObjects() {
  val iCanHasHeap = HeapDumpControl.iCanHasHeap()

  val config = configProvider()
  // ...
  // 獲取仍然存在於 map 中沒有被回收的對象數量
  var retainedReferenceCount = objectWatcher.retainedObjectCount
  // 如果有再執行一次 gc
  if (retainedReferenceCount > 0) {
    gcTrigger.runGc() // gc 操作,註意這裡的實現 後面會貼源碼
    retainedReferenceCount = objectWatcher.retainedObjectCount // 再次獲取數量
  }
  // 裡面是對app 是否在前臺,存活對象數量的判斷,retainedVisibleThreshold 默認是 5
  if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
  
  val now = SystemClock.uptimeMillis()
  val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
  // 判斷上次堆轉儲時間間隔是否小於 60s
  if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
    onRetainInstanceListener.onEvent(DumpHappenedRecently)
    showRetainedCountNotification(
      objectCount = retainedReferenceCount,
      contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
    )
    // 小於60s 就延遲對應時間在重新執行該方法
    scheduleRetainedObjectCheck(
      delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
    )
    return
  }
  
  dismissRetainedCountNotification()
  val visibility = if (applicationVisible) "visible" else "not visible"
  // 開始堆轉儲
  dumpHeap(
    retainedReferenceCount = retainedReferenceCount,
    retry = true,
    reason = "$retainedReferenceCount retained objects, app is $visibility"
  )
}

註意上述的 gcTrigger.runGc() 觸發 gc 操作,這段代碼看一下 Leakcanary 是如何實現的:

object Default : GcTrigger {
  override fun runGc() {
    // 這段執行gc的代碼 leakcanary 註釋中表示是借鑒於 Android 系統源碼片段
    // System.gc() 並不是每次執行都能觸發gc,而 Runtime gc() 更容易觸發
    // 這裡的源碼細節值得借鑒
    Runtime.getRuntime().gc()
    enqueueReferences()
    System.runFinalization()
  }
  // ...
}

到這裡源碼還剩下兩個部分:1. 獲取堆信息;2. 分析堆信息;先說下第二點 Leakcanary 在 2.x 之前的版本使用 haha 這個庫來解析 .hprof 文件,而 2.x 之後另起爐灶用 shark 庫來完成這一工作,很遺憾我對 shark 的庫不夠瞭解,這部分就暫時不深入分析瞭。

來看看 Leakcanary 是如何獲取堆信息寫入文件的,dumpHeap 方法源碼:

private fun dumpHeap(
  retainedReferenceCount: Int,
  retry: Boolean,
  reason: String
) {
  // 註意這個 directoryProvider 並不是 ContentProvider 隻是一個普通類
  val directoryProvider =
    InternalLeakCanary.createLeakDirectoryProvider(InternalLeakCanary.application)
  // 新建堆轉儲文件,內部包含一些權限、目錄等等邏輯判斷 比較簡單就不貼代碼瞭
  // 生成文件名:fileName = SimpleDateFormat("yyyy-MM-dd_HH-mm-ss_SSS'.hprof'", Locale.US).format(Date())
  val heapDumpFile = directoryProvider.newHeapDumpFile()

  val durationMillis: Long
  if (currentEventUniqueId == null) {
    currentEventUniqueId = UUID.randomUUID().toString()
  }
  try {
    // 通知欄 Event
    InternalLeakCanary.sendEvent(DumpingHeap(currentEventUniqueId!!))
    // ...
    durationMillis = measureDurationMillis {
      // 這裡是堆轉儲核心
      configProvider().heapDumper.dumpHeap(heapDumpFile)
    }
    // 觸發後面的分析方法
    InternalLeakCanary.sendEvent(HeapDump(currentEventUniqueId!!, heapDumpFile, durationMillis, reason))
  } catch (throwable: Throwable) {
    // 堆轉儲失敗
    InternalLeakCanary.sendEvent(HeapDumpFailed(currentEventUniqueId!!, throwable, retry))
    if (retry) { // 重試則延遲再執行一次方法
      scheduleRetainedObjectCheck(
        delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
      )
    }
    // 通知欄展示
    showRetainedCountNotification(
      objectCount = retainedReferenceCount,
      contentText = application.getString(
        R.string.leak_canary_notification_retained_dump_failed
      )
    )
    return
  }
}

configProvider().heapDumper.dumpHeap(heapDumpFile) 這行代碼是堆轉儲的核心,來看一下其源碼:

AndroidDebugHeapDumper.kt

object AndroidDebugHeapDumper : HeapDumper {
  override fun dumpHeap(heapDumpFile: File) {
    // 利用 Android 本身的 Debug dumpHprofData 方法實現
    Debug.dumpHprofData(heapDumpFile.absolutePath)
  }
}

堆轉儲的實現非常簡單,系統源碼中自帶瞭對應方法,傳入文件即可。

FragmentAndViewModelWatcher

接著來看看對於 Fragment 和 ViewModel 是如何進行檢測的,直接看源碼:

和 ActivityWatcher 如出一轍,同樣利用 Application 的 api 註冊對 Activity 的監聽,在 Activity onCreate 生命周期時遍歷 fragmentDestoryWatchers 進行調用,接著來看看 fragmentDestoryWatchers 的定義:

private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
  // 註意這個 list 的類型,item 是函數式變量,相當於方法可以直接調用
  val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
  
  // sdk26 以上添加 AndroidOFragmentDestroyWatcher
  if (SDK_INT >= O) {
    fragmentDestroyWatchers.add(
      // reachabilityWatcher 是老朋友 objectWatcher 瞭
      AndroidOFragmentDestroyWatcher(reachabilityWatcher)
    )
  }
  // 通過反射獲取 AndroidXFragmentDestroyWatcher
  // 對應 androidX 的 fragment
  getWatcherIfAvailable(
    ANDROIDX_FRAGMENT_CLASS_NAME, // "androidx.fragment.app.Fragment"
    ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME, // "AndroidXFragmentDestroyWatcher"
    reachabilityWatcher
  )?.let {
    fragmentDestroyWatchers.add(it)
  }
  
  // 通過反射獲取 AndroidSupportFragmentDestroyWatcher
  // 對應之前的 android support 包下的 Fragment
  getWatcherIfAvailable(
    ANDROID_SUPPORT_FRAGMENT_CLASS_NAME, // "android.support.v4.app.Fragment"
    ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME, // "AndroidSupportFragmentDestroyWatcher"
    reachabilityWatcher
  )?.let {
    fragmentDestroyWatchers.add(it)
  }
  // 返回
  fragmentDestroyWatchers
}

依次來看看上面定義的三個 item 實現細節。

AndroidOFragmentDestroyWatcher

reachabilityWatcher.expectWeaklyReachable() 方法在 ActiviyWatcher 小節中已經詳細分析瞭,就不再贅述瞭。

AndroidXFragmentDestroyWatcher

和 AndroidOFragmentDestroyWatcher 一樣是通過 Activity 來監聽 Fragment 的生命周期,差別在於註冊監聽的 api :

internal class AndroidXFragmentDestroyWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {
  
  // 和上面的 AndroidOFragmentDestroyWatcher 一樣 就不貼代碼瞭
  private val fragmentLifecycleCallbacks = // ...

  override fun invoke(activity: Activity) {
    if (activity is FragmentActivity) {
      // 和 AndroidOFragmentDestroyWatcher 的原理是一樣的
      val supportFragmentManager = activity.supportFragmentManager
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
      // 註意這裡,多瞭一個針對 ViewModel 的處理
      ViewModelClearedWatcher.install(activity, reachabilityWatcher)
    }
  }
}

這裡增加瞭對 ViewModel 的監聽,看一下其是如何實現的。

ViewModelClearedWatcher

直接看源碼:

// 註意這是一個 ViewModel 的子類
internal class ViewModelClearedWatcher(
  storeOwner: ViewModelStoreOwner,
  private val reachabilityWatcher: ReachabilityWatcher
) : ViewModel() {

  companion object {
    // 外部調用的 install 方法
    fun install(
      storeOwner: ViewModelStoreOwner, // 這裡也就是 activity
      reachabilityWatcher: ReachabilityWatcher // objectWatcher
    ) {
      // 相當於再 Activity onCreate 方法中又創建瞭一個 ViewModel
      val provider = ViewModelProvider(storeOwner, object : Factory {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(modelClass: Class<T>): T =
          ViewModelClearedWatcher(storeOwner, reachabilityWatcher) as T
      })
      provider.get(ViewModelClearedWatcher::class.java)
    }
  }
  
  // 反射 activity 中的 ViewModelStore 中的 mMap 字段
  // 這裡面存儲著 activity 中創建的 ViewModel
  private val viewModelMap: Map<String, ViewModel>? = try {
    val mMapField = ViewModelStore::class.java.getDeclaredField("mMap")
    mMapField.isAccessible = true
    mMapField[storeOwner.viewModelStore] as Map<String, ViewModel>
  } catch (ignored: Exception) {
    null
  }
  
  // ViewModel 銷毀時觸發的方法
  override fun onCleared() {
    viewModelMap?.values?.forEach { viewModel ->
      // 進行檢測
      reachabilityWatcher.expectWeaklyReachable(
        viewModel, "${viewModel::class.java.name} received ViewModel#onCleared() callback"
      )
    }
  }
}

如果對 ViewModel 這部分不理解的可以看下我之前對於 ViewModel 源碼分析的博客 ————Android 源碼淺析:Jetpack 組件 —— ViewModel。

AndroidSupportFragmentDestroyWatcher

最後再來看一下 AndroidSupportFragmentDestroyWatcher,猜也能猜到和上面的邏輯是一樣的,僅僅是為瞭支持 AndroidSupport 包:

internal class AndroidSupportFragmentDestroyWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : (Activity) -> Unit {
  // 還是和上面一樣的
  private val fragmentLifecycleCallbacks = // ...

  override fun invoke(activity: Activity) {
    if (activity is FragmentActivity) {
      // AndroidSupport 包的 api
      val supportFragmentManager = activity.supportFragmentManager
      supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, true)
    }
  }
}

RootViewWatcher

這個類的源碼呢,就不貼瞭,因為涉及到瞭 Square 的另一個庫 Curtains,這個庫的作用就是可以監聽 window 的生命周期。很遺憾我對這個庫的瞭解也不到位,就不瞎說八道瞭。

不過這裡可以稍微總結一下,對於監聽對象是否泄漏很關鍵的一個點就是能夠監聽到對象的生命周期,也就是能監聽到對象的銷毀。

下一小節是值得我們學習的一個小節,監聽 Service 的銷毀,看下 Leakcanary 是如何做到的。

ServiceWatcher (重點 值得學習借鑒)

對於 Service 的生命周期,我們都知道 Service 銷毀時會走 onDestory,但是系統並沒有提供像監聽 Activity 的 Application.registerActivityLifecycleCallbacks 這樣的 api,這一小節就重點分析下 Leakcanary 是如何做到的。

直接看源碼:

class ServiceWatcher(private val reachabilityWatcher: ReachabilityWatcher) : InstallableWatcher {
    override fun install() {
      checkMainThread() // 檢測主線程
      // ...
      try {
        // 重點 1: 反射 ActivityThread 的 mH 中的 mCallback
        // 也就是 Handler 的 mCallback
        swapActivityThreadHandlerCallback { mCallback ->
          uninstallActivityThreadHandlerCallback = {
            swapActivityThreadHandlerCallback {
              mCallback
            }
          }
          Handler.Callback { msg ->
            if (msg.obj !is IBinder) {
              return@Callback false
            }
            if (msg.what == STOP_SERVICE) { // AMS Service onDestory 的 message 的 what
              val key = msg.obj as IBinder
              // 註意這個 activityThreadServices
              // 這個是反射獲取的 ActivityThread 中的 mServices 字段是 Map<IBinder, Service> 類型
              // 這個字段存放所有啟動的 Service
              activityThreadServices[key]?.let {
                // 這裡是將要銷毀的 Service 進行存儲
                // key 是 IBinder 對象
                onServicePreDestroy(key, it)
              }
            }
            mCallback?.handleMessage(msg) ?: false
          }
        }
        // 重點 2:反射獲取 AMS (ActivityManagerService)
        swapActivityManager { activityManagerInterface, activityManagerInstance ->
          uninstallActivityManager = {
            swapActivityManager { _, _ ->
              activityManagerInstance
            }
          }
          // 對 AMS 進行動態代理 
          Proxy.newProxyInstance(
            activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
          ) { _, method, args ->
            // 判斷方法名是否是 serviceDoneExecuting 
            if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
              // 獲取 IBinder 對象
              val token = args!![0] as IBinder
              // 從上面存儲的 service 容器中查看是否存在對應 service
              if (servicesToBeDestroyed.containsKey(token)) {
                // 進行內存泄漏檢測
                onServiceDestroyed(token)
              }
            }
            // ...
          }
        }
      } 
      // ...
    }
    
    private fun onServiceDestroyed(token: IBinder) {
      // 從 servicesToBeDestroyed 中移除
      servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
        serviceWeakReference.get()?.let { service ->
          // 利用 objectWatcher 進行內存泄漏檢測
          reachabilityWatcher.expectWeaklyReachable(
            service, "${service::class.java.name} received Service#onDestroy() callback"
          )
        }
      }
    }
}

可以看出對於 Service 進行內存泄漏檢測的核心有兩步驟:

  • 反射 Activity 的 mH 的 mCallback 獲取即將銷毀的 Service 對象;
  • 對 AMS 進行動態代理監聽 Service 的銷毀時機。

總結

內存泄漏檢測的原理非常簡單,利用弱引用的雙參數構造傳入引用隊列,掌握被檢測對象的生命周期,在銷毀時檢查引用隊列的情況。我們自己也能很輕松的寫出對應 Demo,但 Leakcanary 中很多優秀設計非常值得學習,如:無感初始化(ContentProvider),一些 Kotlin 語法糖,對於 Service 生命周期的監聽。

尤其是最後對於 Service 生命周期的監聽,通過反射系統類,結合動態代理很輕松就實現瞭,由此我們也可以延伸出對 Service 其他生命周期的監聽。

內存泄漏檢測並不難,難點在於分析堆信息以及對於各種對象的生命周期把控。

以上就是源碼淺析Android中內存泄漏檢測工具Leakcanary的使用的詳細內容,更多關於Android Leakcanary內存泄漏檢測的資料請關註WalkonNet其它相關文章!

推薦閱讀: