LeakCanary 原理詳解

前言

本文是針對 LeakCanary Version 2.7 (2021-03-26) 版本的源碼進行的分析纵势。

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

1. 基礎知識

  • java 引用之弱引用 WeakReference

弱引用, 當一個對象僅僅被weak reference(弱引用)指向, 而沒有任何其他strong reference(強引用)指向的時候, 如果這時GC運行, 那么這個對象就會被回收,不論當前的內存空間是否足夠,這個對象都會被回收。
構造方法:

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

主要說一下第二構造方法的第二個參數 ReferenceQueue。當WeakReference引用的對象referent被回收后,會把這個WeakReference或其子類的對象澜术,放入隊列ReferenceQueue中。敲黑板猬腰,LeakCanary 原理中的重要一環(huán)鸟废。

  • gc

gc 的可達性分析算法和引用計數法;
Java中可以作為GC Roots的對象:
1姑荷、虛擬機棧(javaStack)(棧幀中的局部變量區(qū)盒延,也叫做局部變量表)中引用的對象缩擂。
2、方法區(qū)中的類靜態(tài)屬性引用的對象兰英。
3撇叁、方法區(qū)中常量引用的對象。
4畦贸、本地方法棧中JNI(Native方法)引用的對象陨闹。

  • ContentProvider

ContentProvider初始化(onCreate)是在Application生命周期方法attachBaseContext之后、onCreate之前薄坏。

2. 源碼分析

2.1 流程概述

為了腦中有個框架趋厉,便于理解。我將源碼的分析過程分成三個步驟胶坠,接下來會依次按照這三個步驟去進行分析君账。圖中涉及的類是調用的說明,不存在繼承關系沈善。


調用流程分析圖.jpg
2.1.1 注冊觀察對象

注冊流程就是LeakCanary的初始化過程乡数,我們首先分析入口類 AppWatcherInstaller

  • AppWatcherInstaller
/**
 * Content providers are loaded before the application class is created. [AppWatcherInstaller] is
 * used to install [leakcanary.AppWatcher] on application start.
 */
internal sealed class AppWatcherInstaller : ContentProvider() {

  /**
   * [MainProcess] automatically sets up the LeakCanary code that runs in the main app process.
   */
  internal class MainProcess : AppWatcherInstaller()
···
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application)
    return true
  }
···

根據源碼可知,AppWatcherInstaller是一個ContentProvider闻牡,它在app啟動的時候就會自動調用onCreate中的代碼净赴。

<?xml version="1.0" encoding="utf-8"?>
<manifest
    xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.squareup.leakcanary.objectwatcher>
  <application>
    <provider
        android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
        android:authorities="${applicationId}.leakcanary-installer"
        android:enabled="@bool/leak_canary_watcher_auto_install"
        android:exported="false"/>
  </application>
</manifest>

AppWatcherInstaller 在 manifest 中注冊源碼如上。接著我們分析 onCreate 中的關鍵代碼 AppWatcher.manualInstall(application)

  • AppWatcher
/**
 * The entry point API for using [ObjectWatcher] in an Android app. [AppWatcher.objectWatcher] is
 * in charge of detecting retained objects, and [AppWatcher] is auto configured on app start to
 * pass it activity and fragment instances. Call [ObjectWatcher.watch] on [objectWatcher] to
 * watch any other object that you expect to be unreachable.
 */
object AppWatcher {
···
/**
   * The [ObjectWatcher] used by AppWatcher to detect retained objects.
   * Only set when [isInstalled] is true.
   */
  val objectWatcher = ObjectWatcher(
    clock = { SystemClock.uptimeMillis() },
    checkRetainedExecutor = {
      check(isInstalled) {
        "AppWatcher not installed"
      }
      mainHandler.postDelayed(it, retainedDelayMillis)
    },
    isEnabled = { true }
  )
···
  @JvmOverloads
  fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
  ) {
    checkMainThread()
    if (isInstalled) {
      throw IllegalStateException(
        "AppWatcher already installed, see exception cause for prior install call", installCause
      )
    }
    check(retainedDelayMillis >= 0) {
      "retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
    }
    installCause = RuntimeException("manualInstall() first called here")
    this.retainedDelayMillis = retainedDelayMillis
    if (application.isDebuggableBuild) {
      LogcatSharkLog.install()
    }
    // Requires AppWatcher.objectWatcher to be set
    LeakCanaryDelegate.loadLeakCanary(application)

    watchersToInstall.forEach {
      it.install()
    }
  }
   /**
   * Creates a new list of default app [InstallableWatcher], created with the passed in
   * [reachabilityWatcher] (which defaults to [objectWatcher]). Once installed,
   * these watchers will pass in to [reachabilityWatcher] objects that they expect to become
   * weakly reachable.
   *
   * The passed in [reachabilityWatcher] should probably delegate to [objectWatcher] but can
   * be used to filter out specific instances.
   */
  fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
  ): List<InstallableWatcher> {
    return listOf(
      ActivityWatcher(application, reachabilityWatcher),
      FragmentAndViewModelWatcher(application, reachabilityWatcher),
      RootViewWatcher(reachabilityWatcher),
      ServiceWatcher(reachabilityWatcher)
    )
  }
