Android內存泄漏-LeakCanary源碼原理分析

LeakCanary原理分析

簡介

使用MAT來分析內存問題,有一些門檻,會有一些難度,並且效率也不是很高,對於一個內存泄漏問題,可能要進行多次排查和對比才能找到問題原因。 爲了能夠簡單迅速的發現內存泄漏,Square公司基於MAT開源了LeakCanary

總結來說LeakCanary是一個基於MAT用來檢測內存泄漏的一個有效的簡單好用的工具。

不足

申請大容量內存導致的OOM問題、Bitmap內存未釋放問題,Service 中的內存泄漏無法檢測等,需要我們用Mat。

使用

2.0之後只需要在gradle裏添加一個這個就可以了 2.4都是用kotlin寫的。

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

其他的註冊install的工作都放到AppWatcherInstaller中了。
AppWatcherInstaller這個類上,它繼承自ContentProvider,四大組件之一。ContentProvider的特點就是不用顯示調用初始化,在執行完application的初始化後就會調用ContentProvider的onCreate()方法。正是利用這一點,LeakCanary把註冊寫在了這裏面,有系統自動調用完成,對開發者完全無感知。
這裏也可以看出我們瞭解四大組件源碼Android啓動流程的重要性。我們可以做很多優化的事情。

internal sealed class AppWatcherInstaller : ContentProvider() {
....省略代碼....
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application)
    return true
  }
....省略代碼....
}

原理

何時檢測泄漏

首先我們要先了解LeakCanary-Android主要是檢測Activity和Fragment的內存泄漏。其他的對象的泄漏需要我們自己去
我們帶着問題先去看下他的註冊源碼。

跟蹤AppWatcherInstaller中註冊的代碼到AppWatcher

 AppWatcher.manualInstall(application)
object AppWatcher {

  .....省略代碼
  /**
   * [AppWatcher] is automatically installed in the main process on startup. You can
   * disable this behavior by overriding the `leak_canary_watcher_auto_install` boolean resource:
   *
   * ```
   * <?xml version="1.0" encoding="utf-8"?>
   * <resources>
   *   <bool name="leak_canary_watcher_auto_install">false</bool>
   * </resources>
   * ```
   *
   * If you disabled automatic install then you can call this method to install [AppWatcher].
   */
  fun manualInstall(application: Application) {
    InternalAppWatcher.install(application)
  }
}

繼續追蹤代碼 InternalAppWatcher.install(application)到InternalAppWatcher

/**
 * Note: this object must load fine in a JUnit environment
 */
internal object InternalAppWatcher {
	...省略代嗎
  fun install(application: Application) {
  ...省略代嗎
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
   ...省略代嗎
  }
  ...省略代嗎
}

InternalAppWatcher中做了ActivityDestroyWatcher.install
FragmentDestroyWatcher.install
我們繼續追蹤到FragmentDestroyWatcher和ActivityDestroyWatcher的代碼細節。

internal class ActivityDestroyWatcher private constructor(
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) {
//創建Activity生命週期監聽對象 監聽Activity destroy方法然後調用 objectWatcher
  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
      //判斷config中配置是否監聽activity泄漏
        if (configProvider().watchActivities) {
          objectWatcher.watch(
              activity, "${activity::class.java.name} received Activity#onDestroy() callback"
          )
        }
      }
    }

  companion object {
  //伴生對象中install方法中創建ActivityDestroyWatcher對象並註冊Activiy監聽傳入ObjectWatcher ,Config
    fun install(
      application: Application,
      objectWatcher: ObjectWatcher,
      configProvider: () -> Config
    ) {
      val activityDestroyWatcher =
        ActivityDestroyWatcher(objectWatcher, configProvider)
    application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
    }
  }
}

Fragment 則要複雜一些FragmentDestroyWatcher中的install中在Activity啓動的onCreate監聽中通過Activity獲取FragmentManager,通過FragmentManager.registerFragmentLifecycleCallbacks註冊監聽,主要是在onFragmentViewDestroyed 和 onFragmentDestroyed方法中監聽 ,onFragmentViewDestroyed中主要是對fragment.view的泄漏檢測。

