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所示:

Android6.0 WMS(七) 窗口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函数的分析。

这两部分都在Android6.0 WMS(三) WMS窗口次序 这篇博客中分析过了,这里我们就不分析了。


三、设置窗口的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;
    }

还有动画开始的时候也会设置Z轴位置,这个在Android6.0 WMS(六) WMS动画管理 中分析过。

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




你可能感兴趣的:(android,WMS)