窗口的顯示在手機上镇眷,是以屏幕左上角為原點咬最,向右為X軸,向下為Y軸欠动,垂直與屏幕表面并指向屏幕外為Z軸永乌。多個窗口依照順序排列在Z軸上顯示,稱為Z order具伍。
WindowState中的序列
在WMS.addWindow的時候翅雏,會為每個窗口創(chuàng)建WindowState用來記錄window 的狀態(tài)。
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow,
PowerManagerWrapper powerManagerWrapper) {
super(service);
......
//為窗口分配z order
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.getWindowLayerLw(parentWindow)
* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
mIsChildWindow = true;
if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + this + " to " + parentWindow);
//將子窗口windowState添加到父窗口的windowState中
parentWindow.addChild(this, sWindowSubLayerComparator);
mLayoutAttached = mAttrs.type !=
WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
mIsImWindow = parentWindow.mAttrs.type == TYPE_INPUT_METHOD
|| parentWindow.mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
mIsWallpaper = parentWindow.mAttrs.type == TYPE_WALLPAPER;
} else {
// The multiplier here is to reserve space for multiple
// windows in the same type layer.
mBaseLayer = mPolicy.getWindowLayerLw(this)
* TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
mSubLayer = 0;
mIsChildWindow = false;
mLayoutAttached = false;
mIsImWindow = mAttrs.type == TYPE_INPUT_METHOD
|| mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
mIsWallpaper = mAttrs.type == TYPE_WALLPAPER;
}
mIsFloatingLayer = mIsImWindow || mIsWallpaper;
}
其中mBaseLayer與mSubLayer人芽,用于描述窗口顯示次序望几。
- mBaseLayer為主序,用于描述窗口及其所有子窗口在所有窗口的顯示位置萤厅。mBaseLayer值越大橄抹,顯示越靠前
- mSubLayer為子序,描述了一個子窗口在其兄弟窗口中的顯示位置祈坠,mSubLayer值越大害碾,其顯示在同mBaseLayer中的位置越靠前。
主序和子序的關(guān)系赦拘,可以通俗的理解成:主序?qū)c劇院里的樓層慌随,而子序?qū)谠摌菍拥牡趲着拧?br> 對于父窗口,主序取決于其類型躺同,子序則為0阁猜;而對于子窗口,主序與父窗口一致蹋艺,子序取決于子窗口類型剃袍。
DisplayContent中的次序
在DisplayContent構(gòu)造函數(shù)中有按順序依次添加四個子Container:
mBelowAppWindowsContainers、mTaskStackContainers捎谨、
mAboveAppWindowsContainers民效、mImeWindowsContainers。
它們的顯示順序也是依次從里到外顯示涛救,在addWindowToken函數(shù)中根據(jù)windowType添加到對應的Container中畏邢,并且根據(jù)type值進行排序:
/*DisplayContent.java*/
private final NonAppWindowContainers mBelowAppWindowsContainers =
new NonAppWindowContainers("mBelowAppWindowsContainers", mService);
private class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> {
/**
* Compares two child window tokens returns -1 if the first is lesser than the second in
* terms of z-order and 1 otherwise.
*/
private final Comparator<WindowToken> mWindowComparator = (token1, token2) ->
// Tokens with higher base layer are z-ordered on-top.
mService.mPolicy.getWindowLayerFromTypeLw(token1.windowType,
token1.mOwnerCanManageAppTokens)
< mService.mPolicy.getWindowLayerFromTypeLw(token2.windowType,
token2.mOwnerCanManageAppTokens) ? -1 : 1;
void addChild(WindowToken token) {
addChild(token, mWindowComparator);
}
......
}
其DisplayContent中的顯示層級如圖所示
窗口顯示次序的第二次調(diào)整
在DisplayContent中按照顯示順序?qū)蛹墑澐趾螅呀?jīng)形成基本的Z Order次序检吆,有些特殊情況需要第二次調(diào)整舒萎,如執(zhí)行窗口動畫。
public int addWindow(Session session, IWindow client, int seq,
LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
......
synchronized(mWindowMap) {
......
//完成驗證添加窗口令牌的有效性
//為新窗口創(chuàng)建WindowState,其初始化主序和子序
final WindowState win = new WindowState(this, session, client, token, parentWindow,
appOp[0], seq, attrs, viewVisibility, session.mUid,
session.mCanAddInternalSystemWindow);
......
win.attach();
mWindowMap.put(client.asBinder(), win);
......
win.mToken.addWindow(win); // 注釋1
if (type == TYPE_INPUT_METHOD) {
win.mGivenInsetsPending = true;
setInputMethodWindowLocked(win);
imMayMove = false;
} else if (type == TYPE_INPUT_METHOD_DIALOG) {
//注釋2
displayContent.computeImeTarget(true /* updateImeTarget */);
imMayMove = false;
} else {
if (type == TYPE_WALLPAPER) {
displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
} else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
} else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) {
// 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;
}
}
// If the window is being added to a stack that's currently adjusted for IME,
// make sure to apply the same adjust to this new window.
win.applyAdjustForImeIfNeeded();
if (type == TYPE_DOCK_DIVIDER) {
mRoot.getDisplayContent(displayId).getDockedDividerController().setWindow(win);
}
final WindowStateAnimator winAnimator = win.mWinAnimator;
winAnimator.mEnterAnimationPending = true;
winAnimator.mEnteringAnimation = true;
// Check if we need to prepare a transition for replacing window first.
if (atoken != null && atoken.isVisible()
&& !prepareWindowReplacementTransition(atoken)) {
// If not, check if need to set up a dummy transition during display freeze
// so that the unfreeze wait for the apps to draw. This might be needed if
// the app is relaunching.
prepareNoneTransitionForRelaunching(atoken);
}
final DisplayFrames displayFrames = displayContent.mDisplayFrames;
// TODO: Not sure if onDisplayInfoUpdated() call is needed.
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
displayFrames.onDisplayInfoUpdated(displayInfo,
displayContent.calculateDisplayCutoutForRotation(displayInfo.rotation));
final Rect taskBounds;
if (atoken != null && atoken.getTask() != null) {
taskBounds = mTmpRect;
atoken.getTask().getBounds(mTmpRect);
} else {
taskBounds = null;
}
if (mPolicy.getLayoutHintLw(win.mAttrs, taskBounds, displayFrames, outFrame,
outContentInsets, outStableInsets, outOutsets, outDisplayCutout)) {
res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
}
if (mInTouchMode) {
res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;
}
if (win.mAppToken == null || !win.mAppToken.isClientHidden()) {
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) {
//注釋2
displayContent.computeImeTarget(true /* updateImeTarget */);
}
// Don't do layout here, the window must call
// relayout to be displayed, so we'll do it there.
win.getParent().assignChildLayers(); //注釋3
if (focusChanged) {
mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
}
mInputMonitor.updateInputWindowsLw(false /*force*/);
if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addWindow: New client "
+ client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5));
if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(displayId)) {
reportNewConfig = true;
}
}
if (reportNewConfig) {
sendNewConfiguration(displayId);
}
Binder.restoreCallingIdentity(origId);
return res;
}
- 注釋1處:該處通過WindowToken.addWin將根據(jù)mBaseLayer大小排序添加到WindowToken中的mChildren列表中
/* WindowToken.java*/
void addWindow(final WindowState win) {
if (win.isChildWindow()) {
// Child windows are added to their parent windows.
return;
}
if (!mChildren.contains(win)) {
addChild(win, mWindowComparator); //繼承與WindowContainer
mService.mWindowsChanged = true;
}
}
private final Comparator<WindowState> mWindowComparator =
(WindowState newWindow, WindowState existingWindow) -> {
final WindowToken token = WindowToken.this;
return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;
};
protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
WindowState existingWindow) {
// New window is considered greater if it has a higher or equal base layer.
return newWindow.mBaseLayer >= existingWindow.mBaseLayer;
}
在WindowToken中的mChildren列表會按照mBaseLayer 由小到大排序蹭沛。
- 注釋2處:displayContent.computeImeTarget(true /* updateImeTarget */)
該函數(shù)主要用于調(diào)整輸入法彈窗的顯示:
WindowState computeImeTarget(boolean updateImeTarget) {
if (mService.mInputMethodWindow == null) {
//如果當前WMS并沒有輸入法
if (updateImeTarget) {
//設置輸入法動畫
setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim);
}
return null;
}
final WindowState curTarget = mService.mInputMethodTarget;
if (!canUpdateImeTarget()) {
return curTarget;
}
mUpdateImeTarget = updateImeTarget;
WindowState target = getWindow(mComputeImeTargetPredicate);
//輸入法彈窗是啟動窗口類型
if (target != null && target.mAttrs.type == TYPE_APPLICATION_STARTING) {
final AppWindowToken token = target.mAppToken;
if (token != null) {
final WindowState betterTarget = token.getImeTargetBelowWindow(target);//自動找到下面那一層窗口
if (betterTarget != null) {
target = betterTarget;
}
}
}
// Now, a special case -- if the last target's window is in the process of exiting, and the
// new target is home, keep on the last target to avoid flicker. Home is a special case
// since its above other stacks in the ordering list, but layed out below the others.
//當前的輸入法彈窗不為空臂寝,同時當前的進程還存在章鲤,并且下一個要啟動的窗口是Home。則直接返回當前進程的輸入法彈窗
if (curTarget != null && curTarget.isDisplayedLw() && curTarget.isClosing()
&& (target == null || target.isActivityTypeHome())) {
if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, "New target is home while current target is "
+ "closing, not changing");
return curTarget;
}
//輸入法彈窗為空
if (target == null) {
if (updateImeTarget) {
if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget
+ " to null." + (SHOW_STACK_CRAWLS ? " Callers="
+ Debug.getCallers(4) : ""));
setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim);
}
return null;
}
//根據(jù)updateImeTarget來確定是否需要輸入法動畫
if (updateImeTarget) {
AppWindowToken token = curTarget == null ? null : curTarget.mAppToken;
if (token != null) {
// Now some fun for dealing with window animations that modify the Z order. We need
// to look at all windows below the current target that are in this app, finding the
// highest visible one in layering.
WindowState highestTarget = null;
if (token.isSelfAnimating()) {
highestTarget = token.getHighestAnimLayerWindow(curTarget);
}
if (highestTarget != null) {
final AppTransition appTransition = mService.mAppTransition;
if (DEBUG_INPUT_METHOD) Slog.v(TAG_WM, appTransition + " " + highestTarget
+ " animating=" + highestTarget.mWinAnimator.isAnimationSet()
+ " layer=" + highestTarget.mWinAnimator.mAnimLayer
+ " new layer=" + target.mWinAnimator.mAnimLayer);
if (appTransition.isTransitionSet()) {
// If we are currently setting up for an animation, hold everything until we
// can find out what will happen.
setInputMethodTarget(highestTarget, true);
return highestTarget;
} else if (highestTarget.mWinAnimator.isAnimationSet() &&
highestTarget.mWinAnimator.mAnimLayer > target.mWinAnimator.mAnimLayer) {
// If the window we are currently targeting is involved with an animation,
// and it is on top of the next target we will be over, then hold off on
// moving until that is done.
setInputMethodTarget(highestTarget, true);
return highestTarget;
}
}
}
if (DEBUG_INPUT_METHOD) Slog.w(TAG_WM, "Moving IM target from " + curTarget + " to "
+ target + (SHOW_STACK_CRAWLS ? " Callers=" + Debug.getCallers(4) : ""));
setInputMethodTarget(target, false);
}
return target;
}
其中setInputMethodTarget主要記錄當前mService.mInputMethodTarget以及mInputMethodTargetWaitingAnim狀態(tài)咆贬,并調(diào)用assignWindowLayers
/** Updates the layer assignment of windows on this display. */
void assignWindowLayers(boolean setLayoutNeeded) {
assignChildLayers(getPendingTransaction());
if (setLayoutNeeded) {
setLayoutNeeded();
}
// We accumlate the layer changes in-to "getPendingTransaction()" but we defer
// the application of this transaction until the animation pass triggers
// prepareSurfaces. This allows us to synchronize Z-ordering changes with
// the hiding and showing of surfaces.
scheduleAnimation();
}
調(diào)用到WindowContainer中函數(shù)
void assignChildLayers(Transaction t) {
int layer = 0;
// We use two passes as a way to promote children which
// need Z-boosting to the end of the list.
for (int j = 0; j < mChildren.size(); ++j) {
final WindowContainer wc = mChildren.get(j);
wc.assignChildLayers(t);
if (!wc.needsZBoost()) {
wc.assignLayer(t, layer++);
}
}
for (int j = 0; j < mChildren.size(); ++j) {
final WindowContainer wc = mChildren.get(j);
if (wc.needsZBoost()) {
wc.assignLayer(t, layer++);
}
}
}
void assignChildLayers() {
assignChildLayers(getPendingTransaction());
scheduleAnimation();
}
assignChildLayers通過 兩個循環(huán)败徊,先將不需要動畫的層級調(diào)整,再調(diào)整需要動畫的層級在不需要動畫的層級之上素征。通過調(diào)整來分離了需要做動畫的層級集嵌,以及普通層級。保證了做動畫的窗口一定在普通窗口之上