WMS相关学习-relayoutWindow(6)

前言

前面说到了computeFrameLw,用来真正计算每个窗口的大小的函数,并为WindowState的相关frame属性赋值,我们接着上周的内容来继续学习relayoutWindow的相关逻辑

WindowState#computeFrameLw

@Override
public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overscanFrame,
        Rect contentFrame, Rect visibleFrame, Rect decorFrame, Rect stableFrame,
        Rect outsetFrame, WmDisplayCutout displayCutout,
        boolean parentFrameWasClippedByDisplayCutout) {
    if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) {
        // This window is being replaced and either already got information that it's being
        // removed or we are still waiting for some information. Because of this we don't
        // want to apply any more changes to it, so it remains in this state until new window
        // appears.
        return;
    }
    mHaveFrame = true; //mHaveFrame表示这个窗口计算过了
    mParentFrameWasClippedByDisplayCutout = parentFrameWasClippedByDisplayCutout;

    final Task task = getTask();
    final boolean inFullscreenContainer = inFullscreenContainer();
    final boolean windowsAreFloating = task != null && task.isFloating();
    final DisplayContent dc = getDisplayContent();

    // If the task has temp inset bounds set, we have to make sure all its windows uses
    // the temp inset frame. Otherwise different display frames get applied to the main
    // window and the child window, making them misaligned.
    // Otherwise we need to clear the inset frame, to avoid using a stale frame after leaving
    // multi window mode.
    if (task != null && isInMultiWindowMode()) {
        task.getTempInsetBounds(mInsetFrame);
    } else {
        mInsetFrame.setEmpty();
    }

    // Denotes the actual frame used to calculate the insets and to perform the layout. When
    // resizing in docked mode, we'd like to freeze the layout, so we also need to freeze the
    // insets temporarily. By the notion of a task having a different layout frame, we can
    // achieve that while still moving the task around.
    final Rect layoutContainingFrame;
    final Rect layoutDisplayFrame;

    // The offset from the layout containing frame to the actual containing frame.
    final int layoutXDiff;
    final int layoutYDiff;
    if (inFullscreenContainer || layoutInParentFrame()) {
        // We use the parent frame as the containing frame for fullscreen and child windows
        mContainingFrame.set(parentFrame);
        mDisplayFrame.set(displayFrame);//通过传递参数给该WindowState的属性赋值
        layoutDisplayFrame = displayFrame;
        layoutContainingFrame = parentFrame;
        layoutXDiff = 0;
        layoutYDiff = 0;
    } else {
        getBounds(mContainingFrame);
        if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {

            // If the bounds are frozen, we still want to translate the window freely and only
            // freeze the size.
            Rect frozen = mAppToken.mFrozenBounds.peek();
            mContainingFrame.right = mContainingFrame.left + frozen.width();
            mContainingFrame.bottom = mContainingFrame.top + frozen.height();
        }
        final WindowState imeWin = mService.mInputMethodWindow;
        // IME is up and obscuring this window. Adjust the window position so it is visible.
        if (imeWin != null && imeWin.isVisibleNow() && isInputMethodTarget()) {
            if (inFreeformWindowingMode()
                    && mContainingFrame.bottom > contentFrame.bottom) {
                // In freeform we want to move the top up directly.
                // TODO: Investigate why this is contentFrame not parentFrame.
                mContainingFrame.top -= mContainingFrame.bottom - contentFrame.bottom;
            } else if (!inPinnedWindowingMode()
                    && mContainingFrame.bottom > parentFrame.bottom) {
                // But in docked we want to behave like fullscreen and behave as if the task
                // were given smaller bounds for the purposes of layout. Skip adjustments for
                // the pinned stack, they are handled separately in the PinnedStackController.
                mContainingFrame.bottom = parentFrame.bottom;
            }
        }

        if (windowsAreFloating) {
            // In floating modes (e.g. freeform, pinned) we have only to set the rectangle
            // if it wasn't set already. No need to intersect it with the (visible)
            // "content frame" since it is allowed to be outside the visible desktop.
            if (mContainingFrame.isEmpty()) {
                mContainingFrame.set(contentFrame);
            }
        }

        final TaskStack stack = getStack();
        if (inPinnedWindowingMode() && stack != null
                && stack.lastAnimatingBoundsWasToFullscreen()) {
            // PIP edge case: When going from pinned to fullscreen, we apply a
            // tempInsetFrame for the full task - but we're still at the start of the animation.
            // To prevent a jump if there's a letterbox, restrict to the parent frame.
            mInsetFrame.intersectUnchecked(parentFrame);
            mContainingFrame.intersectUnchecked(parentFrame);
        }

        mDisplayFrame.set(mContainingFrame);
        layoutXDiff = !mInsetFrame.isEmpty() ? mInsetFrame.left - mContainingFrame.left : 0;
        layoutYDiff = !mInsetFrame.isEmpty() ? mInsetFrame.top - mContainingFrame.top : 0;
        layoutContainingFrame = !mInsetFrame.isEmpty() ? mInsetFrame : mContainingFrame;
        mTmpRect.set(0, 0, dc.getDisplayInfo().logicalWidth, dc.getDisplayInfo().logicalHeight);
        subtractInsets(mDisplayFrame, layoutContainingFrame, displayFrame, mTmpRect);
        if (!layoutInParentFrame()) {
            subtractInsets(mContainingFrame, layoutContainingFrame, parentFrame, mTmpRect);
            subtractInsets(mInsetFrame, layoutContainingFrame, parentFrame, mTmpRect);
        }
        layoutDisplayFrame = displayFrame;
        layoutDisplayFrame.intersect(layoutContainingFrame);
    }

    final int pw = mContainingFrame.width();
    final int ph = mContainingFrame.height();

    if (!mParentFrame.equals(parentFrame)) {
        //Slog.i(TAG_WM, "Window " + this + " content frame from " + mParentFrame
        //        + " to " + parentFrame);
        mParentFrame.set(parentFrame);
        mContentChanged = true;
    }
    if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) {
        mLastRequestedWidth = mRequestedWidth;
        mLastRequestedHeight = mRequestedHeight;
        mContentChanged = true;
    }

    mOverscanFrame.set(overscanFrame);
    mContentFrame.set(contentFrame);
    mVisibleFrame.set(visibleFrame);
    mDecorFrame.set(decorFrame);  //During layout, the current screen borders along which input method windows are placed. 导航栏不允许停靠iem
    mStableFrame.set(stableFrame);
    final boolean hasOutsets = outsetFrame != null;
    if (hasOutsets) {
        mOutsetFrame.set(outsetFrame);
    }

    final int fw = mFrame.width();
    final int fh = mFrame.height();

    applyGravityAndUpdateFrame(layoutContainingFrame, layoutDisplayFrame);

    // Calculate the outsets before the content frame gets shrinked to the window frame.
    if (hasOutsets) {
        /**
         * Outsets determine the area outside of the surface where we want to pretend that it's possible
         * to draw anyway.
       */
        mOutsets.set(Math.max(mContentFrame.left - mOutsetFrame.left, 0),
                Math.max(mContentFrame.top - mOutsetFrame.top, 0),
                Math.max(mOutsetFrame.right - mContentFrame.right, 0),
                Math.max(mOutsetFrame.bottom - mContentFrame.bottom, 0));
    } else {
        mOutsets.set(0, 0, 0, 0);
    }

    // Make sure the content and visible frames are inside of the
    // final window frame.
    if (windowsAreFloating && !mFrame.isEmpty()) {
        // For pinned workspace the frame isn't limited in any particular
        // way since SystemUI controls the bounds. For freeform however
        // we want to keep things inside the content frame.
        final Rect limitFrame = task.inPinnedWindowingMode() ? mFrame : mContentFrame;
        // Keep the frame out of the blocked system area, limit it in size to the content area
        // and make sure that there is always a minimum visible so that the user can drag it
        // into a usable area..
        final int height = Math.min(mFrame.height(), limitFrame.height());
        final int width = Math.min(limitFrame.width(), mFrame.width());
        final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
        final int minVisibleHeight = Math.min(height, WindowManagerService.dipToPixel(
                MINIMUM_VISIBLE_HEIGHT_IN_DP, displayMetrics));
        final int minVisibleWidth = Math.min(width, WindowManagerService.dipToPixel(
                MINIMUM_VISIBLE_WIDTH_IN_DP, displayMetrics));
        final int top = Math.max(limitFrame.top,
                Math.min(mFrame.top, limitFrame.bottom - minVisibleHeight));
        final int left = Math.max(limitFrame.left + minVisibleWidth - width,
                Math.min(mFrame.left, limitFrame.right - minVisibleWidth));
        mFrame.set(left, top, left + width, top + height);
        mContentFrame.set(mFrame);
        mVisibleFrame.set(mContentFrame);
        mStableFrame.set(mContentFrame);
    } else if (mAttrs.type == TYPE_DOCK_DIVIDER) { //针对DockedStackDivider
        dc.getDockedDividerController().positionDockedStackedDivider(mFrame);
        mContentFrame.set(mFrame);
        if (!mFrame.equals(mLastFrame)) {
            mMovedByResize = true;
        }
    } else {
        mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
                Math.max(mContentFrame.top, mFrame.top),
                Math.min(mContentFrame.right, mFrame.right),
                Math.min(mContentFrame.bottom, mFrame.bottom));

        mVisibleFrame.set(Math.max(mVisibleFrame.left, mFrame.left),
                Math.max(mVisibleFrame.top, mFrame.top),
                Math.min(mVisibleFrame.right, mFrame.right),
                Math.min(mVisibleFrame.bottom, mFrame.bottom));

        mStableFrame.set(Math.max(mStableFrame.left, mFrame.left),
                Math.max(mStableFrame.top, mFrame.top),
                Math.min(mStableFrame.right, mFrame.right),
                Math.min(mStableFrame.bottom, mFrame.bottom));
    }

    if (inFullscreenContainer && !windowsAreFloating) {
        // Windows that are not fullscreen can be positioned outside of the display frame,
        // but that is not a reason to provide them with overscan insets.
        mOverscanInsets.set(Math.max(mOverscanFrame.left - layoutContainingFrame.left, 0),
                Math.max(mOverscanFrame.top - layoutContainingFrame.top, 0),
                Math.max(layoutContainingFrame.right - mOverscanFrame.right, 0),
                Math.max(layoutContainingFrame.bottom - mOverscanFrame.bottom, 0));
    }

    if (mAttrs.type == TYPE_DOCK_DIVIDER) {
        // For the docked divider, we calculate the stable insets like a full-screen window
        // so it can use it to calculate the snap positions.
        final WmDisplayCutout c = displayCutout.calculateRelativeTo(mDisplayFrame);
        mTmpRect.set(mDisplayFrame);
        mTmpRect.inset(c.getDisplayCutout().getSafeInsets());
        mTmpRect.intersectUnchecked(mStableFrame);

        mStableInsets.set(Math.max(mTmpRect.left - mDisplayFrame.left, 0),
                Math.max(mTmpRect.top - mDisplayFrame.top, 0),
                Math.max(mDisplayFrame.right - mTmpRect.right, 0),
                Math.max(mDisplayFrame.bottom - mTmpRect.bottom, 0));

        // The divider doesn't care about insets in any case, so set it to empty so we don't
        // trigger a relayout when moving it.
        mContentInsets.setEmpty();
        mVisibleInsets.setEmpty();
        displayCutout = WmDisplayCutout.NO_CUTOUT;
    } else {
        getDisplayContent().getBounds(mTmpRect);
        // Override right and/or bottom insets in case if the frame doesn't fit the screen in
        // non-fullscreen mode.
        boolean overrideRightInset = !windowsAreFloating && !inFullscreenContainer
                && mFrame.right > mTmpRect.right;
        boolean overrideBottomInset = !windowsAreFloating && !inFullscreenContainer
                && mFrame.bottom > mTmpRect.bottom;
        mContentInsets.set(mContentFrame.left - mFrame.left,
                mContentFrame.top - mFrame.top,
                overrideRightInset ? mTmpRect.right - mContentFrame.right
                        : mFrame.right - mContentFrame.right,
                overrideBottomInset ? mTmpRect.bottom - mContentFrame.bottom
                        : mFrame.bottom - mContentFrame.bottom);

        mVisibleInsets.set(mVisibleFrame.left - mFrame.left,
                mVisibleFrame.top - mFrame.top,
                overrideRightInset ? mTmpRect.right - mVisibleFrame.right
                        : mFrame.right - mVisibleFrame.right,
                overrideBottomInset ? mTmpRect.bottom - mVisibleFrame.bottom
                        : mFrame.bottom - mVisibleFrame.bottom);

        mStableInsets.set(Math.max(mStableFrame.left - mFrame.left, 0),
                Math.max(mStableFrame.top - mFrame.top, 0),
                overrideRightInset ? Math.max(mTmpRect.right - mStableFrame.right, 0)
                        : Math.max(mFrame.right - mStableFrame.right, 0),
                overrideBottomInset ? Math.max(mTmpRect.bottom - mStableFrame.bottom, 0)
                        :  Math.max(mFrame.bottom - mStableFrame.bottom, 0));
    }

    mDisplayCutout = displayCutout.calculateRelativeTo(mFrame);

    // Offset the actual frame by the amount layout frame is off.
    mFrame.offset(-layoutXDiff, -layoutYDiff);
    mCompatFrame.offset(-layoutXDiff, -layoutYDiff);
    mContentFrame.offset(-layoutXDiff, -layoutYDiff);
    mVisibleFrame.offset(-layoutXDiff, -layoutYDiff);
    mStableFrame.offset(-layoutXDiff, -layoutYDiff);

    mCompatFrame.set(mFrame);
    if (mEnforceSizeCompat) {
        // If there is a size compatibility scale being applied to the
        // window, we need to apply this to its insets so that they are
        // reported to the app in its coordinate space.
        mOverscanInsets.scale(mInvGlobalScale);
        mContentInsets.scale(mInvGlobalScale);
        mVisibleInsets.scale(mInvGlobalScale);
        mStableInsets.scale(mInvGlobalScale);
        mOutsets.scale(mInvGlobalScale);

        // Also the scaled frame that we report to the app needs to be
        // adjusted to be in its coordinate space.
        mCompatFrame.scale(mInvGlobalScale);
    }

    if (mIsWallpaper && (fw != mFrame.width() || fh != mFrame.height())) {
        final DisplayContent displayContent = getDisplayContent();
        if (displayContent != null) {
            final DisplayInfo displayInfo = displayContent.getDisplayInfo();
            getDisplayContent().mWallpaperController.updateWallpaperOffset(
                    this, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
        }
    }

    if (DEBUG_LAYOUT || localLOGV) Slog.v(TAG,
            "Resolving (mRequestedWidth="
            + mRequestedWidth + ", mRequestedheight="
            + mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph
            + "): frame=" + mFrame.toShortString()
            + " ci=" + mContentInsets.toShortString()
            + " vi=" + mVisibleInsets.toShortString()
            + " si=" + mStableInsets.toShortString()
            + " of=" + mOutsets.toShortString());
}

