1. 程式人生 > >Android6.0 WMS(七) 視窗Z軸位置

Android6.0 WMS(七) 視窗Z軸位置



通過前面幾篇文章的學習,我們知道了在Android系統中,無論是普通的Activity視窗,還是特殊的輸入法視窗和桌布視窗,它們都是被WindowManagerService服務組織在一個視窗堆疊中的,其中,Z軸位置較大的視窗排列在Z軸位置較小的視窗的上面。有了這個視窗堆疊之後,WindowManagerService服務就可以按照一定的規則計算每一個視窗的Z軸位置了,這個在之前的http://blog.csdn.net/kc58236582/article/details/53519710#t0的部落格中分析過這裡我們會再提到,而再把這個Z軸設定到SurfaceFlinger中我們會在這篇部落格中分析。

基於視窗堆疊來計算視窗的Z軸位置是比較有意思的。按照一般的理解,應該是先計算好視窗的Z軸位置,然後再按照Z軸位置的大小來將各個視窗排列在堆疊中。但是,事實上,視窗是按照其它規則排列在堆疊中。這些規則與視窗的型別、建立順序和執行狀態等有關。例如,狀態列視窗總是位於堆疊的頂端,輸入法視窗總是位於需要輸入法的視窗的上面,而桌布視窗總是位於需要顯示桌布的視窗的下面。又如,當一個Activity元件從後臺啟用到前臺時,與它所對應的視窗就會被相應地移動到視窗堆疊的上面去。

視窗的UI最終是需要通過SurfaceFlinger服務來統一渲染的,而SurfaceFlinger服務在渲染視窗的UI之前,需要計算基於各個視窗的Z軸位置來計算它們的可見區域。因此,WindowManagerService服務計算好每一個視窗的Z軸位置之後,還需要將它們設定到SurfaceFlinger服務中去,以便SurfaceFlinger服務可以正確地渲染每一個視窗的UI。

上述視窗的Z軸位置計算和設定過程如圖1所示:

圖1 視窗Z軸位置的計算和設定過程

接下來,我們就首先分析兩個需要重新計算視窗Z軸位置的情景,接著再分析視窗的Z軸位置的計算過程,最後分析WindowManagerService服務將視窗的Z軸設定到SurfaceFlinger服務中去的過程。

一、重新計算Z軸的情景

這裡主要分析兩個需要重新計算視窗Z軸位置的情景:應用程式增加一個視窗到WindowManagerService服務和應用程式請求WindowManagerService服務重新佈局一個視窗。下面我們分兩個小結分別介紹下這兩個地方。

1.1 addWindow

應用程式請求增加一個視窗到WindowManagerService服務的時候,最終會呼叫到WindowManagerService類的成員函式addWindow。接下來我們就主要分析這個函式與重新計算視窗Z軸位置相關的邏輯,如下所示:

    public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
        ......
            boolean addToken = false;
            WindowToken token = mTokenMap.get(attrs.token);
            ......

            WindowState win = new WindowState(this, session, client, token,
                    attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
            ......

            if (addToken) {
                mTokenMap.put(attrs.token, token);
            }
            win.attach();
            mWindowMap.put(client.asBinder(), win);
            ......
            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;
                }
            }

            ......

            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());
            ......
        }
        ......
    }

之前的部落格我們分析過這個函式這裡再針對視窗位置再總結下:

WindowManagerService類的成員函式addWindow會根據當前正在新增的視窗的型別來呼叫不同的成員函式來向視窗堆疊的合適位置插入一個WindowState物件,即:

        1. 如果新增的是一個輸入法視窗,那麼就呼叫成員函式addInputMethodWindowToListLocked將它放置在需要顯示輸入法的視窗的上面去;

        2. 如果新增的是一個輸入法對話方塊,那麼就先呼叫成員函式addWindowToListInOrderLocked來將它插入到視窗堆疊中,接著再呼叫成員函式adjustInputMethodDialogsLocked來將它放置在輸入法視窗的上面;

        3. 如果新增的是一個普通視窗,那麼就直接呼叫成員函式addWindowToListInOrderLocked來將它插入到視窗堆疊中;

        4. 如果新增的是一個普通視窗,並且這個視窗需要顯示桌布,那麼就先呼叫成員函式addWindowToListInOrderLocked來將它插入到視窗堆疊中,接著再呼叫成員函式adjustWallpaperWindowsLocked來將桌布視窗放置在它的下面。

        5. 如果新增的是一個桌布視窗,那麼就先呼叫成員函式addWindowToListInOrderLocked來將它插入到視窗堆疊中,接著再呼叫成員函式adjustWallpaperWindowsLocked來將它放置在需要顯示桌布的視窗的下面。

        無論如何,WindowManagerService類的成員函式addWindow最終都會呼叫成員函式assignLayersLocked來重新計算系統中所有視窗的Z軸位置,這是因為前面往視窗堆疊增加了一個新的視窗。