···
}

manualInstall 方法有三個參數罩润,application 會在 AppWatcherInstaller 中傳參玖翅, retainedDelayMillis 和watchersToInstall會走默認值。這里我們著重看下watchersToInstall 這個參數割以,這個參數的默認值是appDefaultWatchers方法返回的一個InstallableWatcher列表金度。列表中有ActivityWatcher、FragmentAndViewModelWatcher严沥、RootViewWatcher和ServiceWatcher四個對象猜极。四個對象構造方法都有一個公共參數reachabilityWatcher,用objectWatcher進行了賦值消玄。這個列表在源碼中遍歷魔吐,并調用 item 的 install()方法。我們用ActivityWatcher 看看 install() 中干了什么莱找。

/**
 * Expects activities to become weakly reachable soon after they receive the [Activity.onDestroy]
 * callback.
 */
class ActivityWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

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

  override fun install() {
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

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

ActivityWatcher.install() 注冊了Activity 的生命周期回調,在 onActivityDestroyed 回調的時候調用了reachabilityWatcher.expectWeaklyReachable()方法嗜桌。其實就是調用了AppWatcher中的objectWatcher對象方法奥溺。接著繼續(xù)看ObjectWatcher

  • ObjectWatcher
class ObjectWatcher constructor(
  private val clock: Clock,
  private val checkRetainedExecutor: Executor,
  /**
   * Calls to [watch] will be ignored when [isEnabled] returns false
   */
  private val isEnabled: () -> Boolean = { true }
) : ReachabilityWatcher {

  private val onObjectRetainedListeners = mutableSetOf<OnObjectRetainedListener>()

  /**
   * References passed to [watch].
   */
  private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()

  private val queue = ReferenceQueue<Any>()
···
  @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)
    }
  }
···
  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)
  }
···
}

ObjectWatcher 中有兩個成員變量,watchedObjects 和 queue 骨宠。根據上面對弱引用的了解浮定,queue 存儲的是已經回收引用對象的 reference相满。前面 activity的ondestory生命周期回調引發(fā)了expectWeaklyReachable 方法的調用,會首先走removeWeaklyReachableObjects方法桦卒,去移除watchedObjects 中已經被回收引用對象的reference立美,保證watchedObjects中的reference所引用的對象沒有被回收。接著為當前 watchedObject 生成一個key 值方灾,然后再放入watchedObjects中建蹄。objectWatcher是在AppWatcher中創(chuàng)建的,checkRetainedExecutor 是一個線程池裕偿,并通過mainHandler延時5秒執(zhí)行了一個 runnable洞慎。這個runnable 執(zhí)行會調用ObjectWatcher 的 moveToRetained方法。到這里先停一下嘿棘,前面選了ActivityWatcher 源碼梳理到這里劲腿,最終確定了LeakCanary 將已經執(zhí)行了 onDestory生命周期的activity,通過 KeyedWeakReference 保存在了ObjectWatcher的watchedObjects這個map里鸟妙。和ActivityWatcher一樣焦人,前面其他 三個watcher觀測的對象按照一定的策略也會保存在這里。

到這里就梳理了LeakCanary的Watcher注冊流程重父,ContentProvider的子類AppWatchInstaller是一切開始的地方花椭。也分析了觀察的可能泄漏的對象在一定的時機會被放在ObjectWatcher的watchedObjects中,比如前面的onActivityDestroyed 回調時機坪郭。ObjectWatcher是一個核心類个从,判斷是否有內存泄漏,它是重要的一環(huán)歪沃。