关键的计算点在applyGravityAndUpdateFrame,根据Window的Gravity(horizontal等)计算更新frame;每个窗口初次计算时都会有这个逻辑,下面再讲

PhoneWindowManager#layoutStatusBar

计算StatusBar的大小/布局(因为有左上角顶点,长和宽)

private boolean layoutStatusBar(DisplayFrames displayFrames, Rect pf, Rect df, Rect of, Rect vf,
        Rect dcf, int sysui, boolean isKeyguardShowing) {
    // decide where the status bar goes ahead of time
    if (mStatusBar == null) {
        return false;
    }
    // apply any navigation bar insets
    of.set(displayFrames.mUnrestricted);
    df.set(displayFrames.mUnrestricted);
    pf.set(displayFrames.mUnrestricted);
    vf.set(displayFrames.mStable);
/*
pf(parentFrame):表示窗口父窗口的大小
df(deviceFrame):设备的屏幕大小
of(OverScanFrame):设备屏幕大小、和df值一样
cf(ContentFrame):窗口内容区域大小
vf(VisibleFrame):窗口可见内容区域大小
dcf(DecorFrame):装饰区域大小。除去状态栏和导航栏的区域。
sf(Stableframe) :除去状态栏和导航栏的区域。
*/

    mStatusBarLayer = mStatusBar.getSurfaceLayer();

    // Let the status bar determine its size.
    mStatusBar.computeFrameLw(pf /* parentFrame */, df /* displayFrame */,
            vf /* overlayFrame */, vf /* contentFrame */, vf /* visibleFrame */,
            dcf /* decorFrame */, vf /* stableFrame */, vf /* outsetFrame */,
            displayFrames.mDisplayCutout, false /* parentFrameWasClippedByDisplayCutout */);

    // For layout, the status bar is always at the top with our fixed height.
    displayFrames.mStable.top = displayFrames.mUnrestricted.top
            + mStatusBarHeightForRotation[displayFrames.mRotation];
    // Make sure the status bar covers the entire cutout height
    displayFrames.mStable.top = Math.max(displayFrames.mStable.top,
            displayFrames.mDisplayCutoutSafe.top);

    // Tell the bar controller where the collapsed status bar content is
    mTmpRect.set(mStatusBar.getContentFrameLw());
    mTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
    mTmpRect.top = mStatusBar.getContentFrameLw().top;  // Ignore top display cutout inset
    mTmpRect.bottom = displayFrames.mStable.top;  // Use collapsed status bar size
    mStatusBarController.setContentFrame(mTmpRect);

    boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
    boolean statusBarTranslucent = (sysui
            & (View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT)) != 0;
    if (!isKeyguardShowing) {
        statusBarTranslucent &= areTranslucentBarsAllowed();
    }

    // If the status bar is hidden, we don't want to cause windows behind it to scroll.
    if (mStatusBar.isVisibleLw() && !statusBarTransient) {
        // Status bar may go away, so the screen area it occupies is available to apps but just
        // covering them when the status bar is visible.
        final Rect dockFrame = displayFrames.mDock;
        dockFrame.top = displayFrames.mStable.top;
        displayFrames.mContent.set(dockFrame);
        displayFrames.mVoiceContent.set(dockFrame);
        displayFrames.mCurrent.set(dockFrame);

        if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar: " + String.format(
                "dock=%s content=%s cur=%s", dockFrame.toString(),
                displayFrames.mContent.toString(), displayFrames.mCurrent.toString()));

        if (!mStatusBar.isAnimatingLw() && !statusBarTranslucent
                && !mStatusBarController.wasRecentlyTranslucent()) {
            // If the opaque status bar is currently requested to be visible, and not in the
            // process of animating on or off, then we can tell the app that it is covered by it.
            displayFrames.mSystem.top = displayFrames.mStable.top;
        }
    }
    return mStatusBarController.checkHiddenLw();
}