1.2 relayoutWindow

應用程式程序請求WindowManagerService服務重新佈局一個視窗的時候,最終會呼叫到WindowManagerService類的成員函式relayoutWindow。接下來我們就主要分析這個函式與重新計算視窗Z軸位置相關的邏輯,如下所示:

    public int relayoutWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int requestedWidth,
            int requestedHeight, int viewVisibility, int flags,
            Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
            Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Configuration outConfig,
            Surface outSurface) {
        ......

        synchronized(mWindowMap) {
            WindowState win = windowForClientLocked(session, client, false);
            if (win == null) {
                return 0;
            }
            WindowStateAnimator winAnimator = win.mWinAnimator;
            ......

            boolean imMayMove = (flagChanges & (FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE)) != 0;

            final boolean isDefaultDisplay = win.isDefaultDisplay();
            boolean focusMayChange = isDefaultDisplay && (win.mViewVisibility != viewVisibility
                    || ((flagChanges & FLAG_NOT_FOCUSABLE) != 0)
                    || (!win.mRelayoutCalled));

            boolean wallpaperMayMove = win.mViewVisibility != viewVisibility
                    && (win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;
            wallpaperMayMove |= (flagChanges & FLAG_SHOW_WALLPAPER) != 0;
            ......

            if (focusMayChange) {
                if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
                        false /*updateInputWindows*/)) {
                    imMayMove = false;
                }
            }

            // updateFocusedWindowLocked() already assigned layers so we only need to
            // reassign them at this point if the IM window state gets shuffled
            if (imMayMove && (moveInputMethodWindowsIfNeededLocked(false) || toBeDisplayed)) {
                // Little hack here -- we -should- be able to rely on the
                // function to return true if the IME has moved and needs
                // its layer recomputed.  However, if the IME was hidden
                // and isn't actually moved in the list, its layer may be
                // out of data so we make sure to recompute it.
                assignLayersLocked(win.getWindowList());
            }

            ......

        return (inTouchMode ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0)
                | (toBeDisplayed ? WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME : 0)
                | (surfaceChanged ? WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED : 0);
    }

        1. 如果系統當前獲得焦點的視窗可能發生了變化,那麼就會呼叫成員函式updateFocusedWindowLocked來重新計算系統當前應該獲得焦點的視窗。如果系統當前獲得焦點的視窗真的發生了變化,即視窗堆疊的視窗排列發生了變化,那麼在呼叫成員函式updateFocusedWindowLocked的時候,就會呼叫成員函式assignLayersLocked來重新計算系統中所有視窗的Z軸位置。

        2. 如果系統中的輸入法視窗可能需要移動,那麼就會呼叫成員函式moveInputMethodWindowsIfNeededLocked來檢查是否真的需要移動輸入法視窗。如果需要移動,那麼成員函式moveInputMethodWindowsIfNeededLocked的返回值就會等於true,這時候就說明輸入法視窗在視窗堆疊中的位置發生了變化,因此,就會呼叫assignLayersLocked函式來重新計算系統中所有視窗的Z軸位置。

        3. 如果當前正在請求調整其佈局的視窗是由不可見變化可見的,即變數toBeDisplayed的值等於true,那麼接下來也是需要重新計算系統中所有視窗的Z軸位置的。

二、計算Z軸位置