2.1.2 觸發(fā)內存檢查

LeakCanary 中觸發(fā)內存檢查的時機可以分為兩種:

  1. 觀測對象可能要銷毀的時機
    -- activity的onActivityDestroyed
    -- service的onServiceDestroyed
    -- rootview的onViewDetachedFromWindow
    -- fragment的onFragmentViewDestroyed
  2. 未熄屏的前提下嗦锐,整個應用app對用戶不可見的時候

怎么得出的這個結論,我們接著上一小節(jié)繼續(xù)分析沪曙。前面我們分析到了ObjectWatcher的moveToRetained()方法被調用奕污,往下看moveToRetained這個方法。

@Synchronized private fun moveToRetained(key: String) {
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    // 判斷應用對象是否被回收
    if (retainedRef != null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      // 沒有回收液走,回調監(jiān)聽
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }

分析代碼碳默,最終回調了OnObjectRetainedListener監(jiān)聽,所以要看哪里添加了監(jiān)聽缘眶。添加監(jiān)聽的地方是 InternalLeakCanary 類的 invoke 方法中添加嘱根,代碼如下

internal object InternalLeakCanary : (Application) -> Unit, OnObjectRetainedListener {

  private const val DYNAMIC_SHORTCUT_ID = "com.squareup.leakcanary.dynamic_shortcut"

  private lateinit var heapDumpTrigger: HeapDumpTrigger

···

  override fun invoke(application: Application) {
    _application = application

    checkRunningInDebuggableBuild()
    //添加回調的地方
    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)

    val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))

    val gcTrigger = GcTrigger.Default

    val configProvider = { LeakCanary.config }

    val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
    handlerThread.start()
    val backgroundHandler = Handler(handlerThread.looper)

    heapDumpTrigger = HeapDumpTrigger(
      application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
      configProvider
    )
    // 這里注冊應用的可見性回調
    application.registerVisibilityListener { applicationVisible ->
      this.applicationVisible = applicationVisible
      heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
    }
    registerResumedActivityListener(application)
    // 這里添加小鳥的快捷圖標
    addDynamicShortcut(application)

    // We post so that the log happens after Application.onCreate()
    mainHandler.post {
      // https://github.com/square/leakcanary/issues/1981
      // We post to a background handler because HeapDumpControl.iCanHasHeap() checks a shared pref
      // which blocks until loaded and that creates a StrictMode violation.
      backgroundHandler.post {
        SharkLog.d {
          when (val iCanHasHeap = HeapDumpControl.iCanHasHeap()) {
            is Yup -> application.getString(R.string.leak_canary_heap_dump_enabled_text)
            is Nope -> application.getString(
              R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
            )
          }
        }
      }
    }
  }
···
override fun onObjectRetained() = scheduleRetainedObjectCheck()

  fun scheduleRetainedObjectCheck() {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.scheduleRetainedObjectCheck()
    }
  }
···
}

分析代碼,InternalLeakCanary 實現了 OnObjectRetainedListener 巷懈,它就是那個監(jiān)聽该抒。找到重載方法onObjectRetained() ,往下看最終是調用了heapDumpTrigger.scheduleRetainedObjectCheck()顶燕。敲黑板凑保,分析到這里得停一下冈爹,因為不知道是誰調用了invoke 方法,才添加了監(jiān)聽欧引∑瞪耍看代碼 InternalLeakCanary還是一個 Function1,參數為Application芝此,返回值為Unit憋肖,所以它重載了invoke()。沒有直接調用執(zhí)行InternalLeakCanary.invoke的地方癌蓖,那是在哪里調用的瞬哼。其實在 AppWatcher. manualInstall() 分析的時候有一行代碼,這行代碼通過代理的方式執(zhí)行了InternalLeakCanary 這個Function1租副。

LeakCanaryDelegate.loadLeakCanary(application)
internal object LeakCanaryDelegate {

  @Suppress("UNCHECKED_CAST")
  val loadLeakCanary by lazy {
    try {
      val leakCanaryListener = Class.forName("leakcanary.internal.InternalLeakCanary")
      leakCanaryListener.getDeclaredField("INSTANCE")
        .get(null) as (Application) -> Unit
    } catch (ignored: Throwable) {
      NoLeakCanary
    }
  }

  object NoLeakCanary : (Application) -> Unit, OnObjectRetainedListener {
    override fun invoke(application: Application) {
    }

