🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
参考WindowManagerGlobal.addView()的代码: **WindowManagerGlobal.java-->WindowManagerGlobal.addView()** ``` publicvoid addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow){ ......// 参数检查 final WindowManager.LayoutParams wparams =(WindowManager.LayoutParams)params; /* ① 如果当前窗口需要被添加为另一个窗口的附属窗口(子窗口),则需要让父窗口视自己的情况 对当前窗口的布局参数(LayoutParams)进行一些修改 */ if(parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } ViewRootImpl root; ViewpanelParentView = null; synchronized (mLock) { ...... // WindowManager不允许同一个View被添加两次 int index = findViewLocked(view, false); if (index >= 0) { throw new IllegalStateException("......");} // ② 创建一个ViewRootImpl对象并保存在root变量中 root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); /* ③ 将作为窗口的控件、布局参数以及新建的ViewRootImpl以相同的索引值保存在三个 **数组中。**到这步为止,我们可以认为完成了窗口信息的添加工作 */ mViews[index] = view; mRoots[index] = root; mParams[index] = wparams; } try{ /* **④ 将作为窗口的控件设置给ViewRootImpl。**这个动作将导致ViewRootImpl向WMS 添加新的窗口、申请Surface以及托管控件在Surface上的重绘动作。这才是真正意义上 完成了窗口的添加操作*/ root.setView(view, wparams, panelParentView); }catch (RuntimeException e) { ...... } } ``` 添加窗口的代码并不复杂。其中的关键点有: - 父窗口修改新窗口的布局参数。可能修改的只有LayoutParams.token和LayoutParams.mTitle两个属性。mTitle属性不必赘述,仅用于调试。而token属性则值得一提。回顾一下第4章的内容,每一个新窗口必须通过LayoutParams.token向WMS出示相应的令牌才可以。在addView()函数中通过父窗口修改这个token属性的目的是为了减少开发者的负担。开发者不需要关心token到底应该被设置为什么值,只需将LayoutParams丢给一个WindowManager,剩下的事情就不用再关心了。父窗口修改token属性的原则是:如果新窗口的类型为子窗口(其类型大于等于LayoutParams.FIRST\_SUB\_WINDOW并小于等于LayoutParams.LAST\_SUB\_WINDOW),则LayoutParams.token所持有的令牌为其父窗口的ID(也就是IWindow.asBinder()的返回值)。否则LayoutParams.token将被修改为父窗口所属的Activity的ID(也就是在第4章中所介绍的AppToken),这对类型为TYPE\_APPLICATION的新窗口来说非常重要。从这点来说,当且仅当新窗的类型为子窗口时addView()的parentWindow参数才是真正意义上的父窗口。这类子窗口有上下文菜单、弹出式菜单以及游标等等,在WMS中,这些窗口对应的WindowState所保存的mAttachedWindow既是parentWindow所对应的WindowState。然而另外还有一些窗口,如对话框窗口,类型为TYPE\_APPLICATION, 并不属于子窗口,但需要AppToken作为其令牌,为此parentWindow将自己的AppToken赋予了新窗口的的LayoutParams.token中。此时parentWindow便并不是严格意义上的父窗口了。 - 为新窗口创建一个ViewRootImpl对象。顾名思义,ViewRootImpl实现了一个控件树的根。它负责与WMS进行直接的通讯,负责管理Surface,负责触发控件的测量与布局,负责触发控件的绘制,同时也是输入事件的中转站。总之,ViewRootImpl是整个控件系统正常运转的动力所在,无疑是本章最关键的一个组件。 - 将控件、布局参数以及新建的ViewRootImpl以相同的索引值添加到三个对应的数组mViews、mParams以及mRoots中,以供之后的查询之需。控件、布局参数以及ViewRootImpl三者共同组成了客户端的一个窗口。或者说,在控件系统中的窗口就是控件、布局参数与ViewRootImpl对象的一个三元组。 * * * * * **注意** :笔者并不认同将这个三元组分别存储在三个数组中的设计。如果创建一个WindowRecord类来统一保存这个三元组将可以省去很多麻烦。 * * * * * 另外,mViews、mParams以及mRoots这三个数组的容量是随着当前进程中的窗口数量的变化而变化的。因此在addView()以及随后的removeView()中都伴随着数组的新建、拷贝等操作。鉴于一个进程所添加的窗口数量不会太多,而且也不会很频繁,所以这些时间开销是可以接受的。不过笔者仍然认为相对于数组,ArrayList或CopyOnWriteArrayList是更好的选择。 - 调用ViewRootImpl.setView()函数,将控件交给ViewRootImpl进行托管。这个动作将使得ViewRootImpl向WMS添加窗口、获取Surface以及重绘等一系列的操作。这一步是控件能够作为一个窗口显示在屏幕上的根本原因! 总体来说,WindowManagerGlobal在通过父窗口调整了布局参数之后,将新建的ViewRootImpl、控件以及布局参数保存在自己的三个数组中,然后将控件交由新建的ViewRootImpl进行托管,从而完成了窗口的添加。WindowManagerGlobal管理窗口的原理如图6-3所示。 :-: ![](http://img.blog.csdn.net/20150814133520258?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center) 图 6 - 3 WindowManagerGlobal的窗口管理