概述
Activity,Window,View三者之间到底是什么关系? 如果长期只做CV工程师,那么很难了解到这个层面。答案并不复杂,只需要深入源码探索一番,探索的流程请看正文。
从Activity的setContentView开始
当我们创建一个新的布局,并把这布局指向一个Activity时,通常的做法就是,新建一个Activity,然后调用 setContentView(R.layout.my_layout);,最后 startActivity来启动这个新的Activity。 平常写代码,都接触不到Window这一层。也不知道 我们给出去的R.layout.my_layout,是如何变成 新的Activity上的那些View实例。
可是只要我们点开Activity.java中setContentView的源代码:

从上面可以得知,Activity只是将 layoutResId交给了 一个 Window类型的实例去处理的。Activity本身并没有做任何事。
所以,看到这里,可以得出结论,setContenView的实际操作,都委托给了 Window 的 setContentView()
插播解释 Window来源
我们在上一篇 juejin.cn/post/729814... 中,startActivity 最终会调用 ActivityThread的 performLaunchActivity方法,反射创建Activity对象,并执行它的 attach方法。

实际上,上面提到的Window只有一个实现类,那就是上图看到的PhoneWindow, 接下来,调用 mWindow.setWindowManager(),来将系统的WindowManager传给了mWindow.
获取系统WindowManager的方式为:
java
(WindowManager)content.getSystemService(Context.WINDOW_SERVICE)
下图是 Window类的 setWindowManager()源码: 
Window类在拿到系统WindowManager之后,利用它 createLocalWindowManager(this)创建了一个WindowManager对象,并放在了 Window对象中。
核心过程来到 PhoneWindow的setContentView

上图中,有两个关键点:
-
installDecor()
- 如果 mContentView为空,就执行 installDecor(),确保 mContentView不为空,然后往下执行。
-
mLayoutInflater.inflate(layoutResId,mContentParent)
- layoutResId这个布局文件最终交给了 mLayoutInflater,它将会根据 布局文件,反射生成一个 viewGroup实例,并将它挂到 mContentView中。
跟踪源代码到这里,可以得出结论:

新的 Activity 组成部分是
- 一个最外层的 PhoneWindow,PhoneWindow内部自带一个 DecorView(FrameLayout类型)
- DecorView内部包含一个 mContentView(ViewGroup)
- 我们在Activity中做的setContentView(R.layout.my_layout)的动作,最终转换出了一个 ViewGroup实例,挂到了 mContentView中。
简而言之,就是 R.layout.my_layout -> mContentView的子view。
注意
Activity执行完onCreate之后并不是可见的。只有在执行完onResume之后才是可见且可交互的。因为: onCreate阶段只是创建了Activity要显示的内容,而在onResume阶段才会将 创建出来的内容绘制在屏幕上。 而onResume阶段,ActivityThread UI线程做的最重要的事情,就是下图所示 handleResumeActivity,源代码如下:

其中wm就是WindowManager,执行它的 addView方法,就是把 onCreate阶段创建出来的 decorView 添加到 windowManager上。
在这一步执行完毕之后
DecorView将会被渲染到屏幕上DecorView可以接收屏幕触摸事件
接下来重点观察 WindowManager.addView() 具体做了什么.
WindowManager的addView
WindowManager是一个接口,它的唯一实现是 WindowManagerImpl , 下图是addView方法的内容。 
上图中,mGlobal.addView,mGlobal的类型是 WindowManagerGLobal,mGlobal 是一个单例,每个app进程中仅有一个。
红框标记的关键代码解读:
-
root = new ViewRootImpl(view.getContent(),display);- 创建出一个 ViewRootImpl 对象
-
root.setView(view, wparams, panelParentView, userId);- 用
ViewRootImpl的setView将view添加到WindowManager中
- 用
ViewRootImpl的setView

- 图中1处,requestLayout执行之后,viewRoot所关联的view将会执行 测量布局绘制操作,确保添加到屏幕上之前,已经是一个绘制完成的View。
- 图中2处,调用 mWindowSession,将View添加到WindowManager中。
- windowSession是 WindowManagerGlobal中的单例 Binder 对象,用于本app进程与系统服务进行通信

在执行完了 mWindowSession.addToDisplay之后,window成功地传递到了WindowManagerService,剩余的重点工作,就全部转移到了系统的WindowManagerService中。
小结
通过 activity的setContentView的源码跟踪可以得出结论:
添加View的操作 完全不在 Activity中,而是在PhoneWindow,在PhoneWindow背后,还有 WindowManagerService(WMS) 在全盘负责。 addView的过程中,Activity没什么参与感。
但是。在activity 执行完了 onResume之后, 屏幕如果接收到touch事件,这个touch首先是Activity感知到,然后往下分发给 WMS 创建出来的那些View。
那么问题就来了,Touch事件是如何传递到 Activity的呢?
ViewRootImpl.setView

上图中,mWindowSession.addToDisplay执行完了,view就会出现在屏幕上,但是触摸事件的设置,是在上图红框内的代码中。这段代码,设置了输入事件的处理逻辑。
当手指碰到屏幕之后,第一时间收到触摸事件的是 硬件驱动层,驱动层通过socket通信,将事件告知 androidFrameWork层,其实也就是WMS,之后,touch事件会到达上图中的 input pipeline 输入管道中。
这些输入管道,其实是链表结构。
下图是 InputState的源代码:

touch事件会被 onProcess 捕获,然后最终会调用 图中2处的 mView.dispatchPointerEvent() ,mView实际上就是我们创建出来的 DecorView
View.dispatchPointerEvent() 的源代码如下:

最终调用了 WindowCallback 的 dispatchTouchEvent方法。
这个 WindowCallback 实际上就是 Activity类型。原因如下:
在 startActivity阶段,创建Activity对象,并调用了attach方法,其中,就有这么一句mWindow.setCallback(this), 这里的this,就是 Activity 对象。 
那么触摸事件的传递,就到达了 Activit.java 的 dispathTouchEvent。
上图可以看出,虽然名义上是由 Activity在处理触摸事件,但是实际上还是交给了 PhoneWindow,PhoneWindow 又交给了 DecorView,执行的还是 DecorView的 dispatchTouchEvent.
总结
-
一个
Activity中有一个Window,也就是PhoneWindow对象,PhoneWindow中有一个DecorView,setContentView时,会将layout填充到DecorView中。 -
一个应用进程中只有一个
WindowManagerGlobal对象,因为在ViewRootImpl中它是静态的 -
每个
PhoneWindow对应一个ViewRootImpl,因为是new出来的 -
WindowManagerGlobal通过调用ViewRootImpl的setView,完成 window的添加过程 -
ViewRootImpl的setView方法主要完成两件事,View的渲染(requestLayout)和 处理事件的接收传递。
TIPS
Q: 为什么引入某些第三方SDK的时候一定要同时引入 混淆规则?
A: 这是因为:androidFramework中很多地方使用了反射来创建对象, 比如,如果开启混淆,打release包。我们用到的一个第三方SDK中定义了一个XXXActivity,那么这个类就不能被混淆,否则,就会出现 start这个XXXActivity不成功的现象,报错内容为:找不到某某Activity。 再比如,第三方SDK中自定义了一个View,然后我们在 自己的布局文件中使用到了这个view,写了全类名,此时,如果没有将这个view的全类名写入到混淆规则中,那么也会报错,找不到这个View所在的类。