LeakCanary 源码分析

2,778 阅读2分钟

1. LeakCanary检测泄漏原理

1.1 Java引用分类

  • 强引用:平时常用的引用类型,JVM发生OOM也不会回收这部分引用。
  • 软引用(SoftReference):发生OOM前会回收这部分引用,如果想使用缓存,可以使用LruCache,而不是SoftReference
  • 弱引用(WeakReference):发生GC就会回收。
  • 虚引用(PhantomReference):get方法返回null,不能获取值。

1.2 WeakReference和ReferenceQueue

class P
fun main() {
    val referenceQueue = ReferenceQueue<P>()
    val weak = WeakReference(P(), referenceQueue)
    println(weak)
    System.gc()
    Thread.sleep(2000)
    println(weak.get())
    println(referenceQueue.poll())
}
//输出:
java.lang.ref.WeakReference@2f0e140b
null
java.lang.ref.WeakReference@2f0e140b

当WeakReference引用的对象没有其他对象引用,在GC时,会回收该对象,如果创建WeakReference时传入了ReferenceQueue,会在对象回收后把WeakReference加入到ReferenceQueue队列中。

1.3 LeakCanary的检测原理

Activity执行onDestroy()方法后,把activity对象放进一个带有ReferenceQueue的WeakReference对象中。过段时间去检测ReferenceQueue队列里是否包含刚刚创建的WeakReference对象,如果没有,则表明activity对象还没被回收,则主动触发一次GC,再次检测。如果还没被回收,则表明发生了泄漏。开始使用Debug.dumpHprofData()生成hprof文件,分析hprof文件,得出引用链。

2. LeakCanary源码分析

源码版本2.7

com.squareup.leakcanary:leakcanary-android:2.7

2.1 初始化

使用ContentProvider,自动初始化。

internal sealed class AppWatcherInstaller : ContentProvider() {
 override fun onCreate(): Boolean {
  val application = context!!.applicationContext as Application
  AppWatcher.manualInstall(application)
  return true
 }
}
fun manualInstall(
  application: Application,
  retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
  watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
) {
  checkMainThread()
  //默认5s后去检测
  this.retainedDelayMillis = retainedDelayMillis
  //初始化一些配置,InternalLeakCanary类invoke方法,配置泄漏Listener,GC触发器,Dump类。
  LeakCanaryDelegate.loadLeakCanary(application)
  //监测类别,Activity,Fragment,View,Service
  watchersToInstall.forEach {
    it.install()
  }
}

监测类别

fun appDefaultWatchers(
  application: Application,
  reachabilityWatcher: ReachabilityWatcher = objectWatcher
): List<InstallableWatcher> {
  return listOf(
    ActivityWatcher(application, reachabilityWatcher),
    FragmentAndViewModelWatcher(application, reachabilityWatcher),
    RootViewWatcher(reachabilityWatcher),
    ServiceWatcher(reachabilityWatcher)
  )
}

使用ActivityLifecycleCallbacks来监听onActivityDestroyed的方法回调。

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

ObjectWatcher的创建,执行检测的对象。

val objectWatcher = ObjectWatcher(
  clock = { SystemClock.uptimeMillis() },
  checkRetainedExecutor = {
    check(isInstalled) {
      "AppWatcher not installed"
    }
    //把runnable post 5s后执行
    mainHandler.postDelayed(it, retainedDelayMillis)
  },
  isEnabled = { true }
)

2.2 添加WeakReference检测对象是否回收

用activity举例,会在onActivityDestroyed后,调用expectWeaklyReachable方法,创建KeyedWeakReference对象,放进watchedObjects的map中。延迟5s后,先根据 ReferenceQueue中的对象移除map中的对应KeyedWeakReference,如果移除(ReferenceQueue有对应的KeyedWeakReference),表明activity被回收,如果未能移除,map还包含该KeyedWeakReference,则表明可能发生了泄漏。

ObjectWatcher.class

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()
  //创建KeyedWeakReference,KeyedWeakReference继承于WeakReference。
  val reference =
    KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
  // 根据key加入map
  watchedObjects[key] = reference
  //会延迟5s
  checkRetainedExecutor.execute {
    moveToRetained(key)
  }
}
  @Synchronized private fun moveToRetained(key: String) {
    //根据queue移除map里的对象
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    //如果未能移除,可能发生了泄漏。通知注册了的Listener
    if (retainedRef != null) {
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
      onObjectRetainedListeners.forEach { it.onObjectRetained() }
    }
  }

  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 {
      //如果activity被回收了,包含该activity的KeyedWeakReference就会在该queue中,根据queue把map中对象移除
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }
}

2.3 发起GC再次检测

ObjectWatcher 会回调到InternalLeakCanary的scheduleRetainedObjectCheck方法,最后调用checkRetainedObjects方法检测未回收对象数,触发GC,发起Dump。

InternalLeakCanary.class

override fun onObjectRetained() = scheduleRetainedObjectCheck()

fun scheduleRetainedObjectCheck() {
  if (this::heapDumpTrigger.isInitialized) {
    heapDumpTrigger.scheduleRetainedObjectCheck()
  }
}
HeapDumpTrigger.class
private fun checkRetainedObjects() {
...
//还存在的对象数量
var retainedReferenceCount = objectWatcher.retainedObjectCount
//触发GC,再获取剩余对象数
if (retainedReferenceCount > 0) {
  gcTrigger.runGc()
  retainedReferenceCount = objectWatcher.retainedObjectCount
}
//小于5个不去dump
if (checkRetainedCount(retainedReferenceCount, config.retainedVisibleThreshold)) return
...
dumpHeap(
  retainedReferenceCount = retainedReferenceCount,
  retry = true,
  reason = "$retainedReferenceCount retained objects, app is $visibility"
)
}

3 分析处理内存泄漏

3.1 分析工具

  • LeakCanary
  • Memory Analyzer Tool:Android产生的hprof文件需要sdk hprof-conv工具转换一下
  • Android Studio Profiler:可以导入hprof文件分析

3.2 根据引用链处理

  • 使用LeakCanary检测到内存泄漏后,如果引用链清晰,根据引用链解决泄漏问题。
  • 如果LeakCanary无法给出引用链,可以将hprof文件导出,导入Android Studio使用Profiler分析,根据操作定位有问题的页面,再根据Profiler找出该页面相关对象,如一个Activity在内存中有10个对象,则很可能发生了泄漏。在根据页面猜测可能有问题的代码,使用二分法,注释该部分代码,再次测试。
  • 部分问题可能需要多次使用才能出现,可以使用Espresso来自动化测试一些界面。

4 常见可能发生内存泄漏问题

  • 单例使用Activity的Context
  • 不正确使用静态Toast,自定义样式的Toast时使用Activity的layoutInflater解析布局文件
  • Handler的错误使用
  • 动画没有调用cancel