LeakCanary详解

3,346 阅读6分钟

简介

LeakCanary是Square公司研发的一个可视化的内存泄漏分析工具

使用

添加依赖

最新的LeakCanary只需引入依赖,不需要初始化代码,就能执行内存泄漏检测;

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

运行效果

Demo

这里使用了一个简单的内存泄露Demo来展示LeakCanary的运行效果

MainActivity:

class MainActivity : AppCompatActivity() {
    private var binding: ActivityMainBinding? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        val view : View = binding!!.root
        setContentView(view)
        binding!!.mianButton.setOnClickListener(View.OnClickListener {
            startActivity(Intent(this,TestActivity::class.java))
        })
    }
}

TestActivity:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityTestBinding.inflate(layoutInflater)
    val view : View = binding!!.root
    setContentView(view)
    TestDataModel.get().activityList.add(this)
}

TestDataModel:

class TestDataModel {
    val activityList = mutableListOf<Activity>()

    companion object{
        private var instance: TestDataModel? = null
            get() {
                if (field == null) {
                    field = TestDataModel()
                }
                return field
            }
        fun get(): TestDataModel{
            return instance!!
        }
    }
}

这里用了一个非常简单的Demo,由MainActivity跳转转到TestActivity,在TestActivity中使用一个单例保存了TestActivity对象,再返回MainActivity,跳到TestActivity,由于单例的生命周期比Activity的声明周期更长,单例持有Activity的引用,Activity在ondestory后无法被回收,导致了Activity的泄露

日志

image.png

  • 在日志上出现:LeakCanary is running and ready to detect memory leaks.这一句话时,表示LeakCanary已经集成成功,
  • 在日志上出现:Found 3 objects retained, not dumping heap yet (app is visible & < 5 threshold).这一句话时,表示已经发现了3个保留对象,但是没有dumping heap,因为app可见时的阈值是5,这里补充一点,应用不可时的阈值是1,也就是如果将应用切换到后台(例如点击hong键),APP将立即进行dumping heap
手机显示

image.png

出现内存泄漏时,手机会出现LeakCanary的小鸟图标,同时通知栏会提示,点击通知栏进入详情,可以看到GC Root可达链路,看到最下面,TestActivity instance Leaking:YES,表示TestActivity发生内存泄露,往上看,Object数组 -> ArrayList -> TestDataModel.activityList-> TestDataModel.instance,此处就可以发现,TestActivity被一个单例所引用导致了泄露

LeaKCanary2与LeaKCanary1的区别

  • LeakCanary2只需要Gradle依赖就可以使用,LeakCanary1还需要进行手动安:LeakCanary.install(this);
  • LeakCanary2使用Kotlin进行重构
  • Leakcanary2中使用Shark来解析hprof文件,相比于Leakcanary1使用的HaHa快8倍,并且内存占用还要少 10 倍,但查找泄漏路径的大致步骤无异

LeakCanary造成卡顿

源码分析

初始化

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

leakcanary从2.0版本开始就不需要手动初始化了,其主要是通过ContentProvider来实现免初始化:

internal sealed class AppWatcherInstaller : ContentProvider() {

  internal class MainProcess : AppWatcherInstaller()

  internal class LeakCanaryProcess : AppWatcherInstaller()

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

  override fun query(uri: Uri,strings: Array<String>?,s: String?,
    strings1: Array<String>?,s1: String?): Cursor? {
    return null
  }

  override fun getType(uri: Uri): String? {
    return null
  }

  override fun insert(uri: Uri,contentValues: ContentValues?): Uri? {
    return null
  }

  override fun delete(uri: Uri,s: String?,strings: Array<String>?): Int {
    return 0
  }

  override fun update(uri: Uri,contentValues: ContentValues?,
    s: String?,strings: Array<String>?): Int {
    return 0
  }
}

当然也可以通过修改leak_canary_watcher_auto_install属性来实现手动初始化:

<?xml version="1.0" encoding="utf-8"?>
<resources> <bool name="leak_canary_watcher_auto_install">false</bool>
</resources>

AppWatcher.manualInstall(this)

再看看AppWatcherInstaller中主要做了什么,AppWatcherInstaller继承至ContentProvider,但是我们可以看到query,getType,insert,delete,update等方法都是空实现,也就是这个类只是使用了ContentProvider特性,具体工作只是在onCreate中调用了AppWatcher.manualInstall(application)方法

if (!data.restrictedBackupMode) {
    if (!ArrayUtils.isEmpty(data.providers)) {
        installContentProviders(app, data.providers);
    }
}

mInstrumentation.callApplicationOnCreate(app);

在ActivityThread中的handleBindApplication方法中,可以看到启动app的时候ContentProvider是比Application的onCreate更早,以此是实现了leakcanary的自动初始化,至于Provide写在了LeakCanary的ManiFest文件中,如何被自己的应用是所使用,参考官方的清单文件管理:管理清单文件  |  Android 开发者  |  Android Developers

对象监听

@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
      )
    }
    //检查参数,保存延迟时间应大于零,默认是5秒
    check(retainedDelayMillis >= 0) {
      "retainedDelayMillis $retainedDelayMillis must be at least 0 ms"
    }
    installCause = RuntimeException("manualInstall() first called here")
    this.retainedDelayMillis = retainedDelayMillis
    //测试模式开启log
    if (application.isDebuggableBuild) {
      LogcatSharkLog.install()
    }
    // 通过反射获取InternalLeakCanary单例对象
    LeakCanaryDelegate.loadLeakCanary(application)

    //注册观察对象
    watchersToInstall.forEach {
      it.install()
    }
}