從前面第一部分的內容可以知道,一旦視窗堆疊中的視窗發生了變化,那麼WindowManagerService類的成員函式assignLayersLocked就會呼叫來計算系統中所有視窗的Z軸位置。

        視窗的Z軸位置除了與它在視窗堆疊中的位置有關之外,還與視窗的型別有關。視窗的型別在建立的時候就已經是確定了的,WindowManagerService服務在為它建立一個WindowState物件的時候,就會根據它的型別得到一個BaseLayer值,這個BaseLayer值在計算它的Z軸位置的時候會用到。還有就是assignLayersLocked函式的分析。

三、設定視窗的Z軸位置到SurfaceFlinger服務中去

 WindowManagerService服務在刷新系統的UI的時候,就會將系統中已經計算好了的視窗Z軸位置設定到SurfaceFlinger服務中去,以便SurfaceFlinger服務可以對系統中的視窗進行可見性計算以及合成和渲染等操作。

刷新系統UI是通過呼叫WindowManagerService類的成員函式performLayoutAndPlaceSurfacesLockedInner來實現的,接下來我們就分析這個成員函式與設定視窗的Z軸位置到SurfaceFlinger服務中去相關的邏輯。

        為了方便描述設定視窗的Z軸位置到SurfaceFlinger服務中去的過程,我們先列出WindowManagerService類的成員函式performLayoutAndPlaceSurfacesLockedInner的實現架構,如下所示:

    private final void performLayoutAndPlaceSurfacesLockedInner(boolean recoveringMemory) {
        ......

        SurfaceControl.openTransaction();
        try {

                ......
                do {
                    repeats++;
                    if (repeats > 6) {
                        Slog.w(TAG, "Animation repeat aborted after too many iterations");
                        displayContent.layoutNeeded = false;
                        break;
                    }

                    ......

                    // FIRST LOOP: Perform a layout, if needed.第一個迴圈
                    if (repeats < 4) {
                        performLayoutLockedInner(displayContent, repeats == 1,//計算Activity視窗大小
                                false /*updateInputWindows*/);
                    } else {
                        Slog.w(TAG, "Layout repeat skipped after too many iterations");
                    }

                    // FIRST AND ONE HALF LOOP: Make WindowManagerPolicy think
                    // it is animating.
                    displayContent.pendingLayoutChanges = 0;

                    if (isDefaultDisplay) {
                        mPolicy.beginPostLayoutPolicyLw(dw, dh);
                        for (i = windows.size() - 1; i >= 0; i--) {//有一個迴圈
                            WindowState w = windows.get(i);
                            if (w.mHasSurface) {
                                mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs, w.mAttachedWindow);
                            }
                        }
                        displayContent.pendingLayoutChanges |= mPolicy.finishPostLayoutPolicyLw();
                        if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats(
                            "after finishPostLayoutPolicyLw", displayContent.pendingLayoutChanges);
                    }
                } while (displayContent.pendingLayoutChanges != 0);

                mInnerFields.mObscured = false;
                mInnerFields.mSyswin = false;
                displayContent.resetDimming();

                // Only used if default window
                final boolean someoneLosingFocus = !mLosingFocus.isEmpty();

                final int N = windows.size();//迴圈遍歷所有的視窗
                for (i=N-1; i>=0; i--) {
                    WindowState w = windows.get(i);
                    final TaskStack stack = w.getStack();
                    if (stack == null && w.getAttrs().type != TYPE_PRIVATE_PRESENTATION) {
                        continue;
                    }

                    final boolean obscuredChanged = w.mObscured != mInnerFields.mObscured;

                    // Update effect.
                    w.mObscured = mInnerFields.mObscured;
                    if (!mInnerFields.mObscured) {
                        handleNotObscuredLocked(w, innerDw, innerDh);
                    }

                    if (stack != null && !stack.testDimmingTag()) {
                        handleFlagDimBehind(w);
                    }

                    if (isDefaultDisplay && obscuredChanged && (mWallpaperTarget == w)
                            && w.isVisibleLw()) {
                        // This is the wallpaper target and its obscured state
                        // changed... make sure the current wallaper's visibility
                        // has been updated accordingly.
                        updateWallpaperVisibilityLocked();
                    }

                    final WindowStateAnimator winAnimator = w.mWinAnimator;

                    ......

                    if (w.mHasSurface) {// 當其有Surface代表有layer資料
                        // Take care of the window being ready to display.
                        ......

                        winAnimator.setSurfaceBoundariesLocked(recoveringMemory);
                    }

                ......

                mDisplayManagerInternal.setDisplayProperties(displayId,
                        mInnerFields.mDisplayHasContent, mInnerFields.mPreferredRefreshRate,
                        mInnerFields.mPreferredModeId,
                        true /* inTraversal, must call performTraversalInTrans... below */);

                getDisplayContentLocked(displayId).stopDimmingIfNeeded();

                if (updateAllDrawn) {
                    updateAllDrawnLocked(displayContent);
                }
            }

            if (focusDisplayed) {
                mH.sendEmptyMessage(H.REPORT_LOSING_FOCUS);
            }

            // Give the display manager a chance to adjust properties
            // like display rotation if it needs to.
            mDisplayManagerInternal.performTraversalInTransactionFromWindowManager();

        } catch (RuntimeException e) {
            Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
        } finally {
            SurfaceControl.closeTransaction();
        }

        ......
    }

