💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
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中的主要成员