不是很深入的分析LeakCanary2.0

878 阅读3分钟

Leakcanary2.0

Leakcanary

使用

debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-2'

原理

Reference 、ReferenceQueue 详解

ReferenceQueue 引用队列,在检测到适当的可到达性更改后,垃圾回收器将已注册的引用对象添加到该队列中

WeakReference weakReference = new WeakReference(list, new ReferenceQueue<WeakReference>());

WeakReference 创建时,如果指定一个ReferenceQueue 对象,在垃圾回收器检测到被引用对象的可到达性更改后,垃圾回收器会将已注册的引用对象添加到ReferenceQueue队列中,等待ReferenceQueue处理。但是如果当 GC 过后引用对象仍然不被加入ReferenceQueue中,就有可能存在内存泄漏问题。


实现无需初始化启动

【译】你的Android库是否还在Application中初始化?

大概原理,gradle打包会将不同的manifest文件合并,从而在启动application的时候带动provider,实现初始化。

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

AppWatcherInstaller.kt

internal sealed class AppWatcherInstaller : ContentProvider() {

  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    InternalAppWatcher.install(application)
    return true
  }
}


分析代码

InternalAppWatcher.kt

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

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

1.安装Activity监听

ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
   fun install(
      application: Application,
      objectWatcher: ObjectWatcher,
      configProvider: () -> Config
    ) {
      val activityDestroyWatcher =
        ActivityDestroyWatcher(objectWatcher, configProvider)
      application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
    }

构造activityDestroyWatcher对象,并注册到application的lifecyclecallbacks中

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"
          )
        }
      }
    }

configProvider()对应AppWather objectWatcher是在install传入的参数,和1.5版本leakcanary原理类似,依旧是在Application的lifecycle执行onActivityDestroyed回调时,进行监控watch()

1.1 ObjectWatcher#watch

@Synchronized fun watch(
    watchedObject: Any,
    description: String
  ) {
 
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
        .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    

    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }
 @Synchronized fun watch(watchedObject: Any) {
    watch(watchedObject, "")
  }

熟悉的味道

  • removeWeaklyReachableObjects() 移除弱可达的对象,在GC发生之前
  • val reference =KeyedWeakReference() ObjectWatcher通过其来判断是否是若可达,用key来追踪是否关联ReferenceQueue
  • moveToRetained(key) 先removeWeaklyReachableObjects,在执行所有onObjectRetainedListeners的onObjectRetained()

1.1.1 removeWeaklyReachableObjects()

首先清理一次watchedObjects中已经在保存在ReferenceQueue的引用,不再监控。

弱引用一旦变得弱可达,就会立即入队。这将在 finalization 或者 GC 之前发生。 也就是说,会被 GC 回收的对象引用,会保存在队列 queue 中。

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)
  }

1.1.2 val reference =KeyedWeakReference()

KeyedWeakReference弱引用

A weak reference used by [ObjectWatcher] to determine which objects become weakly reachable
  and which don't. [ObjectWatcher] uses [key] to keep track of [KeyedWeakReference] instances that
  haven't made it into the associated [ReferenceQueue] yet.
 

随机生成key,将watchUptimeMillis,key,queue传入KeyedWeakReference中,构造出reference对象

大概可以理解为

WeakReference reference = new WeakReference(activity实例, new ReferenceQueue<WeakReference>());
 val key = UUID.randomUUID()
        .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)

    watchedObjects[key] = reference

将reference加入watchedObjects观察的map中。

1.1.3 moveToRetained(key)

@Synchronized private fun moveToRetained(key: String) {
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    if (retainedRef != null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }

再removeWeaklyReachableObjects()一遍,如果对应key的监控对象并没有remove掉(加入queue队列),则很可能是泄漏,会继续进行heapDumpTrigger操作。

InternalLeakCanary#onObjectRetained()
 override fun onObjectRetained() {
    if (this::heapDumpTrigger.isInitialized) {
      heapDumpTrigger.onObjectRetained()
    }
  }
HeapDumpTrigger#onObjectRetained()
  fun onObjectRetained() {
    scheduleRetainedObjectCheck(
        reason = "found new object retained",
        rescheduling = false
    )
  }
  private fun scheduleRetainedObjectCheck(
    reason: String,
    rescheduling: Boolean,
    delayMillis: Long = 0L
  ) {
   
    backgroundHandler.postDelayed({
      checkScheduledAt = 0
      checkRetainedObjects(reason)
    }, delayMillis)
  }

reason = "found new object retained",

checkRetainedObjects(reason)

  private fun checkRetainedObjects(reason: String) {
   

    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
          )
      )//发notification
      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)
  }
