阅读 841

Android-开源框架分析-【LeakCanary】

前言

本文旨在梳理其整个工作流程,文中源码基于 LeakCanary 2.7 版本

简介

1.什么是LeakCanary

LeakCanary 是 square 公司开源的内存泄漏检测工具。

2.什么是内存泄漏

内存泄漏是指没有用的对象资源仍与GcRoot保持可达路径,导致系统无法进行回收。

流程及解析

1.注册及观察

LeakCanary利用ContentProvider的onCreate生命周期处于Application的onCreate生命周期之前的特性,将注册部分写在ContentProvider中。

internal sealed class AppWatcherInstaller : ContentProvider() {
  override fun onCreate(): Boolean {
    val application = context!!.applicationContext as Application
    AppWatcher.manualInstall(application)
    return true
  }
}
复制代码

AppWatcher是初始化观察对象的入口处。

  @JvmOverloads
  fun manualInstall(
    application: Application,
    retainedDelayMillis: Long = TimeUnit.SECONDS.toMillis(5),
    watchersToInstall: List<InstallableWatcher> = appDefaultWatchers(application)
  ) {
    ...
    watchersToInstall.forEach {
      it.install()
    }
  }
复制代码

manualInstall方法的参数watchersToInstall默认实现Activity、Fragment、View、ViewModel和Service的监听。

1.1 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)
  }
}
复制代码

不难看出通过application监听Activity的生命周期,在onDestory回调expectWeaklyReachable方法(此方法后续解释),做内存泄漏的检查工作。

1.2 FragmentAndViewModelWatcher

FragmentAndViewModelWatcher主要检测了三种

  1. Fragment#onDestroy()
  2. Fragment#onDestroyView()
  3. ViewModel#onCleared()
class FragmentAndViewModelWatcher(
  private val application: Application,
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

  private val fragmentDestroyWatchers: List<(Activity) -> Unit> = run {
    val fragmentDestroyWatchers = mutableListOf<(Activity) -> Unit>()
    //sdk在8.0以上 监听
    if (SDK_INT >= O) {
      fragmentDestroyWatchers.add(
        AndroidOFragmentDestroyWatcher(reachabilityWatcher)
      )
    }
    //androidX包中fragment的处理
    getWatcherIfAvailable(
      ANDROIDX_FRAGMENT_CLASS_NAME,
      ANDROIDX_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
      reachabilityWatcher
    )?.let {
      fragmentDestroyWatchers.add(it)
    }
    //区分support包中的fragment
    getWatcherIfAvailable(
      ANDROID_SUPPORT_FRAGMENT_CLASS_NAME,
      ANDROID_SUPPORT_FRAGMENT_DESTROY_WATCHER_CLASS_NAME,
      reachabilityWatcher
    )?.let {
      fragmentDestroyWatchers.add(it)
    }
    fragmentDestroyWatchers
  }

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

  override fun install() {
    application.registerActivityLifecycleCallbacks(lifecycleCallbacks)
  }

  override fun uninstall() {
    application.unregisterActivityLifecycleCallbacks(lifecycleCallbacks)
  }
复制代码

1.3 RootViewWatcher

通过OnAttachStateChangeListener的onViewAttachedToWindow和onViewDetachedFromWindow方法回调可做内存泄漏的检查工作。

class RootViewWatcher(
  private val reachabilityWatcher: ReachabilityWatcher
) : InstallableWatcher {

  private val listener = OnRootViewAddedListener { rootView ->
    //这里区分了PopupWindow创建的RootView对象是否需要判断,假如写一个单例的Dialog仅使用show()和hide()控制 而不会销毁对象 不属于泄漏
    val trackDetached = when(rootView.windowType) {
      PHONE_WINDOW -> {
        when (rootView.phoneWindow?.callback?.wrappedCallback) {
          is Activity -> false
          is Dialog -> rootView.resources.getBoolean(R.bool.leak_canary_watcher_watch_dismissed_dialogs)
          else -> true
        }
      }
      POPUP_WINDOW -> false
      TOOLTIP, TOAST, UNKNOWN -> true
    }

    if (trackDetached) {
      //addOnAttachStateChangeListener监听
      rootView.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {

        val watchDetachedView = Runnable {
          reachabilityWatcher.expectWeaklyReachable(
            rootView, "${rootView::class.java.name} received View#onDetachedFromWindow() callback"
          )
        }

        override fun onViewAttachedToWindow(v: View) {
          mainHandler.removeCallbacks(watchDetachedView)
        }

        override fun onViewDetachedFromWindow(v: View) {
          mainHandler.post(watchDetachedView)
        }
      })
    }
  }

  override fun install() {
    Curtains.onRootViewsChangedListeners += listener
  }

  override fun uninstall() {
    Curtains.onRootViewsChangedListeners -= listener
  }
}
复制代码

1.4 ServiceWatcher

Service通过hook ActivityThread的H类和AMS,当AMS调用serviceDoneExecuting方法可做内存泄漏的检查工作。