上面performLayout的第一步beginLayoutLw来设置屏幕的大小,计算状态栏,导航栏的大小,布局已经处理完毕;接着看第二步(针对其他窗口,父窗口,子窗口的相关布局计算)
首先对于父窗口
forAllWindows(mPerformLayout, true /* traverseTopToBottom */);

针对父窗口的callback-mPerformLayout
private final Consumer<WindowState> mPerformLayout = w -> {
    // Don't do layout of a window if it is not visible, or soon won't be visible, to avoid
    // wasting time and funky changes while a window is animating away.
    final boolean gone = (mTmpWindow != null && mService.mPolicy.canBeHiddenByKeyguardLw(w))
            || w.isGoneForLayoutLw();

    if (DEBUG_LAYOUT && !w.mLayoutAttached) {
        Slog.v(TAG, "1ST PASS " + w + ": gone=" + gone + " mHaveFrame=" + w.mHaveFrame
                + " mLayoutAttached=" + w.mLayoutAttached
                + " screen changed=" + w.isConfigChanged());
        final AppWindowToken atoken = w.mAppToken;
        if (gone) Slog.v(TAG, "  GONE: mViewVisibility=" + w.mViewVisibility
                + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden()
                + " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
                + " parentHidden=" + w.isParentWindowHidden());
        else Slog.v(TAG, "  VIS: mViewVisibility=" + w.mViewVisibility
                + " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden()
                + " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
                + " parentHidden=" + w.isParentWindowHidden());
    }

    // If this view is GONE, then skip it -- keep the current frame, and let the caller know
    // so they can ignore it if they want.  (We do the normal layout for INVISIBLE windows,
    // since that means "perform layout as normal, just don't display").
    if (!gone || !w.mHaveFrame || w.mLayoutNeeded
            || ((w.isConfigChanged() || w.setReportResizeHints())
            && !w.isGoneForLayoutLw() &&
            ((w.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||
                    (w.mHasSurface && w.mAppToken != null &&
                            w.mAppToken.layoutConfigChanges)))) {
        if (!w.mLayoutAttached) {
            if (mTmpInitial) {
                //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
                w.mContentChanged = false;
            }
            if (w.mAttrs.type == TYPE_DREAM) {
                // Don't layout windows behind a dream, so that if it does stuff like hide
                // the status bar we won't get a bad transition when it goes away.
                mTmpWindow = w;
            }
            w.mLayoutNeeded = false;
            w.prelayout();
            final boolean firstLayout = !w.isLaidOut();
            mService.mPolicy.layoutWindowLw(w, null, mDisplayFrames);
            w.mLayoutSeq = mLayoutSeq;

            // If this is the first layout, we need to initialize the last inset values as
            // otherwise we'd immediately cause an unnecessary resize.
            if (firstLayout) {
                w.updateLastInsetValues();
            }

            if (w.mAppToken != null) {
                w.mAppToken.layoutLetterbox(w);
            }

            if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame=" + w.mFrame
                    + " mContainingFrame=" + w.mContainingFrame
                    + " mDisplayFrame=" + w.mDisplayFrame);
        }
    }
};