上面這個函式之前在分析計算Activity視窗大小的時候我們分析過了,這個函式以SurfaceControl.openTransaction開始,以SurfaceControl.closeTransaction結束。之前是分析的performLayoutLockedInner函式(計算Activity視窗大小),這裡我們遍歷所有的視窗,呼叫每個WindowState的winAnimator的setSurfaceBoundariesLocked函式來設定視窗大小,位置等到SurfaceFlinger中。

我們來看下WindowStateAnimator的setSurfaceBoundariesLocked函式,這裡只是設定寬度,高度,位置和變化矩陣到SurfaceFlinger中。

    void setSurfaceBoundariesLocked(final boolean recoveringMemory) {
        final WindowState w = mWin;

        int width;
        int height;
        if ((w.mAttrs.flags & LayoutParams.FLAG_SCALED) != 0) {
            width  = w.mRequestedWidth;
            height = w.mRequestedHeight;
        } else {
            width = w.mCompatFrame.width();
            height = w.mCompatFrame.height();
        }

        // Something is wrong and SurfaceFlinger will not like this,
        // try to revert to sane values
        if (width < 1) {
            width = 1;
        }
        if (height < 1) {
            height = 1;
        }

        float left = w.mShownFrame.left;
        float top = w.mShownFrame.top;

        ......

        width += scale * (attrs.surfaceInsets.left + attrs.surfaceInsets.right);
        height += scale * (attrs.surfaceInsets.top + attrs.surfaceInsets.bottom);
        left -= scale * attrs.surfaceInsets.left;
        top -= scale * attrs.surfaceInsets.top;

        final boolean surfaceMoved = mSurfaceX != left || mSurfaceY != top;
        if (surfaceMoved) {
            mSurfaceX = left;
            mSurfaceY = top;

            try {
                if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
                        "POS " + left + ", " + top, null);
                mSurfaceControl.setPosition(left, top);//設定位置
            } catch (RuntimeException e) {
                Slog.w(TAG, "Error positioning surface of " + w
                        + " pos=(" + left + "," + top + ")", e);
                if (!recoveringMemory) {
                    mService.reclaimSomeSurfaceMemoryLocked(this, "position", true);
                }
            }
        }

        final boolean surfaceResized = mSurfaceW != width || mSurfaceH != height;
        if (surfaceResized) {
            mSurfaceW = width;
            mSurfaceH = height;
            mSurfaceResized = true;

            try {
                if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
                        "SIZE " + width + "x" + height, null);
                mSurfaceControl.setSize(width, height);//設定大小
                mSurfaceControl.setMatrix(//變換矩陣
                        mDsDx * w.mHScale, mDtDx * w.mVScale,
                        mDsDy * w.mHScale, mDtDy * w.mVScale);
                mAnimator.setPendingLayoutChanges(w.getDisplayId(),
                        WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
                if ((w.mAttrs.flags & LayoutParams.FLAG_DIM_BEHIND) != 0) {
                    final TaskStack stack = w.getStack();
                    if (stack != null) {
                        stack.startDimmingIfNeeded(this);
                    }
                }
            } catch (RuntimeException e) {
                if (!recoveringMemory) {
                    mService.reclaimSomeSurfaceMemoryLocked(this, "size", true);
                }
            }
        }

        updateSurfaceWindowCrop(recoveringMemory);
    }