其中對AndroidO 以及 AndroidX和Android庫進行了適配:
AndroidOFragmentDestroyWatcher
AndroidXFragmentDestroyWatcher
AndroidSupportFragmentDestroyWatcher
此處限於篇幅不列出具體代碼要看的可以自己導入下leakCanary去具體看下代碼。

如何檢測泄漏

通過上面的分析我們發現Fragment和Activity都是通過監聽destroy方法然後,去執行ObjectWatcher的watch方法,那看來核心的檢測代碼就在裏邊了。那我們現在就去探索一下吧。

 objectWatcher.watch(
            view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
            "(references to its views should be cleared to prevent leaks)"
        )

追蹤objectWatcher.watch方法。
追蹤過程中你可能需要了解一下強軟弱虛四種應用
一些常見的內存泄漏問題及弱引用的應用

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 }
) {
	private val watchedObjects = mutableMapOf<String, KeyedWeakReference>()

 	private val queue = ReferenceQueue<Any>()

 @Synchronized fun watch(watchedObject: Any) {
    watch(watchedObject, "")
  }

 /**
   * Watches the provided [watchedObject].
   *
   * @param description Describes why the object is watched.
   */
  @Synchronized fun watch(
    watchedObject: Any,
    description: String
  ) {
    if (!isEnabled()) {
      return
    }
    //先清除一次防止重複添加(應該是這麼個作用因爲key每次都是隨機生成的)
    removeWeaklyReachableObjects()
	//生成每個被觀察對象的key
    val key = UUID.randomUUID()
        .toString()
    //主要是在dump的時候使用計算一個對象被引用了多久
    val watchUptimeMillis = clock.uptimeMillis()
    //KeyWeakReference是WeakRefrence的子類,LeakCanary的原理就是如果對象被回收了的話
   //會把引用對象放到 queue(ReferenceQueue<Any>) 生成一個對watchedObject的弱引用
    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"
    }
	//把相應的refrece放到被觀察對象map中,watchedObjects可變map
    watchedObjects[key] = reference
    //然後通過Handler發送延遲五秒消息去判斷是否還有引用沒回收並dump,下邊會介紹checkRetainedExecutor對象的定義
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }
  