上面最重要的就是调用watchersToInstall.forEach进行观察对象的注册,其中watchersToInstall在方法入参中设有默认值为appDefaultWatchers(application):

fun appDefaultWatchers(
    application: Application,
    reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
    return listOf(
      //默认初始化以下四个监听器
      ActivityWatcher(application, reachabilityWatcher),
      FragmentAndViewModelWatcher(application, reachabilityWatcher),
      RootViewWatcher(reachabilityWatcher),
      ServiceWatcher(reachabilityWatcher)
    )
}

appDefaultWatchers中默认设置了四个监听器, 可以监听包括Activity,Fragment, ViewModel, RootView, Service; 以ActivityWatcher为例:

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

通过调用registerActivityLifecycleCallbacks注册Activity 生命周期回调的监听,并在onActivityDestroyed回调中,通过调用reachabilityWatcher.expectWeaklyReachable将每个Activity对象加到观察列表,

Tips:此处by noOpDelegate()是使用接口代理的模式,只需要重写所需要用到的方法,避免产生大量的空方法

objectWatcher是一个ObjectWatcher的对象,那么就去这个类里面看看它的expectWeaklyReachable方法的实现吧

@Synchronized override fun expectWeaklyReachable(
    watchedObject: Any,
    description: String
) {
    if (!isEnabled()) {
      return
    }
    //将已经被 GC 的对象从 watchedObjects 集合中删除
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
      .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    //封装成一个KeyedWeakReference对象
    val reference =
      KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    //添加到集合中,此时,reference的key与watchedObjects中reference所对应的key一致
    watchedObjects[key] = reference
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
}

可以看到最终是封装成一个KeyedWeakReference对象并添加到集合中,它继承于 WeakReference,弱引用是不会阻止 GC 回收对象的,同时可以在构造函数中传递一个 ReferenceQueue,用于对象被 GC 后存放的队列。再通过线程池执行moveToRetained方法

Tips:软引用、弱引用、虚引用的构造方法均可以传入一个ReferenceQueue与之关联。在引用所指的对象被回收后,引用本身将会被加入到ReferenceQueue之中

首先看看removeWeaklyReachableObjects()

private fun removeWeaklyReachableObjects() {
  var ref: KeyedWeakReference?
  do {
    ref = queue.poll() as KeyedWeakReference?
    if (ref != null) {
      watchedObjects.remove(ref.key)
    }
  } while (ref != null)
}

将已被回收的对象从观察对象列表中清除:从queue中取出已经被回收的引用,根据key将引用从观察对象列表中删除

再看看线程池

val objectWatcher = ObjectWatcher(
  clock = { SystemClock.uptimeMillis() },
  checkRetainedExecutor = {
    check(isInstalled) {
      "AppWatcher not installed"
    }
    mainHandler.postDelayed(it, retainedDelayMillis)
  },
  isEnabled = { true }
)

线程池在objectWatcher对象创建时初始化,用来延时执行moveToRetained方法,默认延时5秒

最后再看看moveToRetained

@Synchronized private fun moveToRetained(key: String) {
    //删除已经 GC 的对象
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    if (retainedRef != null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      //剩下的对象就可以认为是被保留(没办法 GC)的对象,回调通知事件
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
}

回到AppWatcher.manualInstall方法中,其中调用watchersToInstall.forEach之前,调用了LeakCanaryDelegate.loadLeakCanary(application)通过反射获取了InternalLeakCanary单例对象:

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

上面代码最终会调用实际会调用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)

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

此方法主要是完成一些对象的初始化。
调用AppWatcher.objectWatcher.addOnObjectRetainedListener(this)添加一个监听,这里入参用的是this,也就是InternalLeakCanary,那么来看一下InternalLeakCanary中对OnObjectRetainedListener的onObjectRetained方法的实现

override fun onObjectRetained() = scheduleRetainedObjectCheck()

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

fun scheduleRetainedObjectCheck(
  delayMillis: Long = 0L
) {
  val checkCurrentlyScheduledAt = checkScheduledAt
  if (checkCurrentlyScheduledAt > 0) {
    return
  }
  checkScheduledAt = SystemClock.uptimeMillis() + delayMillis
  backgroundHandler.postDelayed({
    checkScheduledAt = 0
    checkRetainedObjects()
  }, delayMillis)
}

这里比较简单如果heapDumpTrigger已经初始化完成,就调用scheduleRetainedObjectCheck,scheduleRetainedObjectCheck中进行延时执行checkRetainedObjects,默认不延时,如果上一次没有执行完,不进行下一次执行,主要处理逻辑还是放在checkRetainedObjects中

private fun checkRetainedObjects() {
  val config = configProvider()
  
  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"
  )
}
  • 后台线程轮询当前还存活着的对象
  • 如果存活的对象大于0,那就触发一次GC操作,回收掉没有泄露的对象
  • GC完后,仍然存活着的对象数和预定的对象数相比较,如果多了就调用heapDumper.dumpHeap()方法把对象dump成文件,并交给HeapAnalyzerService去分析
  • 根据存活情况展示通知

后续dump相关我就不细讲了

总结

最后借用前辈一张图作为总结

image.png