DisplayContent#forAllWindows

@Override
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
    // Special handling so we can process IME windows with #forAllImeWindows above their IME
    // target, or here in order if there isn't an IME target.
    if (traverseTopToBottom) {
        for (int i = mChildren.size() - 1; i >= 0; --i) {
            final DisplayChildWindowContainer child = mChildren.get(i);
            if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) {
                // In this case the Ime windows will be processed above their target so we skip
                // here.
                continue;
            }
            if (child.forAllWindows(callback, traverseTopToBottom)) {
                return true;
            }
        }
    } else {
        final int count = mChildren.size();
        for (int i = 0; i < count; i++) {
            final DisplayChildWindowContainer child = mChildren.get(i);
            if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) {
                // In this case the Ime windows will be processed above their target so we skip
                // here.
                continue;
            }
            if (child.forAllWindows(callback, traverseTopToBottom)) {
                return true;
            }
        }
    }
    return false;
}

首先针对DisplayContent的mChildren


DisplayContent.mChildren.png

调用上述WindowContainer的forAllWindows

WindowContainer#forAllWindows

/**
 * For all windows at or below this container call the callback.
 * @param   callback Calls the {@link ToBooleanFunction#apply} method for each window found and
 *                   stops the search if {@link ToBooleanFunction#apply} returns true.
 * @param   traverseTopToBottom If true traverses the hierarchy from top-to-bottom in terms of
 *                              z-order, else from bottom-to-top.
 * @return  True if the search ended before we reached the end of the hierarchy due to
 *          {@link ToBooleanFunction#apply} returning true.
 */
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
    if (traverseTopToBottom) {
        for (int i = mChildren.size() - 1; i >= 0; --i) { 
            if (mChildren.get(i).forAllWindows(callback, traverseTopToBottom)) {
                return true;
            }
        }
    } else {
        final int count = mChildren.size();
        for (int i = 0; i < count; i++) {
            if (mChildren.get(i).forAllWindows(callback, traverseTopToBottom)) {
                return true;
            }
        }
    }
    return false;
}

针对如TaskStackContainer的情况,其mChildren为:


WindowContainer.mChilren_1.png

针对WindowToken的情况,其mChildren为:


WindowContainer.mChilren_2.png

WindowContainer中的mChildren可能为list (WindowToken or WindowState) 复用
DisplayContent.AboveAppWindowsContainers包含除了app页面的一些系统windowToken
DisplayContent.TaskStackContainers包含launcher和app的相应AppWindowToken

WindowState#forAllWindows

@Override
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
    if (mChildren.isEmpty()) { 
        // The window has no children so we just return it.
        return applyInOrderWithImeWindows(callback, traverseTopToBottom);
    }

    if (traverseTopToBottom) {
        return forAllWindowTopToBottom(callback);
    } else {
        return forAllWindowBottomToTop(callback);
    }
}

WindowState#forAllWindowTopToBottom

private boolean forAllWindowTopToBottom(ToBooleanFunction<WindowState> callback) {
    // We want to consume the positive sublayer children first because they need to appear
    // above the parent, then this window (the parent), and then the negative sublayer children
    // because they need to appear above the parent.
    int i = mChildren.size() - 1;
    WindowState child = mChildren.get(i); //前面说过,WindowState的mChildren就是依附于它的子窗口

    while (i >= 0 && child.mSubLayer >= 0) {
        if (child.applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) { //对于子窗口,在这里调用其callback
            return true;
        }
        --i;
        if (i < 0) {
            break;
        }
        child = mChildren.get(i);
    }

    if (applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) {
        return true;
    }

    while (i >= 0) {
        if (child.applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) {
            return true;
        }
        --i;
        if (i < 0) {
            break;
        }
        child = mChildren.get(i);
    }

    return false;
}

调用mPerformLayout callbacks的trace


调用mPerformLayout callbacks.png

PhoneWindowManager#layoutWindowLw

计算窗口大小是在layoutWindowLw中完成的,该方法不会直接计算出窗口大小,而是先计算出窗口能够扩展的最大空间,然后再调用WindowState的computeFrameLw来计算出窗口的最终大小(很长,针对各种类型窗口的处理)