class ServiceWatcher(private val reachabilityWatcher: ReachabilityWatcher) : InstallableWatcher {
   ...
  override fun install() {
    ...
    try {
    //hook ActivityThread中H类的Callback 
      swapActivityThreadHandlerCallback { mCallback ->
        uninstallActivityThreadHandlerCallback = {
          swapActivityThreadHandlerCallback {
            mCallback
          }
        }
        //在收到STOP_SERVICE时将Service以弱应用形式记录
        Handler.Callback { msg ->
          if (msg.what == STOP_SERVICE) {
            val key = msg.obj as IBinder
            activityThreadServices[key]?.let {
              onServicePreDestroy(key, it)
            }
          }
          mCallback?.handleMessage(msg) ?: false
        }
      }
      //这里hook了AMS
      swapActivityManager { activityManagerInterface, activityManagerInstance ->
        uninstallActivityManager = {
          swapActivityManager { _, _ ->
            activityManagerInstance
          }
        }
        //通过代理形式 在调用serviceDoneExecuting方法时可判断对象是否泄漏
        Proxy.newProxyInstance(
          activityManagerInterface.classLoader, arrayOf(activityManagerInterface)
        ) { _, method, args ->
          if (METHOD_SERVICE_DONE_EXECUTING == method.name) {
            val token = args!![0] as IBinder
            if (servicesToBeDestroyed.containsKey(token)) {
              onServiceDestroyed(token)
            }
          }
          try {
            if (args == null) {
              method.invoke(activityManagerInstance)
            } else {
              method.invoke(activityManagerInstance, *args)
            }
          } catch (invocationException: InvocationTargetException) {
            throw invocationException.targetException
          }
        }
      }
    } catch (ignored: Throwable) {
      SharkLog.d(ignored) { "Could not watch destroyed services" }
    }
  }

  override fun uninstall() {
    checkMainThread()
    uninstallActivityManager?.invoke()
    uninstallActivityThreadHandlerCallback?.invoke()
    uninstallActivityManager = null
    uninstallActivityThreadHandlerCallback = null
  }
    //
  private fun onServiceDestroyed(token: IBinder) {
    servicesToBeDestroyed.remove(token)?.also { serviceWeakReference ->
      serviceWeakReference.get()?.let { service ->
        reachabilityWatcher.expectWeaklyReachable(
          service, "${service::class.java.name} received Service#onDestroy() callback"
        )
      }
    }
  }
  ...省略了hook代码部分
 }
复制代码

2. 寻找泄露对象-ObjectWatcher

当对象在恰当的时机回收后,通过ObjectWatcher的expectWeaklyReachable方法进行观察是否有泄露

  @Synchronized override fun expectWeaklyReachable(
    watchedObject: Any,
    description: String
  ) {
    //移除非泄漏的弱引用对象
    removeWeaklyReachableObjects()
    //为此对象生成唯一随机id
    val key = UUID.randomUUID().toString()
    val watchUptimeMillis = clock.uptimeMillis()
    val reference = KeyedWeakReference(watchedObject, key, description, watchUptimeMillis, queue)
    //将此对象包成weakRefrence对象 然后加入队列中
    watchedObjects[key] = reference
    //5s后弱引用依然存在则有泄漏风险
    checkRetainedExecutor.execute {
      moveToRetained(key)
    }
  }
复制代码

接下来检测泄漏的对象

  private fun checkRetainedObjects() {
    ...
    var retainedReferenceCount = objectWatcher.retainedObjectCount
    //存在泄漏对象的话手动GC一次 然后再次检测
    if (retainedReferenceCount > 0) {
      gcTrigger.runGc()
      retainedReferenceCount = objectWatcher.retainedObjectCount
    }
    //如果泄漏数量不超出阈值(默认5) 结束
    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"
    //dump文件 通过原生Debug.dumpHprofData(heapDumpFile.absolutePath);
    dumpHeap(
      retainedReferenceCount = retainedReferenceCount,
      retry = true,
      reason = "$retainedReferenceCount retained objects, app is $visibility"
    )
  }
复制代码

这一步并不会立刻触发dump生成hprof文件分析,而是会先触发GC回收对象,再次判断未回收的对象数量,如果超出5个,通过原生方式生成hprof文件,再通过HeapAnalyzerService分析这个文件。
HeapAnalyzerService属于是square的另一个开源库shark,是一个前台服务,整个流程通过analyzeHeap方法分析文件,将分析出的结果包装成一个HeapAnalysisSuccess对象通过onHeapAnalyzedListener回调。

3. 分析Hprof

hprof文件的标准协议主要由head和body组成,body是由一系列不同类型的Record组成,Record主要用于描述trace、object、thread等信息,依次分为4个部分:TAG、TIME、LENGTH、BODY,其中TAG就是表示Record类型。Record之间依次排列或嵌套,最终组成hprof文件。

多个Record被进抽象为HprofMemoryIndex,Index可以快速定位到对应对象在hprof文件中的位置;最终Index和Hprof一起再组成HprofGraph,graph做为hprof的最上层描述,将所有堆中数据抽象为了 gcRoots、objects、classes、instances等集合。

interface HeapGraph {
  val gcRoots: List<GcRoot>

  val objects: Sequence<HeapObject>

  val classes: Sequence<HeapClass>

  val instances: Sequence<HeapInstance>

  val objectArrays: Sequence<HeapObjectArray>

  ...
}
复制代码

后续通过Graph的objects集合找出泄露对象,然后通过广度优先遍历找出其到GcRoot的引用路径链,结束流程。

总结/最后

LeakCanary通过ContentProvider注册,在对象被销毁时利用WeakRefrence + RefrenceQueue + GC观察泄露对象,dump hprof文件,通过shark库分析文件,查找泄露对象引用路径。

文章分类
Android
文章标签