leakcanery原理分析

LeakCanary是Android上用于检查内存泄漏的工具,LeakCanary大大减少因内存泄漏导致的内存溢出(OutOfMemoryError)崩溃。

从1.6.3开始,LeakCanary就使用Kotlin重写了一次,这里的的源码来自于版本2.5,加入引用:

    debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.5'
LeakCanary包结构
leakcanary-android
集成入口模块,提供 LeakCanary 安装,公开 API 等能力

leakcanary-android-process
和 leakcanary-android 一样,区别是会在单独的进程进行分析

leakcanary-android-core
核心模块

leakcanary-object-watcher-android,leakcanary-object-watcher-android-androidx,leakcanary-watcher-android-support-fragments
对象实例观察模块,在 Activity,Fragment 等对象的生命周期中,注册对指定对象实例的观察,有 Activity,Fragment,Fragment View,ViewModel 等

shark-android
提供特定于 Android 平台的分析能力。例如设备的信息,Android 版本,已知的内存泄露问题等

shark
hprof 文件解析与分析的入口模块

shark-graph
分析堆中对象的关系图模块

shark-hprof
解析 hprof 文件模块

shark-log
日志模块
初始化

LeakCanary 2.0不需要添加代码便可以跟随APP启动,省去了1.6版本前需要install的代码。原理在于利用了ContentProvider的特性,ContentProvider.onCreate方法会先于Application.onCreate执行。

leakcanary库中声明的ContentProvider。
    //注册ContentProvider @leakcanary-object-watcher-android/src/main/AndroidManifest.xml
<application>
    <provider
        android:name="leakcanary.internal.AppWatcherInstaller$MainProcess"
        android:authorities="${applicationId}.leakcanary-installer"
        android:exported="false"/>
  </application>

//@AppWatcherInstaller.kt
internal class LeakCanaryProcess : AppWatcherInstaller() {
    override fun onCreate(): Boolean {
      super.onCreate()
      AppWatcher.config = AppWatcher.config.copy(enabled = false)
      return true
    }
  }

  override fun onCreate(): Boolean {
    //获取application
    val application = context!!.applicationContext as Application
    //-->2.1 加载LeakCanary
    InternalAppWatcher.install(application)
    return true
  }
}

//2.1 加载LeakCanary @InternalAppWatcher.kt
fun install(application: Application) {
    ...
    //检查当前线程是否有主线程
    checkMainThread()
    if (this::application.isInitialized) {
      //如果LeakCanary已经加载过,直接放回
      return
    }
    InternalAppWatcher.application = application

    val configProvider = { AppWatcher.config }
    //-->2.1监视Activity
    ActivityDestroyWatcher.install(application, objectWatcher, configProvider)
    //-->2.2监视Fragment
    FragmentDestroyWatcher.install(application, objectWatcher, configProvider)
    //-->2.3调用上层模块InternalLeakCanary.invoke
    onAppWatcherInstalled(application)
  }

监听器利用了Activity、fragment的生命周期回调,在ActivityDestroyWatcher类中,获取该销毁的activity,添加了该activity的监听。

companion object {
  fun install(
    application: Application,
    objectWatcher: ObjectWatcher,
    configProvider: () -> Config
  ) {
    //-->2.1.1 创建Activity destroy监听回调
    val activityDestroyWatcher =
      ActivityDestroyWatcher(objectWatcher, configProvider)
    //-->2.1.2 同Application绑定
    application.registerActivityLifecycleCallbacks(activityDestroyWatcher.lifecycleCallbacks)
  }
}

//2.1.1 创建Activity destroy监听回调
  private val lifecycleCallbacks =
    object : Application.ActivityLifecycleCallbacks by noOpDelegate() {
      override fun onActivityDestroyed(activity: Activity) {
        //Activity destroy触发存在对象检查
        if (configProvider().watchActivities) {
          // -->2.1.2 objectWatcher监视activity
          objectWatcher.watch(
              activity, "${activity::class.java.name} received Activity#onDestroy() callback"
          )
        }
      }
    }

lifecycleCallbacks监听Activity的onDestroy方法,正常情况下activity在onDestroy后需要立即被回收,onActivityDestroyed方法最终会调用RefWatcher.watch方法:

  @Synchronized fun watch(
    watchedObject: Any,
    description: String
  ) {
    if (!isEnabled()) {
      return
    }
    removeWeaklyReachableObjects()
    val key = UUID.randomUUID()
        .toString()
    val watchUptimeMillis = clock.uptimeMillis()
    //根据activity创建对应的弱引用,并绑定ReferenceQueue
    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"
    }
    //将reference保存到watchedObjects数组中
    watchedObjects[key] = reference
    //启动延时5s任务
    checkRetainedExecutor.execute {
      //获取GC无法回收的Activity
      moveToRetained(key)
    }
  }

监测机制利用了Java的WeakReference和ReferenceQueue,通过将Activity包装到WeakReference中,被WeakReference包装过的Activity对象如果被回收,该WeakReference引用会被放到ReferenceQueue中,通过监测ReferenceQueue里面的内容就能检查到Activity是否能够被回收。

ReferenceQueue:

引用队列,换言之就是存放引用的队列,保存的是Reference对象。其作用在于Reference对象所引用的对象被GC回收时,该Reference对象将会被加入引用队列的队尾。

  //获取GC无法回收的Activity
  @Synchronized private fun moveToRetained(key: String) {
    removeWeaklyReachableObjects()
    val retainedRef = watchedObjects[key]
    if (retainedRef != null) {
      //保存当前时间作为泄漏时间
      retainedRef.retainedUptimeMillis = clock.uptimeMillis()
     //通知InternalLeakCanary发生内存泄漏
      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 {
      ref = queue.poll() as KeyedWeakReference?
      if (ref != null) {
        //在watchedObjects中删除不发送内存泄漏对象,剩下内存泄漏对象
        watchedObjects.remove(ref.key)
      }
    } while (ref != null)
  }
  //通知InternalLeakCanary发生内存泄漏 @HeapDumpTrigger.kt
  override fun onObjectRetained() {
    if (this::heapDumpTrigger.isInitialized) {
      //通知heapDumpTrigger有内存泄漏
      heapDumpTrigger.onObjectRetained()
    }
  }
  • LeakCanary检测内存泄漏的基本流程

1、 首先通过removeWeaklyReachablereference来移除已经被回收的Activity引用

2、 通过gone(reference)判断当前弱引用对应的Activity是否已经被回收,如果已经回收说明activity能够被GC,直接返回即可。

3、 如果Activity没有被回收,调用GcTigger.runGc方法运行GC,GC完成后在运行第1步,然后运行第2步判断Activity是否被回收了,如果这时候还没有被回收,那就说明Activity可能已经泄露。

4、 如果Activity泄露了,就抓取内存dump文件(Debug.dumpHprofData)

5、 之后通过HeapAnalyzerService.runAnalysis进行分析内存文件分析
接着通过HeapAnalyzer(checkForLeak—findLeakingReference---findLeakTrace)来进行内存泄漏分析。

6、 最后通过DisplayLeakService进行内存泄漏的展示。

参考

LeakCanary原理解析
LeakCanary2源码分析
关于LeakCanary2.0的四个问题

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容