/** {@inheritDoc} */
@Override
public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
    // We've already done the navigation bar, status bar, and all screen decor windows. If the
    // status bar can receive input, we need to layout it again to accommodate for the IME
    // window.
    if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar
            || mScreenDecorWindows.contains(win)) {
        return;
    }
    final WindowManager.LayoutParams attrs = win.getAttrs();
    final boolean isDefaultDisplay = win.isDefaultDisplay();
    final boolean needsToOffsetInputMethodTarget = isDefaultDisplay &&
            (win == mLastInputMethodTargetWindow && mLastInputMethodWindow != null);
    if (needsToOffsetInputMethodTarget) {
        if (DEBUG_LAYOUT) Slog.i(TAG, "Offset ime target window by the last ime window state");
        offsetInputMethodWindowLw(mLastInputMethodWindow, displayFrames);
    }

    final int type = attrs.type;
    final int fl = PolicyControl.getWindowFlags(win, attrs);
    final int pfl = attrs.privateFlags;
    final int sim = attrs.softInputMode;
    final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(null, attrs);
    final int sysUiFl = requestedSysUiFl | getImpliedSysUiFlagsForLayout(attrs);

    final Rect pf = mTmpParentFrame;
    final Rect df = mTmpDisplayFrame;
    final Rect of = mTmpOverscanFrame;
    final Rect cf = mTmpContentFrame;
    final Rect vf = mTmpVisibleFrame;
    final Rect dcf = mTmpDecorFrame;
    final Rect sf = mTmpStableFrame;
    Rect osf = null;
    dcf.setEmpty();

    final boolean hasNavBar = (isDefaultDisplay && mHasNavigationBar
            && mNavigationBar != null && mNavigationBar.isVisibleLw());

    final int adjust = sim & SOFT_INPUT_MASK_ADJUST;

    final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0
            || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;

    final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
    final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;

    sf.set(displayFrames.mStable);

    if (type == TYPE_INPUT_METHOD) {
        vf.set(displayFrames.mDock);
        cf.set(displayFrames.mDock);
        of.set(displayFrames.mDock);
        df.set(displayFrames.mDock);
        pf.set(displayFrames.mDock);
        // IM dock windows layout below the nav bar...
        pf.bottom = df.bottom = of.bottom = displayFrames.mUnrestricted.bottom;
        // ...with content insets above the nav bar
        cf.bottom = vf.bottom = displayFrames.mStable.bottom;
        if (mStatusBar != null && mFocusedWindow == mStatusBar && canReceiveInput(mStatusBar)) {
            // The status bar forces the navigation bar while it's visible. Make sure the IME
            // avoids the navigation bar in that case.
            if (mNavigationBarPosition == NAV_BAR_RIGHT) {
                pf.right = df.right = of.right = cf.right = vf.right =
                        displayFrames.mStable.right;
            } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
                pf.left = df.left = of.left = cf.left = vf.left = displayFrames.mStable.left;
            }
        }
        // IM dock windows always go to the bottom of the screen.
        attrs.gravity = Gravity.BOTTOM;
        mDockLayer = win.getSurfaceLayer();
    } else if (type == TYPE_VOICE_INTERACTION) {
        of.set(displayFrames.mUnrestricted);
        df.set(displayFrames.mUnrestricted);
        pf.set(displayFrames.mUnrestricted);
        if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
            cf.set(displayFrames.mDock);
        } else {
            cf.set(displayFrames.mContent);
        }
        if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
            vf.set(displayFrames.mCurrent);
        } else {
            vf.set(cf);
        }
    } else if (type == TYPE_WALLPAPER) {
       layoutWallpaper(displayFrames, pf, df, of, cf);
    } else if (win == mStatusBar) {
        of.set(displayFrames.mUnrestricted);
        df.set(displayFrames.mUnrestricted);
        pf.set(displayFrames.mUnrestricted);
        cf.set(displayFrames.mStable);
        vf.set(displayFrames.mStable);

        if (adjust == SOFT_INPUT_ADJUST_RESIZE) {
            cf.bottom = displayFrames.mContent.bottom;
        } else {
            cf.bottom = displayFrames.mDock.bottom;
            vf.bottom = displayFrames.mContent.bottom;
        }
    } else {
        dcf.set(displayFrames.mSystem);
        final boolean inheritTranslucentDecor =
                (attrs.privateFlags & PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) != 0;
        final boolean isAppWindow =
                type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW;
        final boolean topAtRest =
                win == mTopFullscreenOpaqueWindowState && !win.isAnimatingLw();
        if (isAppWindow && !inheritTranslucentDecor && !topAtRest) {
            if ((sysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0
                    && (fl & FLAG_FULLSCREEN) == 0
                    && (fl & FLAG_TRANSLUCENT_STATUS) == 0
                    && (fl & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
                    && (pfl & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) == 0) {
                // Ensure policy decor includes status bar
                dcf.top = displayFrames.mStable.top;
            }
            if ((fl & FLAG_TRANSLUCENT_NAVIGATION) == 0
                    && (sysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0
                    && (fl & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
                // Ensure policy decor includes navigation bar
                dcf.bottom = displayFrames.mStable.bottom;
                dcf.right = displayFrames.mStable.right;
            }
        }

        if (layoutInScreen && layoutInsetDecor) {
            if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
                        + "): IN_SCREEN, INSET_DECOR");
            // This is the case for a normal activity window: we want it to cover all of the
            // screen space, and it can take care of moving its contents to account for screen
            // decorations that intrude into that space.
            if (attached != null) {
                // If this window is attached to another, our display
                // frame is the same as the one we are attached to.
                setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf,
                        displayFrames); //针对child window的处理
            } else {
                if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_STATUS_BAR_SUB_PANEL) {
                    // Status bar panels are the only windows who can go on top of the status
                    // bar. They are protected by the STATUS_BAR_SERVICE permission, so they
                    // have the same privileges as the status bar itself.
                    //
                    // However, they should still dodge the navigation bar if it exists.

                    pf.left = df.left = of.left = hasNavBar
                            ? displayFrames.mDock.left : displayFrames.mUnrestricted.left;
                    pf.top = df.top = of.top = displayFrames.mUnrestricted.top;
                    pf.right = df.right = of.right = hasNavBar
                            ? displayFrames.mRestricted.right
                            : displayFrames.mUnrestricted.right;
                    pf.bottom = df.bottom = of.bottom = hasNavBar
                            ? displayFrames.mRestricted.bottom
                            : displayFrames.mUnrestricted.bottom;

                    if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
                                    "Laying out status bar window: (%d,%d - %d,%d)",
                                    pf.left, pf.top, pf.right, pf.bottom));
                } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
                        && type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {
                    // Asking to layout into the overscan region, so give it that pure
                    // unrestricted area.
                    of.set(displayFrames.mOverscan);
                    df.set(displayFrames.mOverscan);
                    pf.set(displayFrames.mOverscan);
                } else if (canHideNavigationBar()
                        && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
                        && (type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW
                        || type == TYPE_VOLUME_OVERLAY)) {
                    // Asking for layout as if the nav bar is hidden, lets the application
                    // extend into the unrestricted overscan screen area. We only do this for
                    // application windows and certain system windows to ensure no window that
                    // can be above the nav bar can do this.
                    df.set(displayFrames.mOverscan);
                    pf.set(displayFrames.mOverscan);
                    // We need to tell the app about where the frame inside the overscan is, so
                    // it can inset its content by that amount -- it didn't ask to actually
                    // extend itself into the overscan region.
                    of.set(displayFrames.mUnrestricted);
                } else {
                    df.set(displayFrames.mRestrictedOverscan);
                    pf.set(displayFrames.mRestrictedOverscan);
                    // We need to tell the app about where the frame inside the overscan
                    // is, so it can inset its content by that amount -- it didn't ask
                    // to actually extend itself into the overscan region.
                    of.set(displayFrames.mUnrestricted);
                }

                if ((fl & FLAG_FULLSCREEN) == 0) {
                    if (win.isVoiceInteraction()) {
                        cf.set(displayFrames.mVoiceContent);
                    } else {
                        if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
                            cf.set(displayFrames.mDock);
                        } else {
                            cf.set(displayFrames.mContent);
                        }
                    }
                } else {
                    // Full screen windows are always given a layout that is as if the status
                    // bar and other transient decors are gone. This is to avoid bad states when
                    // moving from a window that is not hiding the status bar to one that is.
                    cf.set(displayFrames.mRestricted);
                }
                applyStableConstraints(sysUiFl, fl, cf, displayFrames);
                if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
                    vf.set(displayFrames.mCurrent);
                } else {
                    vf.set(cf);
                }
            }
        } else if (layoutInScreen || (sysUiFl
                & (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) {
            if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
                    + "): IN_SCREEN");
            // A window that has requested to fill the entire screen just
            // gets everything, period.
            if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_STATUS_BAR_SUB_PANEL) {
                cf.set(displayFrames.mUnrestricted);
                of.set(displayFrames.mUnrestricted);
                df.set(displayFrames.mUnrestricted);
                pf.set(displayFrames.mUnrestricted);
                if (hasNavBar) {
                    pf.left = df.left = of.left = cf.left = displayFrames.mDock.left;
                    pf.right = df.right = of.right = cf.right = displayFrames.mRestricted.right;
                    pf.bottom = df.bottom = of.bottom = cf.bottom =
                            displayFrames.mRestricted.bottom;
                }
                if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
                        "Laying out IN_SCREEN status bar window: (%d,%d - %d,%d)",
                        pf.left, pf.top, pf.right, pf.bottom));
            } else if (type == TYPE_NAVIGATION_BAR || type == TYPE_NAVIGATION_BAR_PANEL) {
                // The navigation bar has Real Ultimate Power.
                of.set(displayFrames.mUnrestricted);
                df.set(displayFrames.mUnrestricted);
                pf.set(displayFrames.mUnrestricted);
                if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
                                "Laying out navigation bar window: (%d,%d - %d,%d)",
                                pf.left, pf.top, pf.right, pf.bottom));
            } else if ((type == TYPE_SECURE_SYSTEM_OVERLAY || type == TYPE_SCREENSHOT)
                    && ((fl & FLAG_FULLSCREEN) != 0)) {
                // Fullscreen secure system overlays get what they ask for. Screenshot region
                // selection overlay should also expand to full screen.
                cf.set(displayFrames.mOverscan);
                of.set(displayFrames.mOverscan);
                df.set(displayFrames.mOverscan);
                pf.set(displayFrames.mOverscan);
            } else if (type == TYPE_BOOT_PROGRESS) {
                // Boot progress screen always covers entire display.
                cf.set(displayFrames.mOverscan);
                of.set(displayFrames.mOverscan);
                df.set(displayFrames.mOverscan);
                pf.set(displayFrames.mOverscan);
            } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
                    && type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {
                // Asking to layout into the overscan region, so give it that pure unrestricted
                // area.
                cf.set(displayFrames.mOverscan);
                of.set(displayFrames.mOverscan);
                df.set(displayFrames.mOverscan);
                pf.set(displayFrames.mOverscan);
            } else if (canHideNavigationBar()
                    && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
                    && (type == TYPE_STATUS_BAR
                        || type == TYPE_TOAST
                        || type == TYPE_DOCK_DIVIDER
                        || type == TYPE_VOICE_INTERACTION_STARTING
                        || (type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW))) {
                // Asking for layout as if the nav bar is hidden, lets the
                // application extend into the unrestricted screen area.  We
                // only do this for application windows (or toasts) to ensure no window that
                // can be above the nav bar can do this.
                // XXX This assumes that an app asking for this will also
                // ask for layout in only content.  We can't currently figure out
                // what the screen would be if only laying out to hide the nav bar.
                cf.set(displayFrames.mUnrestricted);
                of.set(displayFrames.mUnrestricted);
                df.set(displayFrames.mUnrestricted);
                pf.set(displayFrames.mUnrestricted);
            } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) {
                of.set(displayFrames.mRestricted);
                df.set(displayFrames.mRestricted);
                pf.set(displayFrames.mRestricted);
                if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
                    cf.set(displayFrames.mDock);
                } else {
                    cf.set(displayFrames.mContent);
                }
            } else {
                cf.set(displayFrames.mRestricted);
                of.set(displayFrames.mRestricted);
                df.set(displayFrames.mRestricted);
                pf.set(displayFrames.mRestricted);
            }

            applyStableConstraints(sysUiFl, fl, cf,displayFrames);

            if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
                vf.set(displayFrames.mCurrent);
            } else {
                vf.set(cf);
            }
        } else if (attached != null) {
            if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
                    + "): attached to " + attached);
            // A child window should be placed inside of the same visible
            // frame that its parent had.
            setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, of, cf, vf,
                    displayFrames);
        } else {
            if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
                    "): normal window");
            // Otherwise, a normal window must be placed inside the content
            // of all screen decorations.
            if (type == TYPE_STATUS_BAR_PANEL) {
                // Status bar panels can go on
                // top of the status bar. They are protected by the STATUS_BAR_SERVICE
                // permission, so they have the same privileges as the status bar itself.
                cf.set(displayFrames.mRestricted);
                of.set(displayFrames.mRestricted);
                df.set(displayFrames.mRestricted);
                pf.set(displayFrames.mRestricted);
            } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT) {
                // These dialogs are stable to interim decor changes.
                cf.set(displayFrames.mStable);
                of.set(displayFrames.mStable);
                df.set(displayFrames.mStable);
                pf.set(displayFrames.mStable);
            } else {
                pf.set(displayFrames.mContent);
                if (win.isVoiceInteraction()) {
                    cf.set(displayFrames.mVoiceContent);
                    of.set(displayFrames.mVoiceContent);
                    df.set(displayFrames.mVoiceContent);
                } else if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
                    cf.set(displayFrames.mDock);
                    of.set(displayFrames.mDock);
                    df.set(displayFrames.mDock);
                } else {
                    cf.set(displayFrames.mContent);
                    of.set(displayFrames.mContent);
                    df.set(displayFrames.mContent);
                }
                if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
                    vf.set(displayFrames.mCurrent);
                } else {
                    vf.set(cf);
                }
            }
        }
    }

    boolean parentFrameWasClippedByDisplayCutout = false;
    final int cutoutMode = attrs.layoutInDisplayCutoutMode;
    final boolean attachedInParent = attached != null && !layoutInScreen;
    final boolean requestedHideNavigation =
            (requestedSysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;

    // TYPE_BASE_APPLICATION windows are never considered floating here because they don't get
    // cropped / shifted to the displayFrame in WindowState.
    final boolean floatingInScreenWindow = !attrs.isFullscreen() && layoutInScreen
            && type != TYPE_BASE_APPLICATION;

    // Ensure that windows with a DEFAULT or NEVER display cutout mode are laid out in
    // the cutout safe zone.
    if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
        final Rect displayCutoutSafeExceptMaybeBars = mTmpDisplayCutoutSafeExceptMaybeBarsRect;
        displayCutoutSafeExceptMaybeBars.set(displayFrames.mDisplayCutoutSafe);
        if (layoutInScreen && layoutInsetDecor && !requestedFullscreen
                && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) {
            // At the top we have the status bar, so apps that are
            // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR but not FULLSCREEN
            // already expect that there's an inset there and we don't need to exclude
            // the window from that area.
            displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
        }
        if (layoutInScreen && layoutInsetDecor && !requestedHideNavigation
                && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) {
            // Same for the navigation bar.
            switch (mNavigationBarPosition) {
                case NAV_BAR_BOTTOM:
                    displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
                    break;
                case NAV_BAR_RIGHT:
                    displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
                    break;
                case NAV_BAR_LEFT:
                    displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
                    break;
            }
        }
        if (type == TYPE_INPUT_METHOD && mNavigationBarPosition == NAV_BAR_BOTTOM) {
            // The IME can always extend under the bottom cutout if the navbar is there.
            displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
        }
        // Windows that are attached to a parent and laid out in said parent already avoid
        // the cutout according to that parent and don't need to be further constrained.
        // Floating IN_SCREEN windows get what they ask for and lay out in the full screen.
        // They will later be cropped or shifted using the displayFrame in WindowState,
        // which prevents overlap with the DisplayCutout.
        if (!attachedInParent && !floatingInScreenWindow) {
            mTmpRect.set(pf);
            pf.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
            parentFrameWasClippedByDisplayCutout |= !mTmpRect.equals(pf);
        }
        // Make sure that NO_LIMITS windows clipped to the display don't extend under the
        // cutout.
        df.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
    }

    // Content should never appear in the cutout.
    cf.intersectUnchecked(displayFrames.mDisplayCutoutSafe);

    // TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
    // Also, we don't allow windows in multi-window mode to extend out of the screen.
    if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && type != TYPE_SYSTEM_ERROR
            && !win.isInMultiWindowMode()) {
        df.left = df.top = -10000;
        df.right = df.bottom = 10000;
        if (type != TYPE_WALLPAPER) {
            of.left = of.top = cf.left = cf.top = vf.left = vf.top = -10000;
            of.right = of.bottom = cf.right = cf.bottom = vf.right = vf.bottom = 10000;
        }
    }

    // If the device has a chin (e.g. some watches), a dead area at the bottom of the screen we
    // need to provide information to the clients that want to pretend that you can draw there.
    // We only want to apply outsets to certain types of windows. For example, we never want to
    // apply the outsets to floating dialogs, because they wouldn't make sense there.
    final boolean useOutsets = shouldUseOutsets(attrs, fl);
    if (isDefaultDisplay && useOutsets) {
        osf = mTmpOutsetFrame;
        osf.set(cf.left, cf.top, cf.right, cf.bottom);
        int outset = ScreenShapeHelper.getWindowOutsetBottomPx(mContext.getResources());
        if (outset > 0) {
            int rotation = displayFrames.mRotation;
            if (rotation == Surface.ROTATION_0) {
                osf.bottom += outset;
            } else if (rotation == Surface.ROTATION_90) {
                osf.right += outset;
            } else if (rotation == Surface.ROTATION_180) {
                osf.top -= outset;
            } else if (rotation == Surface.ROTATION_270) {
                osf.left -= outset;
            }
            if (DEBUG_LAYOUT) Slog.v(TAG, "applying bottom outset of " + outset
                    + " with rotation " + rotation + ", result: " + osf);
        }
    }

    if (DEBUG_LAYOUT) Slog.v(TAG, "Compute frame " + attrs.getTitle()
            + ": sim=#" + Integer.toHexString(sim)
            + " attach=" + attached + " type=" + type
            + String.format(" flags=0x%08x", fl)
            + " pf=" + pf.toShortString() + " df=" + df.toShortString()
            + " of=" + of.toShortString()
            + " cf=" + cf.toShortString() + " vf=" + vf.toShortString()
            + " dcf=" + dcf.toShortString()
            + " sf=" + sf.toShortString()
            + " osf=" + (osf == null ? "null" : osf.toShortString()));

    win.computeFrameLw(pf, df, of, cf, vf, dcf, sf, osf, displayFrames.mDisplayCutout,
            parentFrameWasClippedByDisplayCutout); //最后调用computeFrameLw计算该窗口大小相关的属性
    // Dock windows carve out the bottom of the screen, so normal windows
    // can't appear underneath them.
    if (type == TYPE_INPUT_METHOD && win.isVisibleLw()
            && !win.getGivenInsetsPendingLw()) {
        setLastInputMethodWindowLw(null, null);
        offsetInputMethodWindowLw(win, displayFrames);
    }
    if (type == TYPE_VOICE_INTERACTION && win.isVisibleLw()
            && !win.getGivenInsetsPendingLw()) {
        offsetVoiceInputWindowLw(win, displayFrames);
    }
}