判断剩余引用数量 > 0 时,gcTrigger.runGc()

同1.5时,常规做法

 override fun runGc() {

      // System.gc() does not garbage collect every time. Runtime.gc() is
      // more likely to perform a gc.
      Runtime.getRuntime()
          .gc()
      enqueueReferences()
      System.runFinalization()
    }
检查剩余引用数量 checkRetainedCount()

retainedVisibleThreshold 默认是5

 /**
     * When the app is visible, LeakCanary will wait for at least
     * [retainedVisibleThreshold] retained instances before dumping the heap. Dumping the heap
     * freezes the UI and can be frustrating for developers who are trying to work. This is
     * especially frustrating as the Android Framework has a number of leaks that cannot easily
     * be fixed.
     *
     * When the app becomes invisible, LeakCanary dumps the heap after
     * [AppWatcher.Config.watchDurationMillis] ms.
     *
     * The app is considered visible if it has at least one activity in started state.
     *
     * A higher threshold means LeakCanary will dump the heap less often, therefore it won't be
     * bothering developers as much but it could miss some leaks.
     *
     * Defaults to 5.
     */
  private fun checkRetainedCount(
    retainedKeysCount: Int,
    retainedVisibleThreshold: Int
  ): Boolean {
    val countChanged = lastDisplayedRetainedObjectCount != retainedKeysCount
    lastDisplayedRetainedObjectCount = retainedKeysCount
    if (retainedKeysCount == 0) {//如果没有剩余引用 return true
      SharkLog.d { "Check for retained object found no objects remaining" }
      if (countChanged) {
      //判断和上次display的数量是否一只,不一致show notification
        onRetainInstanceListener.onEvent(NoMoreObjects)
        showNoMoreRetainedObjectNotification()
      }
      return true
    }
    //如果小于5
    if (retainedKeysCount < retainedVisibleThreshold) {
      if (applicationVisible || applicationInvisibleLessThanWatchPeriod) {
        if (countChanged) {
            //发Event
          onRetainInstanceListener.onEvent(BelowThreshold(retainedKeysCount))
        }
        //发notification
        showRetainedCountNotification(
            objectCount = retainedKeysCount,
            contentText = application.getString(
                R.string.leak_canary_notification_retained_visible, retainedVisibleThreshold
            )
        )
        
        //继续安排剩余object检查  reason不一样了  delay = 2s
        scheduleRetainedObjectCheck(
            reason = "found only $retainedKeysCount retained objects (< $retainedVisibleThreshold while app visible)",
            rescheduling = true,
            delayMillis = WAIT_FOR_OBJECT_THRESHOLD_MILLIS
        )
        return true
      }
    }
    //大于5 false 继续进行
    return false
  }

判断是否isDebuggerAttached

(这个我不懂)

 scheduleRetainedObjectCheck(
          reason = "debugger is attached",
          rescheduling = true,
          delayMillis = WAIT_FOR_DEBUG_MILLIS
      )
如果上次dump 和现在dump的时间 < 60s 继续 ,然后return
if (elapsedSinceLastDumpMillis < WAIT_BETWEEN_HEAP_DUMPS_MILLIS) {
     
      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
    }
dumpHeap(retainedReferenceCount, retry = true)

判断有没有hprof,如果没有重新scheduleRetainedObjectCheck,如果有dumpheap

