💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
回到WMS的addWindow()函数中,继续往下看: **WindowManagerService.java::WindowManagerService.addWindow()** ``` public int addWindow(Session session, IWindowclient, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, RectoutContentInsets, InputChannel outInputChannel) { ...... synchronized(mWindowMap){ //在前面的代码中,WMS验证了添加窗口的令牌的有效性,并为新窗口创建了新的WindowState对象 // 新的WindowState对象在其构造函数中根据窗口类型初始化了其主序mBaseLayer和mSubLayer ...... // 接下来,将新的WindowState按照显示次序插入到当前DisplayContent的mWindows列表中 // 为了代码结构的清晰,不考虑输入法窗口和壁纸窗口的处理 if (type== TYPE_INPUT_METHOD) { ...... }else if (type == TYPE_INPUT_METHOD_DIALOG) { }else { // 将新的WindowState按显示次序插入到当前DisplayContent的mWindows列表中 addWindowToListInOrderLocked(win,true); if(type == TYPE_WALLPAPER) { ...... } } ...... // 根据窗口的排序结果,为DisplayContent的所有窗口分配最终的显示次序 assignLayersLocked(displayContent.getWindowList()); ...... } ...... returnres; } ``` 这里有两个关键点: - addWindowToListInOrderLocked()将新建的WindowState按照一定的顺序插入到当前DisplayContent的mWindows列表中。在分析WMS的重要成员时提到过这个列表。它严格地按照显示顺序存储了所有窗口的WindowState。 - assignLayersLocked()将根据mWindows的存储顺序对所有的WindowState的主序和子序进行调整。 接下来分别分析一下这两个函数。 #### 1.addWindowToListInOrderLocked()分析 addWindowToListInOrderLocked()的代码很长,不过其排序原则却比较清晰。这里直接给出其处理原则,感兴趣的读者可根据这些原则自行深究相关代码。 **注意** :再次强调一下,mWindows列表是按照主序与子序的升序进行排序的,所以显示靠前的窗口放在列表靠后的位置,而显示靠前的窗口,则位于列表的前面。也就是说,列表顺序与显示顺序是相反的。这点在阅读代码时要牢记,以免混淆。 在后面的叙述中,非特别强调,所谓的前后都是指显示顺序而不是在列表的存储顺序。 **子窗口的排序规则**:子窗口的位置计算是相对父窗口的,并根据其子序进行排序。由于父窗口的子序为0,所以子序为负数的窗口会放置在父窗口的后面,而子序为正数的窗口会放置在父窗口的前面。如果新窗口与现有窗口子序相等,则正数子序的新窗口位于现有窗口的前面,负数子序的新窗口位于现有窗口的后面。 非子窗口的排序则是依据主序进行的,但是其规则较为复杂,分为应用窗口和非应用窗口两种情况。之所以要区别处理应用窗口是因为所有的应用窗口的初始主序都是21000,并且应用窗口的位置应该与它所属的应用的其他窗口放在一起。例如应用A显示于应用B的后方,当应用A因为某个动作打开一个新的窗口时,新窗口应该位于应用A其他窗口的前面,但是不得覆盖应用B的窗口。只依据主序进行排序是无法实现这个管理逻辑的,还需要依赖Activity的顺序。在WindowToken一节的讲解中,曾经简单分析了mAppTokens列表的性质,它所保存的AppWindowToken的顺序与AMS中ActivityRecord的顺序时刻保持一致。因此,AppWindowToken在mAppTokens的顺序就是Activity的顺序。 **非应用窗口的排序规则**:依照主序进行排序,主序高者排在前面,当现有窗口的主序与新窗口相同时,新窗口位于现有窗口的前面。 **应用窗口的排序规则**:如上所述,同一个应用的窗口的显示位置必须相邻。如果当前应用已有窗口在显示(当前应用的窗口存储在其WindowState.appWindowToken.windows中),新窗口将插入到其所属应用其他窗口的前面,但是保证STARTING\_WINDOW永远位于最前方,BASE\_APPLICATION永远位于最后方。如果新窗口是当前应用的第一个窗口,则参照其他应用的窗口顺序,将新窗口插入到位于前面的最后一个应用的最后一个窗口的后方,或者位于后面的第一个应用的最前一个窗口的前方。如果当前没有其他应用的窗口可以参照,则直接根据主序将新窗口插入到列表中。 窗口排序的总结如下: - 子窗口依据子序相对于其父窗口进行排序。相同子序的窗体,正子序则越新越靠前,负子序则越新越靠后。 - 应用窗口参照本应用其他窗口或相邻应用的窗口进行排序。如果没有任何窗口可以参照,则根据主序进行排序。 - 非应用窗口根据主序进行排序。 经过addWindowToListInOrderLocked()函数的处理之后,当前DisplayContent的窗口列表被插入了一个新的窗口。然后等待assignLayersLocked()的进一步处理。 #### 2.assignLayersLocked分析 assignLayersLocked()函数将根据每个窗口的主序以及它们在窗口列表中的位置重新计算最终的显示次序mLayer。 **WindowManagerService.java::WindowManagerService.assignLayersLocked()** ``` privatefinal void assignLayersLocked(WindowList windows) { int N = windows.size(); int curBaseLayer = 0; // curLayer表示当前分配到的Layer序号 int curLayer = 0; int i; // 遍历列表中的所有的窗口,逐个分配显示次序 for (i=0; i<N; i++) { final WindowState w = windows.get(i); final WindowStateAnimator winAnimator =w.mWinAnimator; boolean layerChanged = false; int oldLayer = w.mLayer; if (w.mBaseLayer == curBaseLayer ||w.mIsImWindow || (i > 0 &&w.mIsWallpaper)) { // 为具有相同主序的窗口在curLayer上增加一个偏移量,并将curLayer作为最终的显示次序 curLayer +=WINDOW_LAYER_MULTIPLIER; w.mLayer = curLayer; } else { // 此窗口拥有不同的主序,直接将主序作为其显示次序并更新curLayer curBaseLayer = curLayer =w.mBaseLayer; w.mLayer = curLayer; } // 如果现实次序发生了变化则进行标记 if (w.mLayer != oldLayer) { layerChanged = true; anyLayerChanged = true; } ...... } ...... // 向当前DisplayContent的监听者通知显示次序的更新 if (anyLayerChanged) { scheduleNotifyWindowLayersChangedIfNeededLocked( getDefaultDisplayContentLocked()); } } ``` assignLayersLocked()的工作原理比较绕,简单来说,如果某个窗口在整个列表中拥有唯一的主序,则该主序就是其最终的显示次序。如果若干个窗口拥有相同的主序(注意经过addWindowToListInOrderLocked()函数的处理后,拥有相同主序的窗口都是相邻的),则第i个相同主序的窗口的显示次序为在主序的基础上增加i \* WINDOW\_LAYER\_MULTIPLIER的偏移。 经过assignLayersLocked()之后,一个拥有9个窗口的系统的现实次序的信息如表4-3所示。 :-: 表4- 3 窗口最终的显示次序信息 | | 窗口1 | 窗口2 | 窗口3 | 窗口4 | 窗口5 | 窗口6 | 窗口7 | 窗口8 | 窗口9 | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | 主序mBaseLayer | 11000 | 11000 | 21000 | 21000 | 21000 | 21000 | 71000 | 71000 | 101000 | | 子序mSubLayer | 0 | 0 | 0 | -1 | 0 | 0 | 0 | 0 | 0 | | 显示次序mLayer | 11000 |11005 | 21000 | 21005 | 21010 | 21015 | 71000 | 71005 | 101000 | 在确定了最终的显示次序mLayer后,又计算了WindowStateAnimator另一个属性:mAnimLayer。如下所示: **WindowManagerService.java::assignLayersLocked()** ``` finalWindowStateAnimator winAnimator = w.mWinAnimator; ...... if (w.mTargetAppToken != null) { // 输入目标为Activity的输入法窗口,其mTargetAppToken是其输入目标所属的AppToken winAnimator.mAnimLayer = w.mLayer + w.mTargetAppToken.mAppAnimator.animLayerAdjustment; } elseif (w.mAppToken != null) { // 属于一个Activity的窗口 winAnimator.mAnimLayer = w.mLayer + w.mAppToken.mAppAnimator.animLayerAdjustment; } else { winAnimator.mAnimLayer = w.mLayer; } ...... ``` 对于绝大多数窗口而言,其对应的WindowStateAnimator的mAnimLayer就是mLayer。而当窗口附属为一个Activity时,mAnimLayer会加入一个来自AppWindowAnimator的矫正:animLayerAdjustment。 WindowStateAnimator和AppWindowAnimator是动画系统中的两员大将,它们负责渲染窗口动画以及最终的Surface显示次序的修改。回顾一下4.1.2中的WMS的组成结构图,WindowState属于窗口管理体系的类,因此其所保存的mLayer的意义偏向于窗口管理。WindowStateAnimator/AppWindowAnimator则是动画体系的类,其mAnimLayer的意义偏向于动画,而且由于动画系统维护着窗口的Surface,因此**mAnimLayer是Surface的实际显示次序**。 在没有动画的情况下,mAnimLayer与mLayer是相等的,而当窗口附属为一个Activity时,则会根据AppTokenAnimator的需要适当地增加一个矫正值。这个矫正值来自AppTokenAnimator所使用的Animation。当Animation要求动画对象的ZOrder必须位于其他对象之上时(Animation.getZAdjustment()的返回值为Animation.ZORDER\_TOP),这个矫正是一个正数WindowManagerService.TYPE\_LAYER\_OFFSET(1000),这个矫正值很大,于是窗口在动画过程中会显示在其他同主序的窗口之上。相反,如果要求ZOrder必须位于其他对象之下时,矫正为-WindowManagerService.TYPE\_LAYER\_OFFSET(-1000),于是窗口会显示在其他同主序的窗口之下。在动画完结后,mAnimLayer会被重新赋值为WindowState.mLayer,使得窗口回到其应有的位置。 动画系统的工作原理将在4.5节详细探讨。 **注意** 矫正值为常数1000,也就出现一个隐藏的bug:当同主序的窗口的数量大于200时,APPLICATION窗口的mLayer值可能超过22000。此时,在对于mLayer值为21000的窗口应用矫正后,仍然无法保证动画窗口位于同主序的窗口之上。不过超过200个应用窗口的情况非常少见,而且仅在动画过程中才会出现bug,所以google貌似也懒得解决这个问题。