//poll出queue中的被回收的對象移除watchedObjects中的相應弱引用對象
private fun removeWeaklyReachableObjects() {
    // WeakReferences are enqueued as soon as the object to which they point to becomes weakly
    // reachable. This is before finalization or garbage collection has actually happened.
    var ref: KeyedWeakReference?
    do {
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }
  
@Synchronized fun addOnObjectRetainedListener(listener: OnObjectRetainedListener) {
    onObjectRetainedListeners.add(listener)
  }

  
//handler延遲五秒後執行run方法中的moveToRetained方法
@Synchronized private fun moveToRetained(key: String) {
	//先去poll出queue中的被回收的對象移除watchedObjects中的相應弱引用對象 防止5秒過程中可能對象已經被回收了 下邊還要進行一次gc和dump
    removeWeaklyReachableObjects()
    //拿出當前watch對象的弱引用
    val retainedRef = watchedObjects[key]
    //【標記最後一步】若該對象若引用不爲空則繼續下一步爲null則認爲已被回收了不作處理 
    if (retainedRef != null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      //執行onObjectRetainedListeners中每個監聽的onObjectRetained方法
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }
}

checkRetainedExecutor和對Activity,Fragment的檢測ObjectWatch
都是定義在InternalAppWatcher中的前邊有說到過這個類。

private val checkRetainedExecutor = Executor {
  	//AppWatcher.config.watchDurationMillis
	//在AppWatcher的Config類中定義val watchDurationMillis: Long = TimeUnit.SECONDS.toMillis(5),默認五秒
    mainHandler.postDelayed(it, AppWatcher.config.watchDurationMillis)
}
  
val objectWatcher = ObjectWatcher(
      clock = clock,
      checkRetainedExecutor = checkRetainedExecutor,
      isEnabled = { true }
)

接着代碼中分析的最後一步(有【標記最後一步】標記的地方 handler的五秒信息後清除後引用對象不爲null),
我們直接跟蹤ObjectWatcher中的addOnObjectRetainedListener代碼

@Synchronized fun addOnObjectRetainedListener(listener: OnObjectRetainedListener) {
    onObjectRetainedListeners.add(listener)
  }

鼠標放到這個方法上邊 Command + 觸摸板點擊,我們會發現我們到了InternalLeakCanary的invoke方法中但是這個地方我們從前邊分析過來也沒發現那裏調用了

override fun invoke(application: Application) {
    AppWatcher.objectWatcher.addOnObjectRetainedListener(this)
    val heapDumper = AndroidHeapDumper(application, createLeakDirectoryProvider(application))
    //創建一個後臺子線程的Handler,backgroundHandler這個後邊是用來出來
    val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
    handlerThread.start()
    val backgroundHandler = Handler(handlerThread.looper)

    heapDumpTrigger = HeapDumpTrigger(
        application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
        configProvider
    )
  }

但是不要急他肯定在哪裏被調用了,我們看的時候都注意在主流程上邊,忽略了這個沒關係我們再回頭去看下到底哪裏使用了它,經過在前邊AppWatcherInstaller,AppWatcher,InternalAppWatcher
我們發現他是通過反射調用了

internal object InternalAppWatcher {
 private val onAppWatcherInstalled: (Application) -> Unit
  ...省略代嗎
  init {//我們創建InternalAppWatcherinit方法就會調用創建InternalLeakCanary對象並把對象賦值給onAppWatcherInstalled,onAppWatcherInstalled這個是個Lamda表達式 其實就是InternalLeakCanary中的invoke方法表達式
    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 as (Application) -> Unit
  }

  fun install(application: Application) {
   ...省略代嗎
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
    
    //在install的時候通過onAppWatcherInstalled調用InternalLeakCanary invoke方法
    onAppWatcherInstalled(application)
  }
...省略代嗎
}

Kotlin中init代碼塊和構造方法以及伴生對象中代碼的調用時機及執行順序
好了經過上變得分寫我們已經知道InternalLeakCanary在哪裏創建並註冊對象未回收監聽addOnObjectRetainedListener。
接下來我們分析InternalLeakCanary實現了監聽方法後方法裏調用了什麼。

override fun onObjectRetained() {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.onObjectRetained()
    }
  }

  fun onDumpHeapReceived(forceDump: Boolean) {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.onDumpHeapReceived(forceDump)
    }
  }

繼續跟蹤

//調用
heapDumpTrigger.onObjectRetained()

//onObjectRetained方法的實現
 fun onObjectRetained() {
    scheduleRetainedObjectCheck(
        reason = "found new object retained",
        rescheduling = false
    )
  }

分析scheduleRetainedObjectCheck:

private fun scheduleRetainedObjectCheck(
    reason: String,
    rescheduling: Boolean,
    delayMillis: Long = 0L
  ) {
    val checkCurrentlyScheduledAt = checkScheduledAt
    if (checkCurrentlyScheduledAt > 0) {
      val scheduledIn = checkCurrentlyScheduledAt - SystemClock.uptimeMillis()
      SharkLog.d { "Ignoring request to check for retained objects ($reason), already scheduled in ${scheduledIn}ms" }
      return
    } else {
      val verb = if (rescheduling) "Rescheduling" else "Scheduling"
      val delay = if (delayMillis > 0) " in ${delayMillis}ms" else ""
      SharkLog.d { "$verb check for retained objects${delay} because $reason" }
    }
    checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
    //在後臺子線程的handler中檢測未回收對象
    backgroundHandler.postDelayed({
      checkScheduledAt = 0
      checkRetainedObjects(reason)
    }, delayMillis)
  }