    override fun onObjectRetained() {
    }
  }
}

invoke方法除了添加監(jiān)聽坐慰,還創(chuàng)建了HeapDumpTrigger對象;添加了一個 application.registerVisibilityListener 的一個回調用僧,這個回調就是這一節(jié)最開始說的應用不可見引起內存檢查的地方结胀,一會兒再看這塊的邏輯;invoke 中執(zhí)行addDynamicShortcut添加了一個動態(tài)快捷方式责循,敲黑板糟港,桌面上小鳥的由來。這些就是invoke做了的事院仿〗崭В回到剛才的分析heapDumpTrigger.scheduleRetainedObjectCheck() ,看這個方法做了哪些事情歹垫。

internal class HeapDumpTrigger(
  private val application: Application,
  private val backgroundHandler: Handler,
  private val objectWatcher: ObjectWatcher,
  private val gcTrigger: GcTrigger,
  private val heapDumper: HeapDumper,
  private val configProvider: () -> Config
) {
  ···
  private fun checkRetainedObjects() {
    val iCanHasHeap = HeapDumpControl.iCanHasHeap()

    val config = configProvider()

    if (iCanHasHeap is Nope) {
      if (iCanHasHeap is NotifyingNope) {
        // Before notifying that we can't dump heap, let's check if we still have retained object.
        var retainedReferenceCount = objectWatcher.retainedObjectCount

        if (retainedReferenceCount > 0) {
          gcTrigger.runGc()
          retainedReferenceCount = objectWatcher.retainedObjectCount
        }

        val nopeReason = iCanHasHeap.reason()
        val wouldDump = !checkRetainedCount(
          retainedReferenceCount, config.retainedVisibleThreshold, nopeReason
        )

        if (wouldDump) {
          val uppercaseReason = nopeReason[0].toUpperCase() + nopeReason.substring(1)
          onRetainInstanceListener.onEvent(DumpingDisabled(uppercaseReason))
          showRetainedCountNotification(
            objectCount = retainedReferenceCount,
            contentText = uppercaseReason
          )
        }
      } else {
        SharkLog.d {
          application.getString(
            R.string.leak_canary_heap_dump_disabled_text, iCanHasHeap.reason()
          )
        }
      }
      return
    }

    var retainedReferenceCount = objectWatcher.retainedObjectCount

    if (retainedReferenceCount > 0) {
      gcTrigger.runGc()
      retainedReferenceCount = objectWatcher.retainedObjectCount
    }

    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return

    val now = SystemClock.uptimeMillis()
    val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
    if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
      onRetainInstanceListener.onEvent(DumpHappenedRecently)
      showRetainedCountNotification(
        objectCount = retainedReferenceCount,
        contentText = application.getString(R.string.leak_canary_notification_retained_dump_wait)
      )
      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"
    )
  }

  private fun dumpHeap(
    retainedReferenceCount: Int,
    retry: Boolean,
    reason: String
  ) {
    saveResourceIdNamesToMemory()
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
    when (val heapDumpResult = heapDumper.dumpHeap()) {
      is NoHeapDump -> {
        if (retry) {
          SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" }
          scheduleRetainedObjectCheck(
            delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
          )
        } else {
          SharkLog.d { "Failed to dump heap, will not automatically retry" }
        }
        showRetainedCountNotification(
          objectCount = retainedReferenceCount,
          contentText = application.getString(
            R.string.leak_canary_notification_retained_dump_failed
          )
        )
      }
      is HeapDump -> {
        lastDisplayedRetainedObjectCount = 0
        lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
        objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
        HeapAnalyzerService.runAnalysis(
          context = application,
          heapDumpFile = heapDumpResult.file,
          heapDumpDurationMillis = heapDumpResult.durationMillis,
          heapDumpReason = reason
        )
      }
    }
  }

  private fun saveResourceIdNamesToMemory() {
    val resources = application.resources
    AndroidResourceIdNames.saveToMemory(
      getResourceTypeName = { id ->
        try {
          resources.getResourceTypeName(id)
        } catch (e: NotFoundException) {
          null
        }
      },
      getResourceEntryName = { id ->
        try {
          resources.getResourceEntryName(id)
        } catch (e: NotFoundException) {
          null
        }
      })
  }

  fun onDumpHeapReceived(forceDump: Boolean) {
    backgroundHandler.post {
      dismissNoRetainedOnTapNotification()
      gcTrigger.runGc()
      val retainedReferenceCount = objectWatcher.retainedObjectCount
      if (!forceDump && retainedReferenceCount == 0) {
        SharkLog.d { "Ignoring user request to dump heap: no retained objects remaining after GC" }
        @Suppress("DEPRECATION")
        val builder = Notification.Builder(application)
          .setContentTitle(
            application.getString(R.string.leak_canary_notification_no_retained_object_title)
          )
          .setContentText(
            application.getString(
              R.string.leak_canary_notification_no_retained_object_content
            )
          )
          .setAutoCancel(true)
          .setContentIntent(NotificationReceiver.pendingIntent(application, CANCEL_NOTIFICATION))
        val notification =
          Notifications.buildNotification(application, builder, LEAKCANARY_LOW)
        notificationManager.notify(
          R.id.leak_canary_notification_no_retained_object_on_tap, notification
        )
        backgroundHandler.postDelayed(
          scheduleDismissNoRetainedOnTapNotification,
          DISMISS_NO_RETAINED_OBJECT_NOTIFICATION_MILLIS
        )
        lastDisplayedRetainedObjectCount = 0
        return@post
      }

      SharkLog.d { "Dumping the heap because user requested it" }
      dumpHeap(retainedReferenceCount, retry = false, "user request")
    }
  }

  private fun checkRetainedCount(
    retainedKeysCount: Int,
    retainedVisibleThreshold: Int,
    nopeReason: String? = null
  ): Boolean {
    val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
    lastDisplayedRetainedObjectCount = retainedKeysCount
    //  沒有泄漏的對象
    if (retainedKeysCount == 0) {
      if (countChanged) {
        SharkLog.d { "All retained objects have been garbage collected" }
        onRetainInstanceListener.onEvent(NoMoreObjects)
        showNoMoreRetainedObjectNotification()
      }
      return true
    }

    val applicationVisible = applicationVisible
    val applicationInvisibleLessThanWatchPeriod = applicationInvisibleLessThanWatchPeriod

    if (countChanged) {
      val whatsNext = if (applicationVisible) {
        if (retainedKeysCount < retainedVisibleThreshold) {
          "not dumping heap yet (app is visible & < $retainedVisibleThreshold threshold)"
        } else {
          if (nopeReason != null) {
            "would dump heap now (app is visible & >=$retainedVisibleThreshold threshold) but $nopeReason"
          } else {
            "dumping heap now (app is visible & >=$retainedVisibleThreshold threshold)"
          }
        }
      } else if (applicationInvisibleLessThanWatchPeriod) {
        val wait =
          AppWatcher.config.watchDurationMillis - (SystemClock.uptimeMillis() - applicationInvisibleAt)
        if (nopeReason != null) {
          "would dump heap in $wait ms (app just became invisible) but $nopeReason"
        } else {
          "dumping heap in $wait ms (app just became invisible)"
        }
      } else {
        if (nopeReason != null) {
          "would dump heap now (app is invisible) but $nopeReason"
        } else {
          "dumping heap now (app is invisible)"
        }
      }

      SharkLog.d {
        val s = if (retainedKeysCount > 1) "s" else ""
        "Found $retainedKeysCount object$s retained, $whatsNext"
      }
    }
      // 內存顯露的watchObject 少于5個剥汤,會先顯示一個通知暫不走堆棧分析,等待2秒再走一次判斷
    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
  }

  fun scheduleRetainedObjectCheck(
    delayMillis: Long = 0L
  ) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) {
      //  前一次 checkRetainedObjects 還沒執(zhí)行
      return
    }
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    backgroundHandler.postDelayed({
      checkScheduledAt = 0
      checkRetainedObjects()
    }, delayMillis)
  }