调用子窗口的callback mPerformLayoutAttached

private final Consumer<WindowState> mPerformLayoutAttached = w -> {
    if (w.mLayoutAttached) { //对于子窗口,mLayoutAttached的值为true
        if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + w + " mHaveFrame=" + w.mHaveFrame
                + " mViewVisibility=" + w.mViewVisibility
                + " mRelayoutCalled=" + w.mRelayoutCalled);
        // If this view is GONE, then skip it -- keep the current frame, and let the caller
        // know so they can ignore it if they want.  (We do the normal layout for INVISIBLE
        // windows, since that means "perform layout as normal, just don't display").
        if (mTmpWindow != null && mService.mPolicy.canBeHiddenByKeyguardLw(w)) {
            return;
        }
        if ((w.mViewVisibility != GONE && w.mRelayoutCalled) || !w.mHaveFrame
                || w.mLayoutNeeded) {
            if (mTmpInitial) {
                //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
                w.mContentChanged = false;
            }
            w.mLayoutNeeded = false;
            w.prelayout();
            mService.mPolicy.layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);
            w.mLayoutSeq = mLayoutSeq;
            if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.mFrame
                    + " mContainingFrame=" + w.mContainingFrame
                    + " mDisplayFrame=" + w.mDisplayFrame);
        }
    } else if (w.mAttrs.type == TYPE_DREAM) {
        // Don't layout windows behind a dream, so that if it does stuff like hide the
        // status bar we won't get a bad transition when it goes away.
        mTmpWindow = mTmpWindow2;
    }
};
子窗口计算窗口大小.png

