一. 前语

之前有些过两篇剖析LeakCanary怎样监听各种目标的文章:

LeakCanary怎样监听Fragment、Fragment View、ViewModel销毁机遇?

LeakCanary怎样监听Service、Root View销毁机遇?

其实,在剖析LeakCanary怎样监听Root View(如Dialog、Toast窗口根View)的时候,并没有剖析RootViewWatcher是怎样完结监听使用一切窗口根View的增加和移除的(其时我也不明白),只需下面孤零零的一段牛逼的监听代码:

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

这也算是留下了一个遗憾吧,究竟学习到怎样监听使用一切窗口Root View的增加和移除的这种技巧,不管是对源码的认知,仍是对今后的项目功用的启发都必定会有协助的。

现在就来解开谜底了,Curtains是大名鼎鼎的Square提供的一个window协助库,而RootViewWatcher便是借助这个库监听Root View增加和移除,所以咱们就带着上面的问题细细剖析下这个库的完结机制了。

PS:剖析的Curtains库依靠版别:

dependencies {
    implementation 'com.squareup.curtains:curtains:1.2.4'
}

二. Curtains探究

Curtains.onRootViewsChangedListeners瞧一瞧

这儿咱们直接以上面的Curtains怎样协助RootViewWatcher监听Root View增加作为切入点剖析:

聊一聊LeakCanary的RootViewWatcher如何实现全局Root View监听

继续往下看:

聊一聊LeakCanary的RootViewWatcher如何实现全局Root View监听

其间rootViewsSpyRootViewsSpy类的实例目标,一起仍是Curtains内的一个特点。

聊一聊LeakCanary的RootViewWatcher如何实现全局Root View监听

能够看到,终究会将listener增加到RootViewsSpylisteners调集中。

聊一聊LeakCanary的RootViewWatcher如何实现全局Root View监听

这儿趁便说个知识点,+=是调集界说的运算符重载扩展函数完结的,相当于完结了add()操作。

接下来的目标就要寻找这个listeners调集中的元素什么时候被取出并履行的,接下来,咱们先看下创立RootViewsSpy目标时发生了哪些操作


RootViewsSpy的创立初始化

上面有说,rootViewsSpyCurtains的一个特点,而且仍是经过懒加载创立的,懒加载代码块中调用RootViewsSpy.install()完结终究的创立,咱们看下这个办法:

聊一聊LeakCanary的RootViewWatcher如何实现全局Root View监听

该办法创立RootViewSpy一起,还调用了非常要害的办法WindowManagerSpy.swapWindowManagerGlobalMViews{}

这个办法从表面上看,便是将该办法内部的mViews增加到delegatingViewList中,其间这个mViews便是搜集的使用全局一切的Root View目标,至于怎样获取的咱们下面会进行剖析的,这儿咱们看下delegatingViewList的操作:

聊一聊LeakCanary的RootViewWatcher如何实现全局Root View监听

delegatingViewList重写了调集的add()办法,这个办法会遍历上面的RootViewsSpy.listeners目标,终究履行了咱们增加的listener目标,从而终究完结了关于Root Viewattachdetach内存泄漏监听。

所以,WindowManagerSpy.swapWindowManagerGlobalMViews{}便是咱们本篇文章解说的核心,接下来咱们看下它是怎样搜集使用一切的Root View的。


WindowManagerSpy.swapWindowManagerGlobalMViews{}探究

聊一聊LeakCanary的RootViewWatcher如何实现全局Root View监听

要害代码mViewsField[windowManagerInstance] as ArrayList<View>,经过这个终究拿到了使用一切的Root View,而且很明显是经过反射获取的,其间mViewsField特点对应windowManagerInstance的一个字段。

接下来咱们看下最要害的mViewsField是个啥。


mViewsField是个啥?

聊一聊LeakCanary的RootViewWatcher如何实现全局Root View监听

mViewsField这个代表这windowManagerClass类中一个叫mViews的字段,windowManagerClass这个是个啥类:

聊一聊LeakCanary的RootViewWatcher如何实现全局Root View监听

到这儿,终于破案了,windowManagerClass便是WindowManagerGlobal目标,这个目标相信关于View烘托流程比较了解的人应该不会默认,这儿咱们简略说下这个类的效果:

界面烘托流程是从ActivityonResume开始的,其间在onResume界面会调用WindowManager.addView()办法完结窗口的增加,而这个办法经过WindowManagerImpl(WindowManager是接口,这个是其完结类)终究会调用到WindowManagerGlobal.addView(View)办法;

在这个办法中会将界面要烘托显现的根View增加到WindowManagerGlobal.mViews对应的调集中,所以只需咱们能拿到WindowManagerGlobal.mViews ,自然就能够拿到使用一切的Root View

像常见的DialogToast等显现的界面终究也是会调用WindowManager.addView()办法走到上面的这个流程中的。

其间,WindowManagerGlobal目标实例能够经过其提供的getInstance()静态办法获取:

聊一聊LeakCanary的RootViewWatcher如何实现全局Root View监听

对应在WindowManagerSpy中获取该实例目标的办法为:

private val windowManagerInstance by lazy(NONE) {
    windowManagerClass?.let { windowManagerClass ->
      val methodName = if (SDK_INT > 16) {
        "getInstance"
    } else {
        "getDefault"
    }
      windowManagerClass.getMethod(methodName).invoke(null)
  }
}

三. 总结

从上面的流程中,咱们能够得到这个机制完结的一个大致流程:

  1. 因为一切窗口的增加都会经过WindowManagerGlobal.addView(View)的办法,所以自然能够拿到窗口的根View并保存到WindowManagerGlobal.mViews中,所以咱们经过反射获取mViews字段实例;

  2. 接着咱们自界说一个ArrayList,监听调集的add()removeAt()办法,并经过反射将其赋值mViews

  3. 终究经过监听的add()办法调用外部传入的OnRootViewsChangedListener调集实例,也便是咱们文章一开始说到的listener,终究完结了关于Root Viewattachdetach监听:

    聊一聊LeakCanary的RootViewWatcher如何实现全局Root View监听

四. 后续

其实上面的这个用法只是curtains提供的一个小功用而已,关于这个curtains的其他功用探究后续有空会再一一进行剖析。


本文正在参加「金石计划」