···
}

scheduleRetainedObjectCheck 會走到 checkRetainedCount 方法排惨,該方法中會通過objectWatcher.retainedObjectCount 去判斷是否有可能泄露的對象吭敢。count > 0 則會通過 gcTrigger.runGc() 進行一次 gc操作,然后再獲取count進行判斷暮芭,如果count仍然>0則調用dumpHeap 方法進行堆棧信息分析鹿驼,調用到 HeapAnalyzerService.runAnalysis()方法。到這里就是判定已經有內存泄漏辕宏,往下就是對堆棧進行分析了畜晰。關于觸發(fā)內存檢查的時機,上面我們已經分析完了觀測對象可能要銷毀的時機中的activity的onActivityDestroyed瑞筐,因為我們是選取ActivityWatcher 進行了分析舷蟀,其他三種就不在這里分析贅述了。
那再看看第二種時機,未鎖屏的前提下野宜,整個應用app對用戶不可見的時候。上面其實已經分析到了魔策,在InternalLeakCanary的invoke方法添加了applicationVisible的回調匈子。

application.registerVisibilityListener { applicationVisible ->
      this.applicationVisible = applicationVisible
      heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
    }

看下registerVisibilityListener這個拓展方法

internal fun Application.registerVisibilityListener(listener: (Boolean) -> Unit) {
  val visibilityTracker = VisibilityTracker(listener)
  registerActivityLifecycleCallbacks(visibilityTracker)
  registerReceiver(visibilityTracker, IntentFilter().apply {
    addAction(ACTION_SCREEN_ON)
    addAction(ACTION_SCREEN_OFF)
  })
}

