LeakCanary淺析

前提:LeakCanary 版本v2.4; Android 8.0
????LeakCanary相信很多開發(fā)者都用過,也是目前為止我看到的一款最簡單方便的簡單內(nèi)存泄漏的工具了,使用之后,會有以下幾個問題:

1:LeakCanary的初始化在哪里?

????較早之前使用leankCanary時,在Application中,會有一個初始化的代碼,但在后來2.0版本之后,初始化的代碼沒了,但確不影響我們使用。難道是后續(xù)不需要初始化了?
????其實(shí)初始化還是有的,只是后續(xù)的版本,利用了Android app的啟動流程中的機(jī)制,將自身的初始化放入的provider中。在app啟動時,會優(yōu)先初始化app中的provider,正是利用這一特性,所以才不用我們寫初始化的代碼


image.png
2:初始化的provider在哪里?干了什么事情?

???? 我們可以去github中找一下相關(guān)的provider或者在Android Studio引入的leakcanary包中,在相應(yīng)的mainfest中找一下provider


image.png

從上圖已標(biāo)出初始化的provider為 AppWatchInstaller$MainProcess

注意:在有的文章中,會說初始化的工程是leakcanar-leaksentry下面的LeakSentryInstaller,我也被這些文章弄的差點(diǎn)錯了,后來翻看LeakCanary的github各版本修改日志發(fā)現(xiàn),確實(shí)以前初始化的provider是LeakSentryInstall,但僅限于在2.0 Alpha各版本中,在2.0 Beta版之后,就統(tǒng)一改了。

image.png

這個大家要注意一些了。
???? 那究竟初始化都做了些什么。

  fun install(application: Application) {
    checkMainThread()
    if (this::application.isInitialized) {
      return
    }
    SharkLog.logger = DefaultCanaryLog()
    InternalAppWatcher.application = application

    val configProvider = { AppWatcher.config }
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
    onAppWatcherInstalled(application)
  }

跟進(jìn)代碼可以發(fā)現(xiàn),這里做了最關(guān)鍵的一步,xxxDestroyWatcher的注冊。在xxxDestroyWatcher中,對Activity,Fragment做了相應(yīng)的保存,并監(jiān)聽它他的生命周期。

3:檢查內(nèi)存泄漏的原理是什么?

???? 我們在使用WeakReference或PhantomReference時,會發(fā)現(xiàn)他們有一個構(gòu)造方法是這樣的:

public class WeakReference<T> extends Reference<T> {
     public WeakReference(T referent) {
        super(referent);
    }
    public WeakReference(T referent, ReferenceQueue<? super T> q) {
        super(referent, q);
    }
}

我們發(fā)現(xiàn),第二個構(gòu)造方法中,需要傳入一個ReferenceQueue的構(gòu)造方法。這個RefereneQueue就是檢查內(nèi)存泄漏用的。當(dāng)對象被回收之后,就會把對象放到這個ReferenceQueue中來,所以如果Activity在onDestroy后,如果沒有被回收,由在隊(duì)列中就沒有該Activity,那么就可以認(rèn)為存在內(nèi)存泄漏。

4:LeakCanary是如何檢測的?
ActivityDestroyWatcher
private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        if (configProvider().watchActivities) {
          objectWatcher.watch(
              activity, "${activity::class.java.name} received Activity#onDestroy() callback"
          )
        }
      }
    }

從代碼中可以看到,當(dāng)監(jiān)聽到activity的destroy后,就會執(zhí)行相應(yīng)的objectWatch.watch來做一些處理,但真正判斷內(nèi)存泄漏的地方卻不在這里。
在InternalAppWatcher.install方法中,除了調(diào)用xxxDestroyWatch外,還有一段代碼onAppWatcherInstalled(application),看代碼其實(shí)就是InternalLeakCanary的invoke。

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)

    disableDumpHeapInTests()
  }

在這里面初始化了heapDumper,gcTrigger,heapDumpTrigger等對象用于gc和heapDump,同時還實(shí)現(xiàn)了OnObjectRetainedListener,并把自己添加到了上面的onObjectRetainedListeners中,以便每個對象moveToRetained的時候,InternalLeakCanary都能獲取到onObjectRetained()的回調(diào),回調(diào)里就只是回調(diào)了heapDumpTrigger.onObjectRetained()方法??磥矶际且蕾囉贖eapDumpTrigger這個類。
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
    }

    var retainedReferenceCount = objectWatcher.retainedObjectCount

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

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

    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
    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()
    dumpHeap(retainedReferenceCount, retry = true)
  }

那么HeapDumpTrigger主要是下面幾個功能:

  • 后臺線程輪詢當(dāng)前還存活著的對象
  • 如果存活的對象大于0,那就觸發(fā)一次GC操作,回收掉沒有泄露的對象
  • GC完后,仍然存活著的對象數(shù)和預(yù)定的對象數(shù)相比較,如果多了就調(diào)用heapDumper.dumpHeap()方法把對象dump成文件,并交給HeapAnalyzerService去分析
  • 根據(jù)存活情況展示通知
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容