layoutWindowLw->setAttachedWindowFrames

computeFrameLw 第一次计算mHaveFrame = false,一开始计算时mFrame = (0,0-0,0)

以后再走到这里时mHaveFrame = false,虽然也会走到下面,但mFrame已经是window的大小了

第一次计算mFrame时,computeFrameLw中调用相关逻辑:

applyGravityAndUpdateFrame(layoutContainingFrame, layoutDisplayFrame);
WindowState#applyGravityAndUpdateFrame

更新mFrame

void applyGravityAndUpdateFrame(Rect containingFrame, Rect displayFrame) {
    final int pw = containingFrame.width();
    final int ph = containingFrame.height();
    final Task task = getTask();
    final boolean inNonFullscreenContainer = !inFullscreenContainer();
    final boolean noLimits = (mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;

    // We need to fit it to the display if either
    // a) The window is in a fullscreen container, or we don't have a task (we assume fullscreen
    // for the taskless windows)
    // b) If it's a secondary app window, we also need to fit it to the display unless
    // FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on
    // screen, but SurfaceViews want to be always at a specific location so we don't fit it to
    // the display.
    final boolean fitToDisplay = (task == null || !inNonFullscreenContainer)
            || ((mAttrs.type != TYPE_BASE_APPLICATION) && !noLimits);
    float x, y;
    int w,h;

    if ((mAttrs.flags & FLAG_SCALED) != 0) {
        if (mAttrs.width < 0) {
            w = pw;
        } else if (mEnforceSizeCompat) {
            w = (int)(mAttrs.width * mGlobalScale + .5f);
        } else {
            w = mAttrs.width;
        }
        if (mAttrs.height < 0) {
            h = ph;
        } else if (mEnforceSizeCompat) {
            h = (int)(mAttrs.height * mGlobalScale + .5f);
        } else {
            h = mAttrs.height;
        }
    } else {
        if (mAttrs.width == MATCH_PARENT) {
            w = pw;
        } else if (mEnforceSizeCompat) {
            w = (int)(mRequestedWidth * mGlobalScale + .5f);
        } else {
            w = mRequestedWidth;
        }
        if (mAttrs.height == MATCH_PARENT) {
            h = ph;
        } else if (mEnforceSizeCompat) {
            h = (int)(mRequestedHeight * mGlobalScale + .5f);
        } else {
            h = mRequestedHeight;
        }
    }

    if (mEnforceSizeCompat) {
        x = mAttrs.x * mGlobalScale;
        y = mAttrs.y * mGlobalScale;
    } else {
        x = mAttrs.x;
        y = mAttrs.y;
    }
   //根据attr得到height,width和左上角顶点
    if (inNonFullscreenContainer && !layoutInParentFrame()) {
        // Make sure window fits in containing frame since it is in a non-fullscreen task as
        // required by {@link Gravity#apply} call.
        w = Math.min(w, pw);
        h = Math.min(h, ph);
    }

    // Set mFrame
    Gravity.apply(mAttrs.gravity, w, h, containingFrame,
            (int) (x + mAttrs.horizontalMargin * pw),
            (int) (y + mAttrs.verticalMargin * ph), mFrame); //设置WindowState的mFrame

    // Now make sure the window fits in the overall display frame.
    if (fitToDisplay) {
        Gravity.applyDisplay(mAttrs.gravity, displayFrame, mFrame);
    }

    // We need to make sure we update the CompatFrame as it is used for
    // cropping decisions, etc, on systems where we lack a decor layer.
    mCompatFrame.set(mFrame);
    if (mEnforceSizeCompat) {
        // See comparable block in computeFrameLw.
        mCompatFrame.scale(mInvGlobalScale);
    }
}

Gravity#apply
/**
 * Apply a gravity constant to an object.
 * 
 * @param gravity The desired placement of the object, as defined by the
 *                constants in this class.
 * @param w The horizontal size of the object.
 * @param h The vertical size of the object.
 * @param container The frame of the containing space, in which the object
 *                  will be placed.  Should be large enough to contain the
 *                  width and height of the object.
 * @param xAdj Offset to apply to the X axis.  If gravity is LEFT this
 *             pushes it to the right; if gravity is RIGHT it pushes it to
 *             the left; if gravity is CENTER_HORIZONTAL it pushes it to the
 *             right or left; otherwise it is ignored.
 * @param yAdj Offset to apply to the Y axis.  If gravity is TOP this pushes
 *             it down; if gravity is BOTTOM it pushes it up; if gravity is
 *             CENTER_VERTICAL it pushes it down or up; otherwise it is
 *             ignored.
 * @param outRect Receives the computed frame of the object in its
 *                container.
 */
public static void apply(int gravity, int w, int h, Rect container,
        int xAdj, int yAdj, Rect outRect) {
    switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_X_SHIFT)) {
        case 0:
            outRect.left = container.left
                    + ((container.right - container.left - w)/2) + xAdj;
            outRect.right = outRect.left + w;
            if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
                    == (AXIS_CLIP<<AXIS_X_SHIFT)) {
                if (outRect.left < container.left) {
                    outRect.left = container.left;
                }
                if (outRect.right > container.right) {
                    outRect.right = container.right;
                }
            }
            break;
        case AXIS_PULL_BEFORE<<AXIS_X_SHIFT:
            outRect.left = container.left + xAdj;
            outRect.right = outRect.left + w;
            if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
                    == (AXIS_CLIP<<AXIS_X_SHIFT)) {
                if (outRect.right > container.right) {
                    outRect.right = container.right;
                }
            }
            break;
        case AXIS_PULL_AFTER<<AXIS_X_SHIFT:
            outRect.right = container.right - xAdj;
            outRect.left = outRect.right - w;
            if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
                    == (AXIS_CLIP<<AXIS_X_SHIFT)) {
                if (outRect.left < container.left) {
                    outRect.left = container.left;
                }
            }
            break;
        default:
            outRect.left = container.left + xAdj;
            outRect.right = container.right + xAdj;
            break;
    }
    
    switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_Y_SHIFT)) {
        case 0:
            outRect.top = container.top
                    + ((container.bottom - container.top - h)/2) + yAdj;
            outRect.bottom = outRect.top + h;
            if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
                    == (AXIS_CLIP<<AXIS_Y_SHIFT)) {
                if (outRect.top < container.top) {
                    outRect.top = container.top;
                }
                if (outRect.bottom > container.bottom) {
                    outRect.bottom = container.bottom;
                }
            }
            break;
        case AXIS_PULL_BEFORE<<AXIS_Y_SHIFT:
            outRect.top = container.top + yAdj;
            outRect.bottom = outRect.top + h;
            if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
                    == (AXIS_CLIP<<AXIS_Y_SHIFT)) {
                if (outRect.bottom > container.bottom) {
                    outRect.bottom = container.bottom;
                }
            }
            break;
        case AXIS_PULL_AFTER<<AXIS_Y_SHIFT:
            outRect.bottom = container.bottom - yAdj;
            outRect.top = outRect.bottom - h;
            if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
                    == (AXIS_CLIP<<AXIS_Y_SHIFT)) {
                if (outRect.top < container.top) {
                    outRect.top = container.top;
                }
            }
            break;
        default:
            outRect.top = container.top + yAdj;
            outRect.bottom = container.bottom + yAdj;
            break;
    }
}

attr是客户端赋值完成后调用到服务端的

relayoutWindow还有createSurfaceControl的相关流程,后续再梳理;本文主要介绍了WMS端对窗口大小的计算,布局
relayoutWindow.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,444评论 4 365
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,867评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,157评论 0 248
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,312评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,673评论 3 289
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,802评论 1 223
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,010评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,743评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,470评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,696评论 2 250
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,187评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,538评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,188评论 3 240
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,127评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,902评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,889评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,741评论 2 274

推荐阅读更多精彩内容