可見性這塊邏輯就是通過注冊廣播接收者接收系統(tǒng)屏幕息屏與否的廣播,注冊ActivityLifecycle 來判斷當前是否有用戶可見的activity闯袒。這塊邏輯是在VisibilityTracker 中處理的虎敦,可見性變化就會調用InternalLeakCanary的invoke中的可見性回調,接著就會調用 heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)政敢,進入這個方法

 /**
   ** 未鎖屏的前提下其徙,整個應用app對用戶不可見的時候引發(fā)內存檢查
   **/
  fun onApplicationVisibilityChanged(applicationVisible: Boolean) {
    if (applicationVisible) {
      applicationInvisibleAt = -1L
    } else {
      applicationInvisibleAt = SystemClock.uptimeMillis()
      // Scheduling for after watchDuration so that any destroyed activity has time to become
      // watch and be part of this analysis.
      scheduleRetainedObjectCheck(
        delayMillis = AppWatcher.config.watchDurationMillis
      )
    }
  }

分析可知當 applicationVisible 為false 的時候,也就是不可見的時候喷户,回調用到scheduleRetainedObjectCheck -> checkRetainedObjects -> dumpHeap ->HeapAnalyzerService.runAnalysis() 唾那。應用可見性這個時機也就走到堆棧分析這一步了。

2.1.3 分析內存堆棧

上一小節(jié)分析到 HeapAnalyzerService.runAnalysis()

fun runAnalysis(
      context: Context,
      heapDumpFile: File,
      heapDumpDurationMillis: Long? = null,
      heapDumpReason: String = "Unknown"
    ) 

該方法有個 heapDumpFile 參數褪尝,這個參數就是進行堆棧分析的文件闹获。這個文件怎么來的,追溯參數對象創(chuàng)建的地方河哑,是 AndroidHeapDumper.dumpHeap()方法避诽。文件的創(chuàng)建以及數據的寫入就是通過下面一段代碼

override fun dumpHeap(): DumpHeapResult {
    // 創(chuàng)建空文件
    val heapDumpFile = leakDirectoryProvider.newHeapDumpFile() ?: return NoHeapDump
    ···
    return try {
      val durationMillis = measureDurationMillis {
        // 將 dump hprof 數據寫入到空文件中
        Debug.dumpHprofData(heapDumpFile.absolutePath)
      }
      if (heapDumpFile.length() == 0L) {
        SharkLog.d { "Dumped heap file is 0 byte length" }
        NoHeapDump
      } else {
        HeapDump(file = heapDumpFile, durationMillis = durationMillis)
      }
    } catch (e: Exception) {
      SharkLog.d(e) { "Could not dump heap" }
      // Abort heap dump
      NoHeapDump
    } finally {
      cancelToast(toast)
      notificationManager.cancel(R.id.leak_canary_notification_dumping_heap)
    }
  }

看源碼可知,要分析的堆棧信息是通過android sdk 中的 Debug.dumpHprofData()方法來獲取堆棧信息的璃谨。有文件之后沙庐,接著看HeapAnalyzerService.runAnalysis() 的實現