然後在分析checkRetainedObjects:

 private fun checkRetainedObjects(reason: String) {
    val config = configProvider()
    // A tick will be rescheduled when this is turned back on.
    //配置中是否要dump堆棧信息並通知
    if (!config.dumpHeap) {
      SharkLog.d { "Ignoring check for retained objects scheduled because $reason: LeakCanary.Config.dumpHeap is false" }
      return
    }
	//目前還有多少未回收對象
    var retainedReferenceCount = objectWatcher.retainedObjectCount

	//如果還有未回收對象則在進行一次GC不一定能執行確保對象能回收的都回收了,然後再去賦值retainedReferenceCount
    if (retainedReferenceCount > 0) {
      gcTrigger.runGc()
      retainedReferenceCount = objectWatcher.retainedObjectCount
    }
	//檢測retainedReferenceCount是否爲零或者小於retainedVisibleThreshold = 5如果小於則跳過下邊的檢測延遲 WAIT_FOR_OBJECT_THRESHOLD_MILLIS = 2_000L 2秒 重新從scheduleRetainedObjectCheck重新開始檢測,一直循環,直到大於等於5個或者等於0個,爲了防止頻發回收堆造成卡頓。爲零的話就直接顯示通知。
    if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
	//大於5個後,如果處於debug模式,會再等20秒,再次執行scheduleRetainedObjectCheck操作。防止debug模式會減慢回收
    if (!config.dumpHeapWhenDebugging && DebuggerControl.isDebuggerAttached) {
      onRetainInstanceListener.onEvent(DebuggerIsAttached)
      showRetainedCountNotification(
          objectCount = retainedReferenceCount,
          contentText = application.getString(
              R.string.leak_canary_notification_retained_debugger_attached
          )
      )
      scheduleRetainedObjectCheck(
          reason = "debugger is attached",
          rescheduling = true,
          delayMillis = WAIT_FOR_DEBUG_MILLIS
      )
      return
    }

    val now = SystemClock.uptimeMillis()
    val elapsedSinceLastDumpMillis = now - lastHeapDumpUptimeMillis
    //距離上次dumpheap的事件小於WAIT_BETWEEN_HEAP_DUMPS_MILLIS =60_000L 1分鐘 則繼續去檢測未回收對象知道大於一分鐘
    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(
          reason = "previous heap dump was ${elapsedSinceLastDumpMillis}ms ago (< ${WAIT_BETWEEN_HEAP_DUMPS_MILLIS}ms)",
          rescheduling = true,
          delayMillis = WAIT_BETWEEN_HEAP_DUMPS_MILLIS - elapsedSinceLastDumpMillis
      )
      return
    }

    SharkLog.d { "Check for retained objects found $retainedReferenceCount objects, dumping the heap" }
    //顯示未回收對象個數通知隱藏
    dismissRetainedCountNotification()
    //dump堆棧信息
    dumpHeap(retainedReferenceCount, retry = true)
  }

總結一下就是:
1)如果未回收對象個數小於5,不做操作等待5秒再次進行檢查未回收的個數,一直循環,直到大於等於5個或者等於0個,爲了防止頻發回收堆造成卡頓。
2)大於5個後,如果處於debug模式,會再等20秒,再次執行scheduleRetainedObjectCheck檢測操作。防止debug模式會減慢回收
3)距離上次堆棧分析是否大於等於1分鐘,如果沒有超過一分鐘,也需要再次延遲(1分鐘-當前距離上次的時間)再次循環執行scheduleRetainedObjectCheck檢測操作

6、如果上面的條件都符合了,就可以開始進行堆棧的分析了
1)、獲取到內容文件 Debug.dumpHprofData(heapDumpFile.absolutePath)
2)、objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)該操作是去掉以前已經分析過的對象,也就是去除掉之前的沒有回收掉的對象,不在本次分析範圍內
3)、HeapAnalyzerService開啓IntentService服務進行分析 具體分析就不寫了,因爲還沒看懂
4)、把結果插入數據庫(泄漏區分了應用本身的內存泄漏和類庫的內存泄漏),並且發送通知

好了 到這裏LeakCanary的原理和源碼分析完畢

老版本的LeakCanary原理
老版本的LeakCanary原理解析,理解起來超簡單!

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