Android WindowManagerService机制分析:窗口的显示层级
2017-10-28 12:47
429 查看
WindowManagerService(以下简称WMS)是Android Framework中一个重要的系统服务,用来管理系统中窗口(Window)的行为。Window是一个抽象的概念,它是一个矩形区域,用来绘制UI界面,响应用户的输入事件。Android系统的界面,从Framework层的角度来看,就是由一个一个窗口组合而成的。
在WMS中一个重要的功能就是对当前窗口的显示顺序进行排序。但是窗口只是一个抽象的概念,WMS中所做的事情实际上是根据各种条件计算窗口的显示层级,然后将这个表示层级的数值传给SurfaceFlinger,SurfaceFlinger根据这个层级信息去进行渲染。本文主要对Android 7.0版本中WMS关于计算窗口的显示层级的相关机制进行简单地分析。
一. 窗口的主序和次序
在WMS中,WindowState.java类用于描述窗口的状态。每一个窗口都对应着一个WindowState实例。其中有两个成员变量用于描述窗口的层级: mBaseLayer:窗口的主序,根据当前窗口类型定义了一个层级。mBaseLayer越大,窗口及其子窗口的显示顺序越靠前。 mSubLayer:窗口的子序,如果当前窗口是子窗口,决定了该窗口相对于其兄弟窗口(同一父窗口下的其它子窗口)以及父窗口的顺序。mSubLayer越大,该窗口相对于其兄弟窗口就越靠前。 这两个变量在WindowState中是final的,也就是说在构造函数中被初始化了以后,就不能再改变了。上面两个值都是根据窗口类型计算出来的:(frameworks/base/services/core/java/com/android/server/wm/WindowState.java)
后面我们会分析同类型的窗口的显示层级的计算规则,其核心逻辑就是根据主序不停地添加偏移量(5),所以需要预留10000的空间,防止某一窗口的层级超越了比它主序高的类型的窗口(但理论上也只能保证2000个同类型窗口层级正确,虽然不太可能会有2000个同类型窗口同时存在的情况)。 窗口的子序就没有这么复杂了,WMS策略类会根据窗口类型返回一个-2 ~ 3的数值。父窗口子序永远为0,小于0的子窗口在父窗口下面,大于0的在父窗口上面。常见的子序如下:
二. 确定新窗口的位置
每当新窗口被添加到WMS时,WMS都会将新创建的WindowState对象添加到其所属的DisplayContent中的WindowState列表中。这个列表记录了在当前屏幕下(DisplayContent.java在WMS中抽象地描述了屏幕的相关信息,默认的就是我们所使用的手机显示屏)WindowState的显示顺序,这个顺序很大程度的上影响了每一个窗口的最终显示层级,但并不是全部决定条件。下面我们简单看一下新窗口被添加后,在DisplayContent中的位置是如何确定的。首先我们对整体流程有一个抽象地认识:
下面根据源代码对上述流程进行分析: 首先是添加窗口的流程,每当有新窗口被添加时,会调用WMS的addWindow方法:(frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java)
三. 应用窗口的重排序 应用窗口的重排序是一个被频繁触发的机制,因为系统中应用窗口的位置会频繁的变化,这个机制(rebuildAppWindowListLocked被调用)确保了应用窗口顺序的正确性。(frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java)
1. 移除所有应用窗口
2. 把即将退出的窗口先插入回去
3. 把非退出的窗口按照Z order(应用窗口就是根据activity的顺序)插入到即将退出的窗口的上面
真正插入的方法是reAddAppWindowsLocked:(frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java)
四. 确定窗口的显示层级 DisplayContent中的WindowList的顺序并不是窗口最终的显示顺序,真正的显示层级还需要根据一些其它因素进行计算。为每一个窗口赋予显示层级的方法为WindowLayersController.java中的assignLayersLocked方法。在addWindow时,窗口的顺序在DisplayContent中确定了以后就会通过此方法为新窗口赋予显示层级。这个方法也会被频繁地调用,上面我们也提到,最终的显示层级除了跟窗口的顺序有关,还跟一些其它因素有关,比如窗口动画的参数。所以其显示层级并不是确定后一直不变的,动画行为也会通过assignLayersLocked更新窗口显示层级。(frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java)
过渡动画可以通过Animation.java中的setZAdjustment方法调整窗口在过渡动画期间的层级:(frameworks/base/services/core/java/com/android/server/wm/AppWindowAnimator.java)
每当某个窗口的过渡动画执行到最后一帧时,用于动画过程中调整层级的偏移量(animLayerAdjustment)会被置0,对应的mAnimLayer也会重新计算。所以动画层级的偏移量仅仅会在过渡动画(普通的窗口动画没有这个动画层级偏移量)过程中影响窗口层级。
在WMS中一个重要的功能就是对当前窗口的显示顺序进行排序。但是窗口只是一个抽象的概念,WMS中所做的事情实际上是根据各种条件计算窗口的显示层级,然后将这个表示层级的数值传给SurfaceFlinger,SurfaceFlinger根据这个层级信息去进行渲染。本文主要对Android 7.0版本中WMS关于计算窗口的显示层级的相关机制进行简单地分析。
一. 窗口的主序和次序
在WMS中,WindowState.java类用于描述窗口的状态。每一个窗口都对应着一个WindowState实例。其中有两个成员变量用于描述窗口的层级: mBaseLayer:窗口的主序,根据当前窗口类型定义了一个层级。mBaseLayer越大,窗口及其子窗口的显示顺序越靠前。 mSubLayer:窗口的子序,如果当前窗口是子窗口,决定了该窗口相对于其兄弟窗口(同一父窗口下的其它子窗口)以及父窗口的顺序。mSubLayer越大,该窗口相对于其兄弟窗口就越靠前。 这两个变量在WindowState中是final的,也就是说在构造函数中被初始化了以后,就不能再改变了。上面两个值都是根据窗口类型计算出来的:(frameworks/base/services/core/java/com/android/server/wm/WindowState.java)
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a, int viewVisibility, final DisplayContent displayContent) { ...... // 首先根据窗口类型判断这是不是一个子窗口 if ((mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW)) { // The multiplier here is to reserve space for multiple // windows in the same type layer. // 计算主序和子序,注意当前窗口是一个子窗口,其主序应该与其父窗口一致,所以传入的是其父窗口(attachWindow)的窗口类型 mBaseLayer = mPolicy.windowTypeToLayerLw( attachedWindow.mAttrs.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER + WindowManagerService.TYPE_LAYER_OFFSET; mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type); ...... } else { // The multiplier here is to reserve space for multiple // windows in the same type layer. // 当前不是一个子窗口,非子窗口的mSubLayer都为0 mBaseLayer = mPolicy.windowTypeToLayerLw(a.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER + WindowManagerService.TYPE_LAYER_OFFSET; mSubLayer = 0; ...... } ...... }窗口的主序和次序都是通过WMS的策略类(PhoneWindowManager.java)获取的,其中的逻辑很简单,就是根据窗口的type返回一个2 ~ 31的数。根据上面的初始化过程我们可以看到,mBaseLayer的值会根据策略类返回的值再乘以10000并加上1000计算而成。根据源码中的注释:The multiplier here is to reserve space for multiple windows in the same type layer. 我们可以推测,这样计算的目的是为了区分同一类型的不同窗口的层级。也就是说,主序仅仅是按照窗口类型划分了一个显示顺序,但是同一类型的窗口不一定只有一个(计算同类型窗口的显示层级还需要其它的条件),不同类型窗口的主序相差10000是为了给这些同类型窗口的显示层级预留的空间。常见的窗口类型的主序如下:
窗口类型 | 主序 |
type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW(应用窗口) | 21000 |
TYPE_WALLPAPER(墙纸) | 21000 |
TYPE_PHONE(电话) | 31000 |
TYPE_TOAST(toast) | 81000 |
TYPE_INPUT_METHOD(输入法) | 101000 |
TYPE_STATUS_BAR(状态栏) | 161000 |
TYPE_KEYGUARD_DIALOG(锁屏) | 181000 |
子窗口类型 | 子序 |
TYPE_APPLICATION_MEDIA(SurfaceView) | -2 |
TYPE_APPLICATION_PANEL | 1 |
TYPE_APPLICATION_SUB_PANEL | 2 |
每当新窗口被添加到WMS时,WMS都会将新创建的WindowState对象添加到其所属的DisplayContent中的WindowState列表中。这个列表记录了在当前屏幕下(DisplayContent.java在WMS中抽象地描述了屏幕的相关信息,默认的就是我们所使用的手机显示屏)WindowState的显示顺序,这个顺序很大程度的上影响了每一个窗口的最终显示层级,但并不是全部决定条件。下面我们简单看一下新窗口被添加后,在DisplayContent中的位置是如何确定的。首先我们对整体流程有一个抽象地认识:
下面根据源代码对上述流程进行分析: 首先是添加窗口的流程,每当有新窗口被添加时,会调用WMS的addWindow方法:(frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java)
public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) { ...... // 经过一些条件的检查,窗口的WindowState对象被创建,根据attrs.type决定其mBaseLayer以及mSubLayer的值 WindowState win = new WindowState(this, session, client, token, attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent); ...... // 这里会对输入法窗口做特殊的处理,这里略去,通用的情况是调用此方法对新窗口位置进行插入 addWindowToListInOrderLocked(win, true); }addWindowToListInOrderLocked方法对新窗口进行插入的操作,其流程如上面流程图所示。下面对照代码分析一下具体流程:(frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java)
private void addWindowToListInOrderLocked(final WindowState win, boolean addToToken) { if (DEBUG_FOCUS) Slog.d(TAG_WM, "addWindowToListInOrderLocked: win=" + win + " Callers=" + Debug.getCallers(4)); // 如果mAttachedWindow是空,则表示该窗口不是一个子窗口 if (win.mAttachedWindow == null) { final WindowToken token = win.mToken; // 注意这个变量,它表示该WindowState对象在所属同一WindowToken的所有WindowState中的位置 int tokenWindowsPos = 0; if (token.appWindowToken != null) { // appWindowToken不为空,意味着这是一个activity窗口 tokenWindowsPos = addAppWindowToListLocked(win); } else { addFreeWindowToListLocked(win); } if (addToToken) { if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + token); // token.windows就是描述所属该token下的所有WindowState对象 // 比如一个activity弹出了一个AlertDialog窗口,这两个窗口的AppWindowToken是一个 token.windows.add(tokenWindowsPos, win); } } else { // 这是一个子窗口 addAttachedWindowToListLocked(win, addToToken); } final AppWindowToken appToken = win.mAppToken; if (appToken != null) { if (addToToken) { appToken.addWindow(win); } } } // 首先是addAppWindowToListLocked方法,该方法对activity窗口进行了插入 private int addAppWindowToListLocked(final WindowState win) { final DisplayContent displayContent = win.getDisplayContent(); if (displayContent == null) { // It doesn't matter this display is going away. return 0; } final IWindow client = win.mClient; final WindowToken token = win.mToken; final WindowList windows = displayContent.getWindowList(); // 获取同一DisplayContent中,所属该WindowToken的所有WindowState WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent); int tokenWindowsPos = 0; // 如果存在相同的WindowToken的WindowState,则通过addAppWindowToTokenListLocked方法对其进行插入 // 比如这种情景就会走到这个case:从一个activity中弹出一个popup window // 此时这个新的弹窗和activity的窗口属于同一WindowToken if (!tokenWindowList.isEmpty()) { // 这个方法下面会分析 return addAppWindowToTokenListLocked(win, token, windows, tokenWindowList); } // No windows from this token on this display if (localLOGV) Slog.v(TAG_WM, "Figuring out where to add app window " + client.asBinder() + " (token=" + token + ")"); // Figure out where the window should go, based on the // order of applications. // 这个是要插入位置(原来)的WindowState WindowState pos = null; // 遍历该DisplayContent中所有的Task // 注意,流程走到这里意味这这个窗口是一个activity窗口,所以在此之前, // AMS会通过addAppToken方法把其AppWindowToken添加到对应的task中去 // 此时正常的状态为:Task中有对应的AppWindowToken, // 但是DisplayContent中的WindowList里面没有该WindowState // 另外说明一点,通过addAppToken添加进来的AppWindowToken会记录在mTokenMaps中, // 在addWindow的过程中,该AppWindowToken会通过mTokenMaps获取, // 并赋值给WindowState的mToken成员(AppWindowToken是WindowToken的子类) final ArrayList<Task> tasks = displayContent.getTasks(); int taskNdx; int tokenNdx = -1; for (taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { AppTokenList tokens = tasks.get(taskNdx).mAppTokens; for (tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) { final AppWindowToken t = tokens.get(tokenNdx); // 自顶向下遍历,正常情况下第一个就应该是目标的AppWindowToken, // 此时会直接跳出循环,pos指向空 if (t == token) { --tokenNdx; if (tokenNdx < 0) { // 如果tokenNdx小于0,意味着已经到该task的最后一个了, // 再插入的位置应该是下一个task的顶部 --taskNdx; if (taskNdx >= 0) { tokenNdx = tasks.get(taskNdx).mAppTokens.size() - 1; } } break; } // We haven't reached the token yet; if this token // is not going to the bottom and has windows on this display, we can // use it as an anchor for when we do reach the token. // 如果第一个不是,那么pos就指向当前遍历到AppWindowToken所属的最下面一个 // 的WindowState(当前activity中最后一个window) // 这样做的目的是这样,activity的WindowState的位置实际上要和activity的位置一致 // 如果下一轮迭代找到了对应的AppWindowToken,那么它应该被插入在它上面的AppWindowToken // 的最后一个窗口的下面pos就指向了这个位置当前的WindowState, // 后面新的WindowState就要被插入到这个位置 tokenWindowList = getTokenWindowsOnDisplay(t, displayContent); if (!t.sendingToBottom && tokenWindowList.size() > 0) { pos = tokenWindowList.get(0); } } if (tokenNdx >= 0) { // early exit break; } } // We now know the index into the apps. If we found // an app window above, that gives us the position; else // we need to look some more. // 不为空的话则表示要插入的WindowState的AppWindowToken不在顶部 // 需要插入到它上面一个AppWindowToken所属的最下面的一个WindowState的后面 if (pos != null) { // Move behind any windows attached to this one. WindowToken atoken = mTokenMap.get(pos.mClient.asBinder()); if (atoken != null) { tokenWindowList = getTokenWindowsOnDisplay(atoken, displayContent); final int NC = tokenWindowList.size(); if (NC > 0) { WindowState bottom = tokenWindowList.get(0); if (bottom.mSubLayer < 0) { // 确保pos指向的是上面AppWindowToken的最后一个WindowState pos = bottom; } } } // 插入到DisplayContent所记录顺序的WindowList中 placeWindowBefore(pos, win); // 返回该WindowState在其WindowToken中所有WindowState的位置 return tokenWindowsPos; } // Continue looking down until we find the first // token that has windows on this display. // 流程到这里,意味这要插入的WindowState所对应的activity是在top task的顶部(大部分情况是这种) for ( ; taskNdx >= 0; --taskNdx) { AppTokenList tokens = tasks.get(taskNdx).mAppTokens; for ( ; tokenNdx >= 0; --tokenNdx) { // 这里就是要看,到底要插入到谁的上面 // 理论上是插入在当前task的顶部WindowState的上面,但是有可能此时的activity是首次启动, // task里面没有WindowState // 这种情况下的状态为:Task里仅仅有准备添加的这个WindowState的AppWindowToken, // 但是该AppWindowToken没有属于它的WindowState //(注意新Window还未添加进去,此方法的返回值是新WindowState在AppWindowToken中的位置, // 然后才会添加进去),所以此时pos仍然为空 // 但是当Task中有其他的AppWindowToken(app内部启动新的activity,并且不加NEW_TASK的flag)时, // pos指向的是该Task内最上面的WindowState final AppWindowToken t = tokens.get(tokenNdx); tokenWindowList = getTokenWindowsOnDisplay(t, displayContent); final int NW = tokenWindowList.size(); if (NW > 0) { pos = tokenWindowList.get(NW-1); break; } } if (tokenNdx >= 0) { // found break; } } // 如果pos不为空,则指向的是该Task最上面的WindowState,新的WindowState即将插入到它上面 if (pos != null) { // Move in front of any windows attached to this // one. WindowToken atoken = mTokenMap.get(pos.mClient.asBinder()); if (atoken != null) { final int NC = atoken.windows.size(); if (NC > 0) { WindowState top = atoken.windows.get(NC-1); if (top.mSubLayer >= 0) { // 这里同上,确保pos是顶部的WindowState pos = top; } } } placeWindowAfter(pos, win); return tokenWindowsPos; } // Just search for the start of this layer. // 走到这里,意味着该WindowState是新启动的一个activity的第一个窗口(新task的第一个WindowState) // 由于没有可参考的activity窗口,所以需要通过mBaseLayer去插入 // 很明显,它将被插入到所有应用窗口的顶部 final int myLayer = win.mBaseLayer; int i; for (i = windows.size() - 1; i >= 0; --i) { WindowState w = windows.get(i); // Dock divider shares the base layer with application windows, but we want to always // keep it above the application windows. The sharing of the base layer is intended // for window animations, which need to be above the dock divider for the duration // of the animation. // 分屏的DOCK_DIVIDER(分屏模式下中间那个黑杆,mBaseLayer也是21000,但是要保证它在所有应用窗口的上面) if (w.mBaseLayer <= myLayer && w.mAttrs.type != TYPE_DOCK_DIVIDER) { break; } } if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Based on layer: Adding window " + win + " at " + (i + 1) + " of " + windows.size()); windows.add(i + 1, win); mWindowsChanged = true; return tokenWindowsPos; } // 如果新添加的WindowState所对应的AppWindowToken已经有其它的WindowState存在了, // 则需要通过addAppWindowToTokenListLocked方法插入 // 这里可以这样理解,要启动的窗口所属的activity之前已经存在了, // activity的所有窗口(即我们上面一直说的所属同一AppWindowToken的WindowState) // 顺序必然是连续的,所以此时新启动的WindowState必定要插入在这其中的某一个位置 // 注意,流程到这里是,已经判断过了该WindowState不是一个子窗口,所以不能用mSubLayer判断位置 private int addAppWindowToTokenListLocked(WindowState win, WindowToken token, WindowList windows, WindowList tokenWindowList) { int tokenWindowsPos; // If this application has existing windows, we // simply place the new window on top of them... but // keep the starting window on top. if (win.mAttrs.type == TYPE_BASE_APPLICATION) { // 如果新窗口是TYPE_BASE_APPLICATION类型,则需要插入在该AppWindowToken所有窗口的最底部 // Base windows go behind everything else. WindowState lowestWindow = tokenWindowList.get(0); placeWindowBefore(lowestWindow, win); tokenWindowsPos = indexOfWinInWindowList(lowestWindow, token.windows); } else { AppWindowToken atoken = win.mAppToken; final int windowListPos = tokenWindowList.size(); WindowState lastWindow = tokenWindowList.get(windowListPos - 1); if (atoken != null && lastWindow == atoken.startingWindow) { // 如果存在starting window,则插入到starting window的下面 placeWindowBefore(lastWindow, win); tokenWindowsPos = indexOfWinInWindowList(lastWindow, token.windows); } else { // 否则,插入到该AppWindowToken所有窗口的最顶部 int newIdx = findIdxBasedOnAppTokens(win); //there is a window above this one associated with the same //apptoken note that the window could be a floating window //that was created later or a window at the top of the list of //windows associated with this token. if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "not Base app: Adding window " + win + " at " + (newIdx + 1) + " of " + windows.size()); windows.add(newIdx + 1, win); if (newIdx < 0) { // No window from token found on win's display. tokenWindowsPos = 0; } else { tokenWindowsPos = indexOfWinInWindowList( windows.get(newIdx), token.windows) + 1; } mWindowsChanged = true; } } return tokenWindowsPos; }上面分析了应用窗口(token.appWindowToken != null)的情况,如果一个窗口不属于一个activity,则需要通过addFreeWindowToListLocked方法对其进行插入。这个插入的依据就是窗口的mBaseLayer,除了墙纸和输入法窗口有一些特殊处理外,通用的插入原则就死插入到第一个mBaseLayer小于等于它的窗口的上面。最后是对子窗口进行插入的操作,通过addAttachedWindowToListLocked插入,主要是根据mSubLayer进行排序:父窗口的mBaseLayer为0,其它为兄弟窗口,大于0的在父窗口上面,越大越靠上;小于0的在父窗口下面,越小越靠下。依据这个逻辑进行插入,具体的代码逻辑比较简单,不在此处赘述。 简单地总结一下上面分析的排序规则: 1. 非应用窗口一句mBaseLayer插入,越高越靠前(墙纸和输入法有特殊处理) 2. 应用窗口依据activity的位置插入,应该被插入在其activity所在task的顶部(通常情况)或者该activity上面的activity的最后一个窗口的下面(分屏模式会遇到这种情况) 3. 子窗口依据mSubLayer插入最终插入的地方有两个:DisplayContent所持有的记录该屏幕下所有窗口顺序的WindowList,以及新窗口的WindowToken所记录的所有属于它的WindowState列表(WindowList)。
三. 应用窗口的重排序 应用窗口的重排序是一个被频繁触发的机制,因为系统中应用窗口的位置会频繁的变化,这个机制(rebuildAppWindowListLocked被调用)确保了应用窗口顺序的正确性。(frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java)
private void rebuildAppWindowListLocked(final DisplayContent displayContent) { final WindowList windows = displayContent.getWindowList(); int NW = windows.size(); int i; // 记录了自底向上第一个应用窗口下面的第一个窗口的位置 int lastBelow = -1; int numRemoved = 0; if (mRebuildTmp.length < NW) { mRebuildTmp = new WindowState[NW+10]; } // First remove all existing app windows. // 遍历所有的WindowState,把应用窗口全部移除 i=0; while (i < NW) { WindowState w = windows.get(i); if (w.mAppToken != null) { WindowState win = windows.remove(i); win.mRebuilding = true; mRebuildTmp[numRemoved] = win; mWindowsChanged = true; if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Rebuild removing window: " + win); NW--; numRemoved++; continue; } else if (lastBelow == i-1) { // 根据PhoneWindowManager里面windowTypeToLayerLw方法我们可以知道, // 应用窗口的主序是最低的 // 仅有墙纸窗口有可能在应用窗口下面 if (w.mAttrs.type == TYPE_WALLPAPER) { lastBelow = i; } } i++; } // Keep whatever windows were below the app windows still below, // by skipping them. lastBelow++; i = lastBelow; // First add all of the exiting app tokens... these are no longer // in the main app list, but still have windows shown. We put them // in the back because now that the animation is over we no longer // will care about them. // 首先把准备退出的窗口重新添加进来,这个mExitingAppTokens在removeAppToken时被记录 // 也就是说,即将退出的窗口都往下面放 final ArrayList<TaskStack> stacks = displayContent.getStacks(); final int numStacks = stacks.size(); for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { AppTokenList exitingAppTokens = stacks.get(stackNdx).mExitingAppTokens; int NT = exitingAppTokens.size(); for (int j = 0; j < NT; j++) { i = reAddAppWindowsLocked(displayContent, i, exitingAppTokens.get(j)); } } // And add in the still active app tokens in Z order. // 接着把非退出的窗口按照Z order重新添加回去,实际上有就是根据activity的顺序添加 for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { final ArrayList<Task> tasks = stacks.get(stackNdx).getTasks(); final int numTasks = tasks.size(); for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) { final AppTokenList tokens = tasks.get(taskNdx).mAppTokens; final int numTokens = tokens.size(); for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) { final AppWindowToken wtoken = tokens.get(tokenNdx); if (wtoken.mIsExiting && !wtoken.waitingForReplacement()) { continue; } i = reAddAppWindowsLocked(displayContent, i, wtoken); } } } // 输出一些日志信息 ...... }上述方法抽象出来主要的行为是:
1. 移除所有应用窗口
2. 把即将退出的窗口先插入回去
3. 把非退出的窗口按照Z order(应用窗口就是根据activity的顺序)插入到即将退出的窗口的上面
真正插入的方法是reAddAppWindowsLocked:(frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java)
private final int reAddAppWindowsLocked(final DisplayContent displayContent, int index, WindowToken token) { final int NW = token.windows.size(); // 由于每次传入的都是一个WindowToken,所以这个方法需要把该WindowToken下的所有窗口都插入回去 for (int i=0; i<NW; i++) { // 这里获取到属于该WindowToken的所有WindowState // 注意,子窗口是不在里面的,所以在接下来的过程中还会通过reAddWindowLocked方法 // 对每一个窗口的子窗口进行插入的操作 final WindowState win = token.windows.get(i); final DisplayContent winDisplayContent = win.getDisplayContent(); if (winDisplayContent == displayContent || winDisplayContent == null) { win.mDisplayContent = displayContent; index = reAddWindowLocked(index, win); } } return index; } // 该方法将传入的WindowState以及它所有子窗口都插入回去,插入的依据仍然是mSubLayer // 传进来的参数win代表着父窗口 private final int reAddWindowLocked(int index, WindowState win) { final WindowList windows = win.getWindowList(); // Adding child windows relies on mChildWindows being ordered by mSubLayer. final int NCW = win.mChildWindows.size(); boolean winAdded = false; for (int j=0; j<NCW; j++) { WindowState cwin = win.mChildWindows.get(j); // 自底向上遍历,如果第一个窗口的mSubLayer就大于等于0,那么就意味着父窗口就在最下面 if (!winAdded && cwin.mSubLayer >= 0) { // 此时cwin就是父窗口,也就是传进来的参数win if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding child window at " + index + ": " + cwin); win.mRebuilding = false; // 把父窗口插入回去 windows.add(index, win); index++; winAdded = true; } // 按原来的顺序继续插入回去 if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding window at " + index + ": " + cwin); cwin.mRebuilding = false; windows.add(index, cwin); index++; } // winAdded表示父窗口是否被插入回去,如果为false, // 则要把父窗口插入到其它子窗口的上面(理论上不应该出现这种情况) if (!winAdded) { if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG_WM, "Re-adding window at " + index + ": " + win); win.mRebuilding = false; windows.add(index, win); index++; } mWindowsChanged = true; return index; }在调整一些特殊窗口的位置时也会触发此机制,因为某些特殊窗口有可能夹在两个应用窗口之间(输入法窗口),一旦影响到了应用窗口的顺序,就需要通过此机制更新应用窗口的顺序。
四. 确定窗口的显示层级 DisplayContent中的WindowList的顺序并不是窗口最终的显示顺序,真正的显示层级还需要根据一些其它因素进行计算。为每一个窗口赋予显示层级的方法为WindowLayersController.java中的assignLayersLocked方法。在addWindow时,窗口的顺序在DisplayContent中确定了以后就会通过此方法为新窗口赋予显示层级。这个方法也会被频繁地调用,上面我们也提到,最终的显示层级除了跟窗口的顺序有关,还跟一些其它因素有关,比如窗口动画的参数。所以其显示层级并不是确定后一直不变的,动画行为也会通过assignLayersLocked更新窗口显示层级。(frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java)
// 这里传入的就是DisplayContent的WindowList final void assignLayersLocked(WindowList windows) { if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based on windows=" + windows, new RuntimeException("here").fillInStackTrace()); clear(); int curBaseLayer = 0; int curLayer = 0; boolean anyLayerChanged = false; for (int i = 0, windowCount = windows.size(); i < windowCount; i++) { final WindowState w = windows.get(i); boolean layerChanged = false; int oldLayer = w.mLayer; // 自底向上遍历,第一个窗口curLayer和mBaseLayer相等 // 每往上迭代一次,遍历到的窗口的curLayer就添加数值为5的偏移量 if (w.mBaseLayer == curBaseLayer || w.mIsImWindow || (i > 0 && w.mIsWallpaper)) { curLayer += WINDOW_LAYER_MULTIPLIER; } else { curBaseLayer = curLayer = w.mBaseLayer; } // 更新该窗口的mAnimLayer,也就是动画层级(可以理解为动画显示时,该窗口的层级) assignAnimLayer(w, curLayer); // TODO: Preserved old behavior of code here but not sure comparing // oldLayer to mAnimLayer and mLayer makes sense...though the // worst case would be unintentionalp layer reassignment. if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) { layerChanged = true; anyLayerChanged = true; } // 记录当前应用窗口的最高显示层级 if (w.mAppToken != null) { mHighestApplicationLayer = Math.max(mHighestApplicationLayer, w.mWinAnimator.mAnimLayer); } // 记录一些特殊窗口,比如分屏相关的窗口,它们的显示层级有特殊要求 collectSpecialWindows(w); // 和Dim Layer有关,窗口显示层级发生变化时,需要对Dim Layer进行调度 if (layerChanged) { w.scheduleAnimationIfDimming(); } } // 遍历结束,调整特殊窗口的层级 adjustSpecialWindows(); //TODO (multidisplay): Magnification is supported only for the default display. if (mService.mAccessibilityController != null && anyLayerChanged && windows.get(windows.size() - 1).getDisplayId() == Display.DEFAULT_DISPLAY) { mService.mAccessibilityController.onWindowLayersChangedLocked(); } if (DEBUG_LAYERS) logDebugLayers(windows); } // 这是一个关键的方法,用于更新窗口动画层级 private void assignAnimLayer(WindowState w, int layer) { // 传入的layer就是刚才计算的curLayer,也就是基于mBaseLayer以偏移量5递增的数值 // 如果不受动画参数影响(w.getAnimLayerAdjustment()), // 并且它不是特殊窗口(getSpecialWindowAnimLayerAdjustment(w),输入法和墙纸) // 那么窗口的显示层级和窗口的顺序是一致的(相邻窗口的mLayer差5) w.mLayer = layer; w.mWinAnimator.mAnimLayer = w.mLayer + w.getAnimLayerAdjustment() + getSpecialWindowAnimLayerAdjustment(w); if (w.mAppToken != null && w.mAppToken.mAppAnimator.thumbnailForceAboveLayer > 0 && w.mWinAnimator.mAnimLayer > w.mAppToken.mAppAnimator.thumbnailForceAboveLayer) { // 这个是用于过渡动画的,因为某些特殊类型的过渡动画会在动画过程中创建一块新的Surface用于配合动画 // 但是这个Surface没有在WMS中以Window的形式体现, // 其显示属性都要有AppWindowAnimator自己控制(直接对Surface操作) // 这里就是更新了这种Surface的层级属性 w.mAppToken.mAppAnimator.thumbnailForceAboveLayer = w.mWinAnimator.mAnimLayer; } }上面提到了两种特殊情况,会对窗口的显示层级有调整。一种是特殊窗口(输入法和墙纸,这里不做介绍),另一种是动画:
过渡动画可以通过Animation.java中的setZAdjustment方法调整窗口在过渡动画期间的层级:(frameworks/base/services/core/java/com/android/server/wm/AppWindowAnimator.java)
public void setAnimation(Animation anim, int width, int height, boolean skipFirstFrame, int stackClip) { ...... // 这里会获得通过Animaion.java的setZAdjustment设置的zorder类型 int zorder = anim.getZAdjustment(); int adj = 0; // ZORDER_TOP则会+1000,ZORDER_BOTTOM则会-1000 if (zorder == Animation.ZORDER_TOP) { adj = TYPE_LAYER_OFFSET; } else if (zorder == Animation.ZORDER_BOTTOM) { adj = -TYPE_LAYER_OFFSET; } if (animLayerAdjustment != adj) { // 上面getAnimLayerAdjustment获得的就是这个值 animLayerAdjustment = adj; // 这里会通知一些特殊的地方,动画层级改变:比如墙纸 updateLayers(); } ...... }接下来需要调整一些特殊窗口的层级:(frameworks/base/services/core/java/com/android/server/wm/WindowLayerController.java)
private void adjustSpecialWindows() { // 这个初始化的值表示了当前最高的应用窗口层级+5 int layer = mHighestApplicationLayer + WINDOW_LAYER_MULTIPLIER; // For pinned and docked stack window, we want to make them above other windows also when // these windows are animating. // Docked Stack(分屏模式下的上半屏窗口)中的窗口需要现实在所有应用窗口之上,这里层级被重新调整了 while (!mDockedWindows.isEmpty()) { // assignAndIncreaseLayerIfNeeded方法会通过assignAnimLayer更新该window的mLayer以及mAnimLayer // 更新完后,layer会再次+5 layer = assignAndIncreaseLayerIfNeeded(mDockedWindows.remove(), layer); } // 接下来更新分屏杆的窗口,它需要在所有应用窗口之上,所以需要先更新Docked Stack里面的应用窗口 // 在此方法中,越靠后更新的窗口,layer越高 layer = assignAndIncreaseLayerIfNeeded(mDockDivider, layer); // 调整输入法与分屏杆的层级关系 if (mDockDivider != null && mDockDivider.isVisibleLw()) { while (!mInputMethodWindows.isEmpty()) { final WindowState w = mInputMethodWindows.remove(); // Only ever move IME windows up, else we brake IME for windows above the divider. if (layer > w.mLayer) { layer = assignAndIncreaseLayerIfNeeded(w, layer); } } } // We know that we will be animating a relaunching window in the near future, which will // receive a z-order increase. We want the replaced window to immediately receive the same // treatment, e.g. to be above the dock divider. while (!mReplacingWindows.isEmpty()) { layer = assignAndIncreaseLayerIfNeeded(mReplacingWindows.remove(), layer); } // 这是一种特殊模式的窗口,类似于画中画模式 while (!mPinnedWindows.isEmpty()) { layer = assignAndIncreaseLayerIfNeeded(mPinnedWindows.remove(), layer); } }至此,WMS中层级信息已经计算完毕,接下来会向SurfaceFlinger更新。上层传递窗口的显示层级的数值实际上是mAnimLayer,也就是动画层级。
每当某个窗口的过渡动画执行到最后一帧时,用于动画过程中调整层级的偏移量(animLayerAdjustment)会被置0,对应的mAnimLayer也会重新计算。所以动画层级的偏移量仅仅会在过渡动画(普通的窗口动画没有这个动画层级偏移量)过程中影响窗口层级。
相关文章推荐
- Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析
- Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析
- Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析
- Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析
- Android窗口管理服务WindowManagerService显示窗口动画的原理分析
- Android窗口管理服务WindowManagerService显示窗口动画的原理分析
- Android窗口管理服务WindowManagerService显示窗口动画的原理分析
- Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析
- Android窗口管理服务WindowManagerService显示窗口动画的原理分析
- Android窗口管理服务WindowManagerService显示窗口动画的原理分析
- Android窗口管理服务WindowManagerService显示窗口动画的原理分析
- Android窗口管理服务WindowManagerService显示Activity组件的启动窗口(Starting Window)的过程分析
- Android窗口管理服务WindowManagerService对输入法窗口(Input Method Window)的管理分析
- Android窗口管理服务WindowManagerService对输入法窗口(Input Method Window)的管理分析
- Android窗口管理服务WindowManagerService对输入法窗口(Input Method Window)的管理分析
- Android窗口管理服务WindowManagerService对壁纸窗口(Wallpaper Window)的管理分析
- Android窗口管理服务WindowManagerService对输入法窗口(Input Method Window)的管理分析
- Android窗口管理服务WindowManagerService切换Activity窗口(App Transition)的过程分析
- Android窗口管理服务WindowManagerService对壁纸窗口(Wallpaper Window)的管理分析
- Android窗口管理服务WindowManagerService对窗口的组织方式分析