internal class HeapAnalyzerService : ForegroundService(
  HeapAnalyzerService::class.java.simpleName,
  R.string.leak_canary_notification_analysing,
  R.id.leak_canary_notification_analyzing_heap
), OnAnalysisProgressListener {

  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 heapDumpReason = intent.getStringExtra(HEAPDUMP_REASON_EXTRA)
    val heapDumpDurationMillis = intent.getLongExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, -1)

    val config = LeakCanary.config
    // 分析堆棧,拿到分析結果
    val heapAnalysis = if (heapDumpFile.exists()) {
      analyzeHeap(heapDumpFile, config)
    } else {
      missingFileFailure(heapDumpFile)
    }
    val fullHeapAnalysis = when (heapAnalysis) {
      is HeapAnalysisSuccess -> heapAnalysis.copy(
        dumpDurationMillis = heapDumpDurationMillis,
        metadata = heapAnalysis.metadata + ("Heap dump reason" to heapDumpReason)
      )
      is HeapAnalysisFailure -> heapAnalysis.copy(dumpDurationMillis = heapDumpDurationMillis)
    }
    onAnalysisProgress(REPORTING_HEAP_ANALYSIS)
    // 將分析結果回調
    config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)
  }
···
  companion object {
    private const val HEAPDUMP_FILE_EXTRA = "HEAPDUMP_FILE_EXTRA"
    private const val HEAPDUMP_DURATION_MILLIS_EXTRA = "HEAPDUMP_DURATION_MILLIS_EXTRA"
    private const val HEAPDUMP_REASON_EXTRA = "HEAPDUMP_REASON_EXTRA"
    private const val PROGUARD_MAPPING_FILE_NAME = "leakCanaryObfuscationMapping.txt"

    fun runAnalysis(
      context: Context,
      heapDumpFile: File,
      heapDumpDurationMillis: Long? = null,
      heapDumpReason: String = "Unknown"
    ) {
      val intent = Intent(context, HeapAnalyzerService::class.java)
      intent.putExtra(HEAPDUMP_FILE_EXTRA, heapDumpFile)
      intent.putExtra(HEAPDUMP_REASON_EXTRA, heapDumpReason)
      heapDumpDurationMillis?.let {
        intent.putExtra(HEAPDUMP_DURATION_MILLIS_EXTRA, heapDumpDurationMillis)
      }
      startForegroundService(context, intent)
    }

    private fun startForegroundService(
      context: Context,
      intent: Intent
    ) {
      if (SDK_INT >= 26) {
        context.startForegroundService(intent)
      } else {
        // Pre-O behavior.
        context.startService(intent)
      }
    }
  }
}

代碼中開啟了HeapAnalyzerService這個前臺服務佳吞,服務開啟會調用 onHandleIntentInForeground 中的邏輯拱雏。onHandleIntentInForeground會調用 analyzeHeap 拿到分析結果,然后將分析結果通過config.onHeapAnalyzedListener.onHeapAnalyzed(fullHeapAnalysis)進行回調給DefaultOnHeapAnalyzedListener的onHeapAnalyzed方法

  override fun onHeapAnalyzed(heapAnalysis: HeapAnalysis) {
    SharkLog.d { "\u200B\n${LeakTraceWrapper.wrap(heapAnalysis.toString(), 120)}" }
    // 將分析結果存入數據庫容达,用于在activity中查看
    val db = LeaksDbHelper(application).writableDatabase
    val id = HeapAnalysisTable.insert(db, heapAnalysis)
    db.releaseReference()

    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)
    }
  }
  private fun showNotification(
    screenToShow: Screen,
    contentTitle: String
  ) {
    val pendingIntent = LeakActivity.createPendingIntent(
      application, arrayListOf(HeapDumpsScreen(), screenToShow)
    )

    val contentText = application.getString(R.string.leak_canary_notification_message)

    Notifications.showNotification(
      application, contentTitle, contentText, pendingIntent,
      R.id.leak_canary_notification_analysis_result,
      LEAKCANARY_MAX
    )
  }

該回調中將分析結果存入數據庫古涧,展示一條通知,點擊通知回調轉到pendingIntent 的目標頁面 LeakActivity 展示數據庫中的信息花盐。關于如何得到分析hprof文件結果的羡滑,其實就是 HeapAnalyzerService 的 analyzeHeap() 方法返回值,一直查看調用算芯,實質調用的是 FindLeakInput 的 analyzeGraph 拓展方法