而設定視窗的Z軸位置,只有在建立SurfaceControl的時候和動畫開始的時候會設定Z軸位置,下面我們先看建立SurfaceControl的時候。

    SurfaceControl createSurfaceLocked() {
        final WindowState w = mWin;
        if (mSurfaceControl == null) {
            ......

                mSurfaceFormat = format;
                if (DEBUG_SURFACE_TRACE) {
                    mSurfaceControl = new SurfaceTrace(
                            mSession.mSurfaceSession,
                            attrs.getTitle().toString(),
                            width, height, format, flags);
                } else {
                    mSurfaceControl = new SurfaceControl(//建立SurfaceControl物件
                        mSession.mSurfaceSession,
                        attrs.getTitle().toString(),
                        width, height, format, flags);
                }

                ......
          

            // Start a new transaction and apply position & offset.
            SurfaceControl.openTransaction();
            try {
                mSurfaceX = left;
                mSurfaceY = top;

                try {
                    mSurfaceControl.setPosition(left, top);
                    mSurfaceLayer = mAnimLayer;
                    final DisplayContent displayContent = w.getDisplayContent();
                    if (displayContent != null) {
                        mSurfaceControl.setLayerStack(displayContent.getDisplay().getLayerStack());
                    }
                    mSurfaceControl.setLayer(mAnimLayer);//設定Z軸位置
                    mSurfaceControl.setAlpha(0);
                    mSurfaceShown = false;
                } catch (RuntimeException e) {
                    mService.reclaimSomeSurfaceMemoryLocked(this, "create-init", true);
                }
                mLastHidden = true;
            } finally {
                SurfaceControl.closeTransaction();
            }

        }
        return mSurfaceControl;
    }
    public void prepareSurfaceLocked(final boolean recoveringMemory) {
        final WindowState w = mWin;
        ......

        if (mIsWallpaper && !mWin.mWallpaperVisible) {
            // Wallpaper is no longer visible and there is no wp target => hide it.
            hide();
        } else if (w.mAttachedHidden || !w.isOnScreen()) {
            hide();
           ......
        } else if (mLastLayer != mAnimLayer
                || mLastAlpha != mShownAlpha
                || mLastDsDx != mDsDx
                || mLastDtDx != mDtDx
                || mLastDsDy != mDsDy
                || mLastDtDy != mDtDy
                || w.mLastHScale != w.mHScale
                || w.mLastVScale != w.mVScale
                || mLastHidden) {
            displayed = true;
            mLastAlpha = mShownAlpha;
            mLastLayer = mAnimLayer;
            mLastDsDx = mDsDx;
            mLastDtDx = mDtDx;
            mLastDsDy = mDsDy;
            mLastDtDy = mDtDy;
            w.mLastHScale = w.mHScale;
            w.mLastVScale = w.mVScale;
            if (WindowManagerService.SHOW_TRANSACTIONS) WindowManagerService.logSurface(w,
                    "alpha=" + mShownAlpha + " layer=" + mAnimLayer
                    + " matrix=[" + mDsDx + "*" + w.mHScale
                    + "," + mDtDx + "*" + w.mVScale
                    + "][" + mDsDy + "*" + w.mHScale
                    + "," + mDtDy + "*" + w.mVScale + "]", null);
            if (mSurfaceControl != null) {
                try {
                    mSurfaceAlpha = mShownAlpha;
                    mSurfaceControl.setAlpha(mShownAlpha);
                    mSurfaceLayer = mAnimLayer;
                    mSurfaceControl.setLayer(mAnimLayer);//設定Z軸位置
                    mSurfaceControl.setMatrix(
                            mDsDx * w.mHScale, mDtDx * w.mVScale,
                            mDsDy * w.mHScale, mDtDy * w.mVScale);

                    ......
                } catch (RuntimeException e) {
                    Slog.w(TAG, "Error updating surface in " + w, e);
                    if (!recoveringMemory) {
                        mService.reclaimSomeSurfaceMemoryLocked(this, "update", true);
                    }
                }
            }
        } 
        ......
    }