android WMS ——窗口顯示次序

窗口的顯示在手機上镇眷,是以屏幕左上角為原點咬最,向右為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 Z order.jpg

其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. 注釋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 由小到大排序蹭沛。

  1. 注釋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)整來分離了需要做動畫的層級集嵌,以及普通層級。保證了做動畫的窗口一定在普通窗口之上

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末御毅,一起剝皮案震驚了整個濱河市根欧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌端蛆,老刑警劉巖凤粗,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異今豆,居然都是意外死亡嫌拣,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進店門呆躲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來异逐,“玉大人,你說我怎么就攤上這事插掂』艺埃” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵辅甥,是天一觀的道長酝润。 經(jīng)常有香客問我,道長璃弄,這世上最難降的妖魔是什么要销? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮夏块,結(jié)果婚禮上疏咐,老公的妹妹穿的比我還像新娘。我一直安慰自己脐供,他們只是感情好凳鬓,可當我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著患民,像睡著了一般。 火紅的嫁衣襯著肌膚如雪垦梆。 梳的紋絲不亂的頭發(fā)上匹颤,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天仅孩,我揣著相機與錄音,去河邊找鬼印蓖。 笑死辽慕,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的赦肃。 我是一名探鬼主播溅蛉,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼他宛!你這毒婦竟也來了船侧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤厅各,失蹤者是張志新(化名)和其女友劉穎镜撩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體队塘,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡袁梗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了憔古。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片遮怜。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鸿市,靈堂內(nèi)的尸體忽然破棺而出锯梁,到底是詐尸還是另有隱情,我是刑警寧澤灸芳,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布涝桅,位于F島的核電站,受9級特大地震影響烙样,放射性物質(zhì)發(fā)生泄漏冯遂。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一谒获、第九天 我趴在偏房一處隱蔽的房頂上張望蛤肌。 院中可真熱鬧,春花似錦批狱、人聲如沸裸准。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽炒俱。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間权悟,已是汗流浹背砸王。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留峦阁,地道東北人谦铃。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像榔昔,于是被迫代替她去往敵國和親驹闰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,762評論 2 345