private fun FindLeakInput.analyzeGraph(
    metadataExtractor: MetadataExtractor,
    leakingObjectFinder: LeakingObjectFinder,
    heapDumpFile: File,
    analysisStartNanoTime: Long
  ): HeapAnalysisSuccess {
    listener.onAnalysisProgress(EXTRACTING_METADATA)
    val metadata = metadataExtractor.extractMetadata(graph)

    val retainedClearedWeakRefCount = KeyedWeakReferenceFinder.findKeyedWeakReferences(graph)
      .filter { it.isRetained && !it.hasReferent }.count()

    // This should rarely happens, as we generally remove all cleared weak refs right before a heap
    // dump.
    val metadataWithCount = if (retainedClearedWeakRefCount > 0) {
      metadata + ("Count of retained yet cleared" to "$retainedClearedWeakRefCount KeyedWeakReference instances")
    } else {
      metadata
    }

    listener.onAnalysisProgress(FINDING_RETAINED_OBJECTS)
    // 查找到泄露對象id
    val leakingObjectIds = leakingObjectFinder.findLeakingObjectIds(graph)
    // 查找泄露對象并分成應用泄露柒昏、庫泄露和其他泄露
    val (applicationLeaks, libraryLeaks, unreachableObjects) = findLeaks(leakingObjectIds)

    return HeapAnalysisSuccess(
      heapDumpFile = heapDumpFile,
      createdAtTimeMillis = System.currentTimeMillis(),
      analysisDurationMillis = since(analysisStartNanoTime),
      metadata = metadataWithCount,
      applicationLeaks = applicationLeaks,
      libraryLeaks = libraryLeaks,
      unreachableObjects = unreachableObjects
    )
  }

到這里就把LeakCanary核心流程梳理結束了,了解了LeakCanary 是如何檢測內存泄漏熙揍、提示以及展示的职祷。

LeakCanary 主頁
LeakCanary GitHub
關于Java中的WeakReference

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子有梆,更是在濱河造成了極大的恐慌是尖,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泥耀,死亡現場離奇詭異饺汹,居然都是意外死亡,警方通過查閱死者的電腦和手機痰催,發(fā)現死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門兜辞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人夸溶,你說我怎么就攤上這事逸吵。” “怎么了缝裁?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵扫皱,是天一觀的道長。 經常有香客問我压语,道長啸罢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任胎食,我火速辦了婚禮扰才,結果婚禮上,老公的妹妹穿的比我還像新娘厕怜。我一直安慰自己衩匣,他們只是感情好,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布粥航。 她就那樣靜靜地躺著琅捏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪递雀。 梳的紋絲不亂的頭發(fā)上柄延,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天,我揣著相機與錄音缀程,去河邊找鬼搜吧。 笑死,一個胖子當著我的面吹牛杨凑,可吹牛的內容都是我干的滤奈。 我是一名探鬼主播,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼撩满,長吁一口氣:“原來是場噩夢啊……” “哼蜒程!你這毒婦竟也來了绅你?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤昭躺,失蹤者是張志新(化名)和其女友劉穎忌锯,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體领炫,經...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡汉规,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了驹吮。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡晶伦,死狀恐怖碟狞,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情婚陪,我是刑警寧澤族沃,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站泌参,受9級特大地震影響脆淹,放射性物質發(fā)生泄漏。R本人自食惡果不足惜沽一,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一盖溺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧铣缠,春花似錦烘嘱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至捡硅,卻和暖如春哮内,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背壮韭。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工北发, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人泰涂。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓鲫竞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親逼蒙。 傳聞我的和親對象是個殘疾皇子从绘,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內容

  • Android內存泄漏一直是困擾我們Android開發(fā)者的一個心病,由于開發(fā)人員對于知識掌握的不夠深入或者代碼的不...
    三葉梧桐閱讀 1,792評論 0 11
  • 簡介 LeakCanary是Square公司基于MAT開源的一個工具,用于檢測Android的內存泄漏僵井。官方地址是...
    Android小工ing閱讀 843評論 0 0
  • 一.前言 在日常的Android開發(fā)中陕截,不經意間就會造成內存泄露,如果持續(xù)泄露的話批什,那么最后會造成內存溢出农曲,應用也...
    雷濤賽文閱讀 2,458評論 1 2
  • LeakCanary 2.0原理 背景: Android應用基于Java(kotlin)實現,因此它也將Java的...
    無名長空劍_real閱讀 1,202評論 0 2
  • leakcanary LeakCananry 是 Square 公司開源的一個針對 Android 的內存泄漏檢測...
    凱玲之戀閱讀 707評論 0 0