private fun dumpHeap(
    retainedReferenceCount: Int,
    retry: Boolean
  ) {
    saveResourceIdNamesToMemory()
    val heapDumpUptimeMillis = SystemClock.uptimeMillis()
    KeyedWeakReference.heapDumpUptimeMillis = heapDumpUptimeMillis
    val heapDumpFile = heapDumper.dumpHeap()
    if (heapDumpFile == null) {
      if (retry) {
        SharkLog.d { "Failed to dump heap, will retry in $WAIT_AFTER_DUMP_FAILED_MILLIS ms" }
        scheduleRetainedObjectCheck(
            reason = "failed to dump heap",
            rescheduling = true,
            delayMillis = WAIT_AFTER_DUMP_FAILED_MILLIS
        )
      } else {
        SharkLog.d { "Failed to dump heap, will not automatically retry" }
      }
    
      return
    }
    lastDisplayedRetainedObjectCount = 0
    lastHeapDumpUptimeMillis = SystemClock.uptimeMillis()
    objectWatcher.clearObjectsWatchedBefore(heapDumpUptimeMillis)
    HeapAnalyzerService.runAnalysis(application, heapDumpFile)
  }

2.安装fragment监听

FragmentDestroyWatcher.install(application, objectWatcher, configProvider)

application.registerActivityLifecycleCallbacks(object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityCreated(
        activity: Activity,
        savedInstanceState: Bundle?
      ) {
        for (watcher in fragmentDestroyWatchers) {
          watcher(activity)
        }
      }
    })

2.1 AndroidOFragmentDestroyWatcher

  • onFragmentViewDestroyed
  • onFragmentDestroyed

在以上两个回调时,通过ObjectWatcher监视fragment 和view 对象,ObjectWatcher之后逻辑相同。


internal class AndroidOFragmentDestroyWatcher(
  private val objectWatcher: ObjectWatcher,
  private val configProvider: () -> Config
) : (Activity) -> Unit {
  private val fragmentLifecycleCallbacks = object : FragmentManager.FragmentLifecycleCallbacks() {

    override fun onFragmentViewDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      val view = fragment.view
      if (view != null && configProvider().watchFragmentViews) {
        objectWatcher.watch(
            view, "${fragment::class.java.name} received Fragment#onDestroyView() callback " +
            "(references to its views should be cleared to prevent leaks)"
        )
      }
    }

    override fun onFragmentDestroyed(
      fm: FragmentManager,
      fragment: Fragment
    ) {
      if (configProvider().watchFragments) {
        objectWatcher.watch(
            fragment, "${fragment::class.java.name} received Fragment#onDestroy() callback"
        )
      }
    }
  }

  
}

3.处理初始化工作

onAppWatcherInstalled(application)

onAppWatcherInstalled对应init中internalLeakCanary对象,可以看到反射调用。

init {
    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
  }

3.1 InternalLeakCanary#invoke

override fun invoke(application: Application) {
    this.application = application

    checkRunningInDebuggableBuild()
    
    //注册listener
    AppWatcher.objectWatcher.addOnObjectRetainedListener(this) 
    //创建 AndroidHeapDumper
    val heapDumper = AndroidHeapDumper(application, leakDirectoryProvider)
    //初始化gctrigger
    val gcTrigger = GcTrigger.Default

    val configProvider = { LeakCanary.config }

    val handlerThread = HandlerThread(LEAK_CANARY_THREAD_NAME)
    handlerThread.start()
    //初始化handler 可以看到绑定的looper
    val backgroundHandler = Handler(handlerThread.looper)
    //初始化HeapDumpTrigger
    heapDumpTrigger = HeapDumpTrigger(
        application, backgroundHandler, AppWatcher.objectWatcher, gcTrigger, heapDumper,
        configProvider
    )
    application.registerVisibilityListener { applicationVisible ->
      this.applicationVisible = applicationVisible
      heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)
    }
    registerResumedActivityListener(application)
    addDynamicShortcut(application)

    disableDumpHeapInTests()
  }

以上进行了一堆的初始化操作,比较重要的只有一条

  heapDumpTrigger.onApplicationVisibilityChanged(applicationVisible)

3.2 HeapDumpTrigger#onApplicationVisibilityChanged

这里解释了leakcanary如何自动监听内存泄漏

判断applicationvisible是否可见。如果不可见scheduleRetainedObjectCheck,delay 默认5s

  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(
          reason = "app became invisible",
          rescheduling = false,
          delayMillis = AppWatcher.config.watchDurationMillis
      )
    }
  }

Android开发者,是时候了解LeakCanary2.0了

LeakCanary 2.0原理