ViewRootImpl创建于WindowManagerGlobal的addView()方法中,而调用addView()方法的线程即是此ViewRootImpl所掌控的控件树的UI线程。ViewRootImpl的构造主要是初始化了一些重要的成员,事先对这些重要的成员有个初步的认识对随后探讨ViewRootImpl的工作原理有很大的帮助。其构造函数代码如下:
**ViewRootImpl.java-->ViewRootImpl.ViewRootImpl()**
```
public ViewRootImpl(Context context, Displaydisplay) {
/* ① 从WindowManagerGlobal中获取一个IWindowSession的实例。它是ViewRootImpl和
WMS进行通信的代理 */
mWindowSession= WindowManagerGlobal.getWindowSession(context.getMainLooper());
// **②保存参数display**,在后面setView()调用中将会把窗口添加到这个Display上
mDisplay= display;
CompatibilityInfoHolder cih = display.getCompatibilityInfo();
mCompatibilityInfo = cih != null ? cih : new CompatibilityInfoHolder();
/* **③ 保存当前线程到mThread。**这个赋值操作体现了创建ViewRootImpl的线程如何成为UI主线程。
在ViewRootImpl处理来自控件树的请求时(如请求重新布局,请求重绘,改变焦点等),会检
查发起请求的thread与这个mThread是否相同。倘若不同则会拒绝这个请求并抛出一个异常*/
mThread= Thread.currentThread();
......
/* **④ mDirty用于收集窗口中的无效区域。**所谓无效区域是指由于数据或状态发生改变时而需要进行重绘
的区域。举例说明,当应用程序修改了一个TextView的文字时,TextView会将自己的区域标记为无效
区域,并通过invalidate()方法将这块区域收集到这里的mDirty中。当下次绘制时,TextView便
可以将新的文字绘制在这块区域上 */
mDirty =new Rect();
mTempRect = new Rect();
mVisRect= new Rect();
/* **⑤ mWinFrame,描述了当前窗口的位置和尺寸。**与WMS中WindowState.mFrame保持着一致 */
mWinFrame = new Rect();
/* ⑥ 创建一个W类型的实例,W是IWindow.Stub的子类。即它将在WMS中作为新窗口的ID,以及接
收来自WMS的回调*/
mWindow= new W(this);
......
/* **⑦ 创建mAttachInfo。**mAttachInfo是控件系统中很重要的对象。它存储了此当前控件树所以贴附
的窗口的各种有用的信息,并且会派发给控件树中的每一个控件。这些控件会将这个对象保存在自己的
mAttachInfo变量中。mAttachInfo中所保存的信息有WindowSession,窗口的实例(即mWindow),
ViewRootImpl实例,窗口所属的Display,窗口的Surface以及窗口在屏幕上的位置等等。所以,当
要需在一个View中查询与当前窗口相关的信息时,非常值得在mAttachInfo中搜索一下 */
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display,this, mHandler, this);
/* **⑧ 创建FallbackEventHandler。**这个类如同PhoneWindowManger一样定义在android.policy
包中,其实现为PhoneFallbackEventHandler。FallbackEventHandler是一个处理未经任何人
消费的输入事件的场所。在6.5.4节中将会介绍它 */
mFallbackEventHandler =PolicyManager.makeNewFallbackEventHandler(context);
......
/* ⑨ 创建一个依附于当前线程,即主线程的Choreographer,用于通过VSYNC特性安排重绘行为 */
mChoreographer= Choreographer.getInstance();
......
}
```
在构造函数之外,还有另外两个重要的成员被直接初始化:
- mHandler,类型为ViewRootHandler,一个依附于创建ViewRootImpl的线程,即主线程上的,用于将某些必须主线程进行的操作安排在主线程中执行。mHandler与mChoreographer的同时存在看似有些重复,其实它们拥有明确不同的分工与意义。由于mChoreographer处理消息时具有VSYNC特性,因此它主要用于处理与重绘相关的操作。但是由于mChoreographer需要等待VSYNC的垂直同步事件来触发对下一条消息的处理,因此它处理消息的及时性稍逊于mHandler。而mHandler的作用,则是为了将发生在其他线程中的事件安排在主线程上执行。所谓发生在其他线程中的事件是指来自于WMS,由继承自IWindow.Stub的mWindow引发的回调。由于mWindow是一个Binder对象的Bn端,因此这些回调发生在Binder的线程池中。而这些回调会影响到控件系统的重新测量、布局与绘制,因此需要此Handler将回调安排到主线程中。
说明 mHandler与mThread两个成员都是为了单线程模型而存在的。Android的UI操作不是线程安全的,而且很多操作也是建立在单线程的假设之上(如scheduleTraversals())。采用单线程模型的目的是降低系统的复杂度,并且降低锁的开销。
- mSurface,类型为Surface。采用无参构造函数创建的一个Surface实例。mSurface此时是一个没有任何内容的空壳子,在 WMS通过relayoutWindow()为其分配一块Surface之前尚不能实用。
- mWinFrame、mPendingContentInset、mPendingVisibleInset以及mWidth,mHeight。这几个成员存储了窗口布局相关的信息。其中mWinFrame、mPendingConentInsets、mPendingVisibleInsets与窗口在WMS中的Frame、ContentInsets、VisibleInsets是保持同步的。这是因为这3个成员不仅会作为 relayoutWindow()的传出参数,而且ViewRootImpl在收到来自WMS的回调IWindow.Stub.resize()时,立即更新这3个成员的取值。因此这3个成员体现了窗口在WMS中的最新状态。与mWinFrame中的记录窗口在WMS中的尺寸不同的是,mWidth/mHeight记录了窗口在ViewRootImpl中的尺寸,二者在绝大多数情况下是相同的。当窗口在WMS中被重新布局而导致尺寸发生变化时,mWinFrame会首先被IWindow.Stub.resize()回调更新,此时mWinFrame便会与mWidth/mHeight产生差异。此时ViewRootImpl即可得知需要对控件树进行重新布局以适应新的窗口变化。在布局完成后,mWidth/mHeight会被赋值为mWinFrame中所保存的宽和高,二者重新统一。在随后分析performTraversals()方法时,读者将会看到这一处理。另外,与mWidth/mHeight类似,ViewRootImpl也保存了窗口的位置信息Left/Top以及ContentInsets/VisibleInsets供控件树查询,不过这四项信息被保存在了mAttachInfo中。
ViewRootImpl的在其构造函数中初始化了一系列的成员变量,然而其创建过程仍未完成。仅在为其指定了一个控件树进行管理,并向WMS添加了一个新的窗口之后,ViewRootImpl承上启下的角色才算完全确立下来。因此需要进一步分析ViewRootImpl.setView()方法。
**ViewRootImp.java-->ViewRootImpl.setView()**
```
public void setView(View view,WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
// **① mView保存了控件树的根**
mView = view;
......
// ②mWindowAttributes保存了窗口所对应的LayoutParams
mWindowAttributes.copyFrom(attrs);
......
/* 在添加窗口之前,先通过requestLayout()方法在主线程上安排一次“遍历”。所谓
“遍历”是指ViewRootImpl中的核心方法performTraversals()。这个方法实现了对
控件树进行测量、布局、向WMS申请修改窗口属性以及重绘的所有工作。由于此“遍历”
操作对于初次遍历做了一些特殊处理,而来自WMS通过mWindow发生的回调会导致一些属性
发生变化,如窗口的尺寸、Insets以及窗口焦点等,从而有可能使得初次“遍历”的现场遭
到破坏。因此,需要在添加窗口之前,先发送一个“遍历”消息到主线程。
在主线程中向主线程的Handler发送消息如果使用得当,可以产生很精妙的效果。例如本例
中可以实现如下的执行顺序:添加窗口->初次遍历->处理来自WMS的回调 */
requestLayout();
/***③ 初始化mInputChannel。**参考第五章,InputChannel是窗口接受来自InputDispatcher
的输入事件的管道。 注意,仅当窗口的属性inputFeatures不含有
INPUT_FEATURE_NO_INPUT_CHANNEL时才会创建InputChannel,否则mInputChannel
为空,从而导致此窗口无法接受任何输入事件 */
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
try {
......
/* 将窗口添加到WMS中。完成这个操作之后,mWindow已经被添加到指定的Display中去
而且mInputChannel(如果不为空)已经准备好接受事件了。只是由于这个窗口没有进行
过relayout(),因此它还没有有效的Surface可以进行绘制 */
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mInputChannel);
} catch (RemoteException e) {......} finally { ...... }
......
if (res < WindowManagerGlobal.ADD_OKAY) {
// 错误处理。窗口添加失败的原因通常是权限问题,重复添加,或者tokeen无效
}
......
/*④ 如果mInputChannel不为空,则创建mInputEventReceiver,用于接受输入事件。
注意第二个参数传递的是Looper.myLooper(),即mInputEventReceiver将在主线程上
触发输入事件的读取与onInputEvent()。这是应用程序可以在onTouch()等事件响应中
直接进行UI操作等根本原因。
*/
if (mInputChannel != null) {
......
mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
Looper.myLooper());
}
/* ViewRootImpl将作为参数view的parent。所以,ViewRootImpl可以从控件树中任何一个
控件开始,通过回溯getParent()的方法得到 */
view.assignParent(this);
......
}
}
}
```
至此,ViewRootImpl所有重要的成员都已经初始化完毕,新的窗口也已经添加到WMS中。ViewRootImpl的创建过程是由构造函数和setView()方法两个环节构成的。其中构造函数主要进行成员的初始化,setView()则是创建窗口、建立输入事件接收机制的场所。同时,触发第一次“遍历”操作的消息已经发送给主线程,在随后的第一次“遍历”完成后,ViewRootImpl将会完成对控件树的第一次测量、布局,并从WMS获取窗口的Surface以进行控件树的初次绘制工作。
在本节的最后,通过图 6 – 4对ViewRootImpl中的重要成员进行了分类整理。
:-: ![](http://img.blog.csdn.net/20150814133547769?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
图 6 - 4 ViewRootImpl中的主要成员
- 前言
- 推荐序
- 第1章 开发环境部署
- 1.1获取Android源代码
- 1.2Android的编译
- 1.3在IDE中导入Android源代码
- 1.3.1将Android源代码导入Eclipse
- 1.3.2将Android源代码导入SourceInsight
- 1.4调试Android源代码
- 1.4.1使用Eclipse调试Android Java源代码
- 1.4.2使用gdb调试Android C/C 源代码
- 1.5本章小结
- 第2章 深入理解Java Binder和MessageQueue
- 2.1概述
- 2.2Java层中的Binder分析
- 2.2.1Binder架构总览
- 2.2.2初始化Java层Binder框架
- 2.2.3窥一斑,可见全豹乎
- 2.2.4理解AIDL
- 2.2.5Java层Binder架构总结
- 2.3心系两界的MessageQueue
- 2.3.1MessageQueue的创建
- 2.3.2提取消息
- 2.3.3nativePollOnce函数分析
- 2.3.4MessageQueue总结
- 2.4本章小结
- 第3章 深入理解AudioService
- 3.1概述
- 3.2音量管理
- 3.2.1音量键的处理流程
- 3.2.2通用的音量设置函数setStreamVolume()
- 3.2.3静音控制
- 3.2.4音量控制小结
- 3.3音频外设的管理
- 3.3.1 WiredAccessoryObserver 设备状态的监控
- 3.3.2AudioService的外设状态管理
- 3.3.3音频外设管理小结
- 3.4AudioFocus机制的实现
- 3.4.1AudioFocus简单的例子
- 3.4.2AudioFocus实现原理简介
- 3.4.3申请AudioFocus
- 3.4.4释放AudioFocus
- 3.4.5AudioFocus小结
- 3.5AudioService的其他功能
- 3.6本章小结
- 第4章 深入理解WindowManager-Service
- 4.1初识WindowManagerService
- 4.1.1一个从命令行启动的动画窗口
- 4.1.2WMS的构成
- 4.1.3初识WMS的小结
- 4.2WMS的窗口管理结构
- 4.2.1理解WindowToken
- 4.2.2理解WindowState
- 4.2.3理解DisplayContent
- 4.3理解窗口的显示次序
- 4.3.1主序、子序和窗口类型
- 4.3.2通过主序与子序确定窗口的次序
- 4.3.3更新显示次序到Surface
- 4.3.4关于显示次序的小结
- 4.4窗口的布局
- 4.4.1从relayoutWindow()开始
- 4.4.2布局操作的外围代码分析
- 4.4.3初探performLayoutAndPlaceSurfacesLockedInner()
- 4.4.4布局的前期处理
- 4.4.5布局DisplayContent
- 4.4.6布局的阶段
- 4.5WMS的动画系统
- 4.5.1Android动画原理简介
- 4.5.2WMS的动画系统框架
- 4.5.3WindowAnimator分析
- 4.5.4深入理解窗口动画
- 4.5.5交替运行的布局系统与动画系统
- 4.5.6动画系统总结
- 4.6本章小结
- 第5章 深入理解Android输入系统
- 5.1初识Android输入系统
- 5.1.1getevent与sendevent工具
- 5.1.2Android输入系统简介
- 5.1.3IMS的构成
- 5.2原始事件的读取与加工
- 5.2.1基础知识:INotify与Epoll
- 5.2.2 InputReader的总体流程
- 5.2.3 深入理解EventHub
- 5.2.4 深入理解InputReader
- 5.2.5原始事件的读取与加工总结
- 5.3输入事件的派发
- 5.3.1通用事件派发流程
- 5.3.2按键事件的派发
- 5.3.3DispatcherPolicy与InputFilter
- 5.3.4输入事件的派发总结
- 5.4输入事件的发送、接收与反馈
- 5.4.1深入理解InputChannel
- 5.4.2连接InputDispatcher和窗口
- 5.4.3事件的发送
- 5.4.4事件的接收
- 5.4.5事件的反馈与发送循环
- 5.4.6输入事件的发送、接收与反馈总结
- 5.5关于输入系统的其他重要话题
- 5.5.1输入事件ANR的产生
- 5.5.2 焦点窗口的确定
- 5.5.3以软件方式模拟用户操作
- 5.6本章小结
- 第6章 深入理解控件系统
- 6.1 初识Android的控件系统
- 6.1.1 另一种创建窗口的方法
- 6.1.2 控件系统的组成
- 6.2 深入理解WindowManager
- 6.2.1 WindowManager的创建与体系结构
- 6.2.2 通过WindowManagerGlobal添加窗口
- 6.2.3 更新窗口的布局
- 6.2.4 删除窗口
- 6.2.5 WindowManager的总结
- 6.3 深入理解ViewRootImpl
- 6.3.1 ViewRootImpl的创建及其重要的成员
- 6.3.2 控件系统的心跳:performTraversals()
- 6.3.3 ViewRootImpl总结
- 6.4 深入理解控件树的绘制
- 6.4.1 理解Canvas
- 6.4.2 View.invalidate()与脏区域
- 6.4.3 开始绘制
- 6.4.4 软件绘制的原理
- 6.4.5 硬件加速绘制的原理
- 6.4.6 使用绘图缓存
- 6.4.7 控件动画
- 6.4.8 绘制控件树的总结
- 6.5 深入理解输入事件的派发
- 6.5.1 触摸模式
- 6.5.2 控件焦点
- 6.5.3 输入事件派发的综述
- 6.5.4 按键事件的派发
- 6.5.5 触摸事件的派发
- 6.5.6 输入事件派发的总结
- 6.6 Activity与控件系统
- 6.6.1 理解PhoneWindow
- 6.6.2 Activity窗口的创建与显示
- 6.7 本章小结
- 第7章 深入理解SystemUI
- 7.1 初识SystemUI
- 7.1.1 SystemUIService的启动
- 7.1.2 状态栏与导航栏的创建
- 7.1.3 理解IStatusBarService
- 7.1.4 SystemUI的体系结构
- 7.2 深入理解状态栏
- 7.2.1 状态栏窗口的创建与控件树结构
- 7.2.2 通知信息的管理与显示
- 7.2.3 系统状态图标区的管理与显示
- 7.2.4 状态栏总结
- 7.3 深入理解导航栏
- 7.3.1 导航栏的创建
- 7.3.2 虚拟按键的工作原理
- 7.3.3 SearchPanel
- 7.3.4 关于导航栏的其他话题
- 7.3.5 导航栏总结
- 7.4 禁用状态栏与导航栏的功能
- 7.4.1 如何禁用状态栏与导航栏的功能
- 7.4.2 StatusBarManagerService对禁用标记的维护
- 7.4.3 状态栏与导航栏对禁用标记的响应
- 7.5 理解SystemUIVisibility
- 7.5.1 SystemUIVisibility在系统中的漫游过程
- 7.5.2 SystemUIVisibility发挥作用
- 7.5.3 SystemUIVisibility总结
- 7.6 本章小结
- 第8章 深入理解Android壁纸
- 8.1 初识Android壁纸
- 8.2深入理解动态壁纸
- 8.2.1启动动态壁纸的方法
- 8.2.2壁纸服务的启动原理
- 8.2.3 理解UpdateSurface()方法
- 8.2.4 壁纸的销毁
- 8.2.5 理解Engine的回调
- 8.3 深入理解静态壁纸-ImageWallpaper
- 8.3.1 获取用作静态壁纸的位图
- 8.3.2 静态壁纸位图的设置
- 8.3.3 连接静态壁纸的设置与获取-WallpaperObserver
- 8.4 WMS对壁纸窗口的特殊处理
- 8.4.1 壁纸窗口Z序的确定
- 8.4.2 壁纸窗口的可见性
- 8.4.3 壁纸窗口的动画
- 8.4.4 壁纸窗口总结
- 8.5 本章小结