这篇博客我们主要分析下,窗口位置排序的一些原理。
上篇博客我们分析了WMS的addWindow函数,这里我们就窗口的次序问题继续分析。
boolean imMayMove = true; if (type == TYPE_INPUT_METHOD) {//如果窗口类是输入法窗口 win.mGivenInsetsPending = true; mInputMethodWindow = win; addInputMethodWindowToListLocked(win);//插入输入法窗口到应用窗口上层 imMayMove = false; } else if (type == TYPE_INPUT_METHOD_DIALOG) {//如果窗口是输入法对话框 mInputMethodDialogs.add(win); addWindowToListInOrderLocked(win, true);//插入到正常位置 moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true));//调整对话框位置 imMayMove = false; } else { addWindowToListInOrderLocked(win, true);//插入正常位置 if (type == TYPE_WALLPAPER) { mLastWallpaperTimeoutTime = 0; displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) { displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } else if (mWallpaperTarget != null && mWallpaperTarget.mLayer >= win.mBaseLayer) { // If there is currently a wallpaper being shown, and // the base layer of the new window is below the current // layer of the target window, then adjust the wallpaper. // This is to avoid a new window being placed between the // wallpaper and its target. displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER; } }DisplayContent类的mWindows列表按Z序保存了每个窗口,这段代码就是在根据窗口类型把窗口加入到DisplayContent合适位置。
addInputMethodWindowToListLocked方法作用就是一个输入法窗口放子啊需要显示输入法窗口的上面。
addWindowToListInOrderLocked将一个窗口插入到窗口堆栈的当前位置。
我们继续看addWindow函数,
final WindowStateAnimator winAnimator = win.mWinAnimator; winAnimator.mEnterAnimationPending = true; winAnimator.mEnteringAnimation = true; if (displayContent.isDefaultDisplay) { mPolicy.getInsetHintLw(win.mAttrs, mRotation, outContentInsets, outStableInsets,//计算窗口大小 outOutsets); } else { outContentInsets.setEmpty(); outStableInsets.setEmpty(); } if (mInTouchMode) { res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;//加入支持触屏的标志 } if (win.mAppToken == null || !win.mAppToken.clientHidden) { res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;//加入应用可用的标志 } mInputMonitor.setUpdateInputWindowsNeededLw();//设置更新输入法窗口的标志 boolean focusChanged = false; if (win.canReceiveKeys()) {//如果窗口能接受输入计算是否引起焦点变化 focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS, false /*updateInputWindows*/); if (focusChanged) { imMayMove = false; } } if (imMayMove) { moveInputMethodWindowsIfNeededLocked(false);//调整输入法的窗口位置 } assignLayersLocked(displayContent.getWindowList());//重新计算z轴的位置 // Don't do layout here, the window must call // relayout to be displayed, so we'll do it there. if (focusChanged) { mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/); } mInputMonitor.updateInputWindowsLw(false /*force*/);//更新输入法窗口的信息 if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG, "addWindow: New client " + client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5)); if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(false)) { reportNewConfig = true;//配置发生变化 } } if (reportNewConfig) { sendNewConfiguration();//发送新的配置 } Binder.restoreCallingIdentity(origId); return res; }如果窗口显示在缺省的显示设备,调用mPolicy的getInsetHintLw函数来获得除了状态条、导航条所占区域后的窗口大小。
接下来如果窗口能接受输入,调用updateFocusedWindowLocked来重新确定系统的焦点位置。如果焦点发生变化,则将imMayMove置为false。
新加入的窗口的位置在前面调用addWindowToListInOrderLocked的时候位置已经确定了,所以这里调用assignLayersLocked只是重新计算Z轴的位置。如果调用updateOrientationFromAppTokensLocked函数计算窗口的配置发生变化,调用sendNewConfiguration函数发送配置。
显示设备的水平方向,垂直方向作为X轴Y轴,我们还可以想象有一个垂直于屏幕的Z轴,Z轴的值越来越靠近屏幕。系统中所有的窗口都按次序排列在Z轴上。窗口对象WindowState的成员变量mLayer表示窗口在Z轴的值,值越小越靠近底层。
WMS作用之一就是管理各个窗口Z轴位置,确保正确显示。在所有窗口中输入法和壁纸窗口比较特殊。输入法窗口出现时,需要显示在应用窗口的前面。壁纸窗口通常在底层,但是又不是位于所有窗口的底层,而是位于当前Activity窗口的下面。
因此,当系统调整某个应用窗口的位置时,如果需要也会调整输入法和壁纸窗口,使当前Activity的窗口位于输入法窗口和壁纸窗口之间。
WindowState的成员变量mLayer的值表示窗口在Z轴的位置,这个值越小,位置越靠下。mLayer是通过计算得到,会经常变化。WindowState的另一个成员变量mBaseLayer的值是固定不变的,只和窗口类型有关。mLayer的值是根据mBaseLayer的值计算而来。
下面我们先来看看mBaseLayer的值如何而来,在WindowState的构造函数中有如下代码:
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. mBaseLayer = mPolicy.windowTypeToLayerLw(//使用依附窗口的类型 attachedWindow.mAttrs.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER + WindowManagerService.TYPE_LAYER_OFFSET; mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);//计算mSubLayer ...... } else {//非子窗口 // The multiplier here is to reserve space for multiple // windows in the same type layer. mBaseLayer = mPolicy.windowTypeToLayerLw(a.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER + WindowManagerService.TYPE_LAYER_OFFSET; mSubLayer = 0; ...... }如果窗口类型是子窗口,则使用它所依附的窗口类型来计算mBaseLayer,否则使用窗口类型来计算mBaseLayer。计算的方法是先调用mPolicy.windowTypeToLayerLw方法将窗口的类型转化成一个基数,然后再乘以TYPE_LAYER_MULTIPLIER(10000),最后加上TYPE_LAYER_OFFSET(1000),我们先来看看windowTypeToLayerLw函数是如果根据类型返回一个基数的。
public int windowTypeToLayerLw(int type) { if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {//应用窗口 return 2; } switch (type) { case TYPE_PRIVATE_PRESENTATION: return 2; case TYPE_WALLPAPER: // wallpaper is at the bottom, though the window manager may move it. return 2; case TYPE_PHONE: return 3; case TYPE_SEARCH_BAR: case TYPE_VOICE_INTERACTION_STARTING: return 4; case TYPE_VOICE_INTERACTION: // voice interaction layer is almost immediately above apps. return 5; case TYPE_INPUT_CONSUMER: return 6; case TYPE_SYSTEM_DIALOG: return 7; case TYPE_TOAST: // toasts and the plugged-in battery thing return 8; case TYPE_PRIORITY_PHONE: // SIM errors and unlock. Not sure if this really should be in a high layer. return 9; case TYPE_DREAM: // used for Dreams (screensavers with TYPE_DREAM windows) return 10; case TYPE_SYSTEM_ALERT: // like the ANR / app crashed dialogs return 11; case TYPE_INPUT_METHOD: // on-screen keyboards and other such input method user interfaces go here. return 12; case TYPE_INPUT_METHOD_DIALOG: // on-screen keyboards and other such input method user interfaces go here. return 13; case TYPE_KEYGUARD_SCRIM: // the safety window that shows behind keyguard while keyguard is starting return 14; case TYPE_STATUS_BAR_SUB_PANEL: return 15; case TYPE_STATUS_BAR: return 16; case TYPE_STATUS_BAR_PANEL: return 17; case TYPE_KEYGUARD_DIALOG: return 18; case TYPE_VOLUME_OVERLAY: // the on-screen volume indicator and controller shown when the user // changes the device volume return 19; case TYPE_SYSTEM_OVERLAY: // the on-screen volume indicator and controller shown when the user // changes the device volume return 20; case TYPE_NAVIGATION_BAR: // the navigation bar, if available, shows atop most things return 21; case TYPE_NAVIGATION_BAR_PANEL: // some panels (e.g. search) need to show on top of the navigation bar return 22; case TYPE_SYSTEM_ERROR: // system-level error dialogs return 23; case TYPE_MAGNIFICATION_OVERLAY: // used to highlight the magnified portion of a display return 24; case TYPE_DISPLAY_OVERLAY: // used to simulate secondary display devices return 25; case TYPE_DRAG: // the drag layer: input for drag-and-drop is associated with this window, // which sits above all other focusable windows return 26; case TYPE_ACCESSIBILITY_OVERLAY: // overlay put by accessibility services to intercept user interaction return 27; case TYPE_SECURE_SYSTEM_OVERLAY: return 28; case TYPE_BOOT_PROGRESS: return 29; case TYPE_POINTER: // the (mouse) pointer layer return 30; } Log.e(TAG, "Unknown window type: " + type); return 2; }这个方法很简单就是根据类型返回一个基数。
WindowState中的成员变量mSubLayer只有在窗口是子窗口的时候才有作用,它表示在窗口和父窗口之间的相对位置。代码如下
public int subWindowTypeToLayerLw(int type) { switch (type) { case TYPE_APPLICATION_PANEL: case TYPE_APPLICATION_ATTACHED_DIALOG: return APPLICATION_PANEL_SUBLAYER;//等于1 case TYPE_APPLICATION_MEDIA: return APPLICATION_MEDIA_SUBLAYER;//等于-2 case TYPE_APPLICATION_MEDIA_OVERLAY: return APPLICATION_MEDIA_OVERLAY_SUBLAYER;//等于-1 case TYPE_APPLICATION_SUB_PANEL: return APPLICATION_SUB_PANEL_SUBLAYER;//等于2 case TYPE_APPLICATION_ABOVE_SUB_PANEL: return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER;//等于3 } Log.e(TAG, "Unknown sub-window type: " + type); return 0; }理解了mBaseLayer和mSubLayer后,我们再来看看mLayer是如何计算出来的,是通过assignLayersLocked方法:
private final void assignLayersLocked(WindowList windows) { int N = windows.size(); int curBaseLayer = 0; int curLayer = 0; int i; boolean anyLayerChanged = false; 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)) {//如果窗口的mBaseLayer和前一个相同、或者是输入法和壁纸窗口 curLayer += WINDOW_LAYER_MULTIPLIER; w.mLayer = curLayer; } else { curBaseLayer = curLayer = w.mBaseLayer; w.mLayer = curLayer; } if (w.mLayer != oldLayer) {//层级发生改变 layerChanged = true; anyLayerChanged = true; } final AppWindowToken wtoken = w.mAppToken; oldLayer = winAnimator.mAnimLayer;//后面都是调整mAnimLayerd的值 if (w.mTargetAppToken != null) { winAnimator.mAnimLayer = w.mLayer + w.mTargetAppToken.mAppAnimator.animLayerAdjustment; } else if (wtoken != null) { winAnimator.mAnimLayer = w.mLayer + wtoken.mAppAnimator.animLayerAdjustment; } else { winAnimator.mAnimLayer = w.mLayer; } if (w.mIsImWindow) { winAnimator.mAnimLayer += mInputMethodAnimLayerAdjustment; } else if (w.mIsWallpaper) { winAnimator.mAnimLayer += mWallpaperAnimLayerAdjustment; } if (winAnimator.mAnimLayer != oldLayer) { layerChanged = true; anyLayerChanged = true; } final TaskStack stack = w.getStack(); if (layerChanged && stack != null && stack.isDimming(winAnimator)) { // Force an animation pass just to update the mDimLayer layer. scheduleAnimationLocked(); } } if (mAccessibilityController != null && anyLayerChanged && windows.get(windows.size() - 1).getDisplayId() == Display.DEFAULT_DISPLAY) { mAccessibilityController.onWindowLayersChangedLocked(); } }在调用assignLayersLocked函数之前,WindowList中的窗口其实已经排好序了,前面调用的函数addWindowToListInOrderLocked就是插入窗口到合适的位置,assignLayersLocked函数并不会改变窗口的位置,只是根据窗口的位置计算mLayer的值。
调整方法是从最底层的窗口开始,具有相同的mBaseLayer的值作为一组,每组窗口的mLayer的值从mBaseLayer的值开始,依次加上WINDOW_LAYER_MULTIPLIER(等于5),这样做的目的是在同层的窗口中每隔一个窗口就留下4个空位,方便下次插入新窗口。
这个方法还对输入法和壁纸窗口做了特殊处理。这两类窗口和它插入位置前面的窗口处于一个层级,而不是根据他们的mBaseLayer值计算。(就是前面说的当输入法和壁纸出现是在当前Activity的窗口之间的)。
在addWindow函数中我们会调用addAppWindowToListLocked来确定窗口的位置,现在我们来看下这个函数。
private void addWindowToListInOrderLocked(final WindowState win, boolean addToToken) { if (win.mAttachedWindow == null) {//非子窗口 final WindowToken token = win.mToken; int tokenWindowsPos = 0; if (token.appWindowToken != null) {//应用窗口的顶层窗口 tokenWindowsPos = addAppWindowToListLocked(win); } else { addFreeWindowToListLocked(win);//系统窗口 } if (addToToken) { if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); token.windows.add(tokenWindowsPos, win); } } else { addAttachedWindowToListLocked(win, addToToken);//添加子窗口 } if (win.mAppToken != null && addToToken) { win.mAppToken.allAppWindows.add(win); } }上面这个函数根据窗口的类型,应用顶层窗口,系统窗口,子窗口。
我们现在分别对这三类窗口的处理方法进行解析,先来看插入Activity顶层窗口的addAppWindowToListLocked
addAppWindowToListLocked方法先判断系统中是否存在和待插入的窗口是否有相同的Token,如果有代表它不是Activity的第一个窗口,因此再判断这个窗口的类型是不是TYPE_BASE_APPLICATION,如果是这类窗口需要放在所有和它相同Token的窗口下面,否则在判断这个应用的启动窗口是否位于最前面(说明正在启动),如果是放在启动窗口的下面。如果不是下面两种情况,则寻找同一应用中位置最高的窗口,然后插在它上面,这表示加入的窗口将覆盖在前面的窗口之上。
下面是部分代码,
private int addAppWindowToListLocked(final WindowState win) { final IWindow client = win.mClient; final WindowToken token = win.mToken; final DisplayContent displayContent = win.getDisplayContent(); if (displayContent == null) { // It doesn't matter this display is going away. return 0; } final WindowList windows = win.getWindowList(); final int N = windows.size(); WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent); int tokenWindowsPos = 0; int windowListPos = tokenWindowList.size(); if (!tokenWindowList.isEmpty()) {//如果有,说明它不是第一个窗口 // 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) {//放在和它相同Token的窗口下面 // Base windows go behind everything else. WindowState lowestWindow = tokenWindowList.get(0);//第一个0,代表最底层的window placeWindowBefore(lowestWindow, win);//放在这个window前面 tokenWindowsPos = indexOfWinInWindowList(lowestWindow, token.windows); } else { AppWindowToken atoken = win.mAppToken; WindowState lastWindow = tokenWindowList.get(windowListPos - 1); if (atoken != null && lastWindow == atoken.startingWindow) { placeWindowBefore(lastWindow, win); tokenWindowsPos = indexOfWinInWindowList(lastWindow, token.windows); } else { int newIdx = findIdxBasedOnAppTokens(win);//寻找同一token位置最前面的window //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. 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; }我们再来看几个函数
placeWindowBefore函数就是插入到windows这个位置前
private void placeWindowBefore(WindowState pos, WindowState window) { final WindowList windows = pos.getWindowList(); int i = windows.indexOf(pos); if (i < 0) { Slog.w(TAG, "placeWindowBefore: Unable to find " + pos + " in " + windows); i = 0; } windows.add(i, window); mWindowsChanged = true; }findIdxBasedOnAppTokens函数就是寻找相同token的最前面的window,所以要注意遍历循环的时候是从window的size最大的时候反过来遍历的。
private int findIdxBasedOnAppTokens(WindowState win) { WindowList windows = win.getWindowList(); for(int j = windows.size() - 1; j >= 0; j--) { WindowState wentry = windows.get(j); if(wentry.mAppToken == win.mAppToken) { return j; } } return -1; }继续看这个函数,如果系统中不存在和窗口具有相同token的窗口(说明Activity刚启动,第一个窗口还没有创建完成),这时就会遍历系统所有的task以及task中包含的AppWindowToken,找到窗口的位置,再在task中排在本窗口前面的窗口中,找出离自己最近的,并且APPWindowToken的窗口列表不为NULL的窗口,插入到它的最后一个子窗口后面。如果前面的窗口的列表也都为NULL,则寻找排在本窗口后面的第一个包含有窗口对象的APPWindowToken,把本窗口插在前面。
WindowState pos = null; 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); if (t == token) { --tokenNdx; if (tokenNdx < 0) { --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. 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. 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 = bottom; } } } placeWindowBefore(pos, win); return tokenWindowsPos; } // Continue looking down until we find the first // token that has windows on this display. for ( ; taskNdx >= 0; --taskNdx) { AppTokenList tokens = tasks.get(taskNdx).mAppTokens; for ( ; tokenNdx >= 0; --tokenNdx) { 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; } } 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 = top; } } } placeWindowAfter(pos, win); return tokenWindowsPos; } ......如果前面窗口的APPWindowToken的窗口列表也为空,则重新遍历整个窗口,然后根据mBaseLayer的值来确定窗口的位置。
final int myLayer = win.mBaseLayer; int i; for (i = N - 1; i >= 0; --i) { WindowState w = windows.get(i); if (w.mBaseLayer <= myLayer) { break; } } if (DEBUG_FOCUS_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "Based on layer: Adding window " + win + " at " + (i + 1) + " of " + N); windows.add(i + 1, win); mWindowsChanged = true; return tokenWindowsPos;addAttachedWindowToListLocked方法,如果mSubLayer大于0的子窗口,按mSubLayer的值大小插入到有相同WindowToken的子窗口的合适位置中,如果mSubLayer相同,插入已插入窗口的下层位置,如果mSubLayer小于0,如果还存在mSubLayer小于0,并且大于等于该窗口的mSubLayer的值的子窗口,则插入到该子窗口之下,否则插入到子窗口所依附的窗口下面。
private void addAttachedWindowToListLocked(final WindowState win, boolean addToToken) { final WindowToken token = win.mToken; final DisplayContent displayContent = win.getDisplayContent(); if (displayContent == null) { return; } final WindowState attached = win.mAttachedWindow; WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent); // Figure out this window's ordering relative to the window // it is attached to. final int NA = tokenWindowList.size(); final int sublayer = win.mSubLayer; int largestSublayer = Integer.MIN_VALUE; WindowState windowWithLargestSublayer = null; int i; for (i = 0; i < NA; i++) { WindowState w = tokenWindowList.get(i); final int wSublayer = w.mSubLayer; if (wSublayer >= largestSublayer) { largestSublayer = wSublayer; windowWithLargestSublayer = w; } if (sublayer < 0) { // For negative sublayers, we go below all windows // in the same sublayer. if (wSublayer >= sublayer) { if (addToToken) { if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); token.windows.add(i, win); } placeWindowBefore(wSublayer >= 0 ? attached : w, win); break; } } else { // For positive sublayers, we go above all windows // in the same sublayer. if (wSublayer > sublayer) { if (addToToken) { if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); token.windows.add(i, win); } placeWindowBefore(w, win); break; } } } if (i >= NA) { if (addToToken) { if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); token.windows.add(win); } if (sublayer < 0) { placeWindowBefore(attached, win); } else { placeWindowAfter(largestSublayer >= 0 ? windowWithLargestSublayer : attached, win); } } }addFreeWindowToListLocked方法简单,只是遍历同一显示设备上的Windows,比较mBaseLayer值的大小,插入合适位置。
private void addFreeWindowToListLocked(final WindowState win) { final WindowList windows = win.getWindowList(); // Figure out where window should go, based on layer. final int myLayer = win.mBaseLayer; int i; for (i = windows.size() - 1; i >= 0; i--) { if (windows.get(i).mBaseLayer <= myLayer) { break; } } i++; if (DEBUG_FOCUS_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "Free window: Adding window " + win + " at " + i + " of " + windows.size()); windows.add(i, win); mWindowsChanged = true; }
这篇博客我们分析了window插入到什么位置,以及mLayer的计算。但是具体里面有很多变量,stack task windows等,不是很熟悉,下篇博客我们主要分析这。