Android Framework 窗口子系统(05)窗口布局说明

系列文章解读&说明:

Android Framework 窗口子系统 的 分析主要分为以下部分:

(01)WindowMangerService基础知识

(02)应用进程和WindowManagerService之间的关系

(03)窗口显示次序

(04)确定窗口尺寸

(05)窗口布局说明

(06)Choreographer机制

(07)窗口动画系统

本模块分享的内容:窗口布局说明

本章关键点总结 & 说明:

该图关注➕思维导图中左上:窗口布局&说明 部分即可。布局分两步:relayoutWindow和PerformLayoutAndPlaceSurfaceLocked前者主要以修改窗口/屏幕属性为主,后者为窗口/屏幕重新计算布局。放大 窗口布局 & 说明 的局部图,效果如下:

WMS窗口属性变更的流程如下:

  1. 修改窗口/屏幕属性-->WMS的relayoutWindow函数
  2. 根据窗口/屏幕属性,为所有窗口重新计算新的布局-->PerformLayoutAndPlaceSurfaceLocked函数
  3. 以动画方式将窗口移动至指定的布局位置,附加其他所需特效-->Choreographer驱动的一系列WindowAnimator

这里主要关注前面两个过程,后一个过程会在后面 动画系统中说明。

1 relayoutWindow分析

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, Configuration outConfig,
        Surface outSurface) {
    //权限检查...
    //WMS统一使用mWindowMap 所有窗口操作进行同步
    synchronized(mWindowMap) {
        //关键点1:从mWindowMap中取出需要进行relayout的WindowState对象,根据参数进行属性更新
        WindowState win = windowForClientLocked(session, client, false);
        WindowStateAnimator winAnimator = win.mWinAnimator;
        if (viewVisibility != View.GONE && (win.mRequestedWidth != requestedWidth
                || win.mRequestedHeight != requestedHeight)) {
            win.mLayoutNeeded = true;
            win.mRequestedWidth = requestedWidth;
            win.mRequestedHeight = requestedHeight;
        }
        //...
        //关键点2:根据window可见性更新或创建Surface以及启动动画效果
        if (viewVisibility == View.VISIBLE &&
                (win.mAppToken == null || !win.mAppToken.clientHidden)) {
            //可见状态
            //窗口没有surface,创建surface
            //客户端改变窗口色彩格式,则重新创建一块指定格式的surface
            //窗口未显示,客户端完成绘制,则启动一个淡入动画
        } else {
            //非可见状态
            //WindowState的mExiting属性为true;
            //窗口正在被显示,启动一个淡入动画
            //释放客户端所有surface对象,客户端无法更新窗口内容
            
        }
        
        //关键点3:更新焦点、壁纸可见性、屏幕旋转等
        //重新计算焦点,因为更新窗口的焦点,窗口的显示和退出都会有此要求
        if (focusMayChange) {
            //...
        }
        
        //输入法窗口变化,要对输入法进行更新
        if (imMayMove && (moveInputMethodWindowsIfNeededLocked(false) || toBeDisplayed)) {
           //...
        }
        
        //wallpaper变更
        if (wallpaperMayMove) {
            getDefaultDisplayContentLocked().pendingLayoutChanges |=
                    WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
        }

        final DisplayContent displayContent = win.getDisplayContent();
        if (displayContent != null) {
            //这里如果不标记,即时displayContent内容变化再多,就不会进行重新布局
            displayContent.layoutNeeded = true;
        }
        win.mGivenInsetsPending = (flags&WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
        //新窗口显示,状态发生变化,比如旋转/缩放。。。
        configChanged = updateOrientationFromAppTokensLocked(false);
        //关键点4:遍历DisplayContent所有窗口,为他们布局计算尺寸,将布局尺寸设置给它们的Surface
        performLayoutAndPlaceSurfacesLocked();
        //关键点5:返回布局结果,根据调用参数的要求完成窗口的布局
        
        outFrame.set(win.mCompatFrame);
        outOverscanInsets.set(win.mOverscanInsets);
        outContentInsets.set(win.mContentInsets);
        outVisibleInsets.set(win.mVisibleInsets);
        outStableInsets.set(win.mStableInsets);
        //...
    }
    //向AMS更新configChanged,因为屏幕可能发生旋转
    if (configChanged) {
        sendNewConfiguration();
    }
    return (inTouchMode ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0)
            | (toBeDisplayed ? WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME : 0)
            | (surfaceChanged ? WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED : 0)
            | (animating ? WindowManagerGlobal.RELAYOUT_RES_ANIMATING : 0);
}

这里对这段代码进行简单总结,如下:

  1. 根据参数更新WindowState对象相应的属性,这些属性是后续对其进行布局的依据。
  2. 处理窗口显示和退出,surface销毁和创建,以及动画相关操作
  3. 更新窗口相关其他机制,比如:焦点、输入法、壁纸、旋转
  4. 调用PerformLayoutAndPlaceSurfacesLocked函数进行布局
  5. 布局结果返回给relayoutWindow的返回者

WMS窗口的相关操作通用做法:

  1. 修改一些属性(窗口、屏幕、焦点)
  2. 标记相关DisplayContent为relayoutNeeded,调用PerformLayoutAndPlaceSurfacesLocked进行全局布局
  3. 布局进一步操作

两类窗口属性:

  1. 布局控制属性:客户端向WMS表达期望的布局、宽、高、mAttrs等
  2. 布局结果属性:客户端必须接受的属性,mFrame、mContentInsets、mVisiableInsets等

总结下窗口属性,客户端期望的是一类布局,而WMS根据实际情况给出的是另一类布局,且重新测绘后进行绘制。

2 PerformLayoutAndPlaceSurfacesLocked分析

代码实现如下:


    private final void performLayoutAndPlaceSurfacesLocked() {
        int loopCount = 6;
        do {
            mTraversalScheduled = false;
            performLayoutAndPlaceSurfacesLockedLoop();
            mH.removeMessages(H.DO_TRAVERSAL);
            loopCount--;
        } while (mTraversalScheduled && loopCount > 0);
        mInnerFields.mWallpaperActionPending = false;
    }

这里继续分析关键方法performLayoutAndPlaceSurfacesLockedLoop,代码实现如下:

private final void performLayoutAndPlaceSurfacesLockedLoop() {
    //...
    mInLayout = true;
    //删除所有"僵尸"窗口,类似android GC机制
    boolean recoveringMemory = false;
    try {
        if (mForceRemoves != null) {
            //这里标记此次回收已经完成内存回收,布局再发生surface操作异常,则不再回收
            recoveringMemory = true;
            // Wait a little bit for things to settle down, and off we go.
            for (int i=0; i<mForceRemoves.size(); i++) {
                WindowState ws = mForceRemoves.get(i);
                removeWindowInnerLocked(ws.mSession, ws);
            }
            mForceRemoves = null;
            Object tmp = new Object();
            synchronized (tmp) {
                try {
                    tmp.wait(250);
                } catch (InterruptedException e) {
                }
            }
        }
    } catch (RuntimeException e) {
        Slog.wtf(TAG, "Unhandled exception while force removing for memory", e);
    }

    try {
        performLayoutAndPlaceSurfacesLockedInner(recoveringMemory);
        mInLayout = false;

        if (needsLayout()) {
            //这里调用了requestTraversalLocked
            if (++mLayoutRepeatCount < 6) {
                requestTraversalLocked();
            } else {
                Slog.e(TAG, "Performed 6 layouts in a row. Skipping");
                mLayoutRepeatCount = 0;
            }
        } else {
            mLayoutRepeatCount = 0;
        }
        //通知监听者,布局发生了变化,目前 监听者只有ViewServer
        if (mWindowsChanged && !mWindowChangeListeners.isEmpty()) {
            mH.removeMessages(H.REPORT_WINDOWS_CHANGE);
            mH.sendEmptyMessage(H.REPORT_WINDOWS_CHANGE);
        }
    } catch (RuntimeException e) {
        mInLayout = false;
        Slog.wtf(TAG, "Unhandled exception while laying out windows", e);
    }
}

这里主要关注requestTraversalLocked的两个调用条件:

  1. needsLayout()返回值:遍历所有DisplayContent并检查他们的mLayoutNeeded字段,只要有一个需要重新布局就会返回true。
  2. mLayoutRepeatCount < 6:最多连续重新布局6次(说明有可能6次也无法完成,防止过度等待,加了6次的限制)

继续分析performLayoutAndPlaceSurfacesLockedInner,由于代码太长,这里采用分段分析,整体流程如下:

  1. 布局前预处理
  2. 遍历所有DisplayContent的所有窗口并布局,检查布局结果,确认是否有必要重新布局,对DisplayContent布局后处理
  3. 完成布局后的策略处理

2.1 优先分析 布局前的预处理

布局前代码如下:

    private final void performLayoutAndPlaceSurfacesLockedInner(boolean recoveringMemory) {
//第一阶段,start 布局前处理
        //...
        //更新处于焦点状态的窗口
        if (mFocusMayChange) {
            mFocusMayChange = false;
            updateInputWindowsNeeded = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
                    false /*updateInputWindows*/);
        }

        //处于退出状态的tokens标记为不可见
        final int numDisplays = mDisplayContents.size();
        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
            final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
            for (i=displayContent.mExitingTokens.size()-1; i>=0; i--) {
                displayContent.mExitingTokens.get(i).hasVisible = false;
            }
        }

        for (int stackNdx = mStackIdToStack.size() - 1; stackNdx >= 0; --stackNdx) {
            // Initialize state of exiting applications.
            final AppTokenList exitingAppTokens =
                    mStackIdToStack.valueAt(stackNdx).mExitingAppTokens;
            for (int tokenNdx = exitingAppTokens.size() - 1; tokenNdx >= 0; --tokenNdx) {
                exitingAppTokens.get(tokenNdx).hasVisible = false;
            }
        }
        //初始化mInnerFields成员变量
        mInnerFields.mHoldScreen = null;    //保存了要求保持屏幕唤醒状态的窗口所在进程
        mInnerFields.mScreenBrightness = -1;//完成布局后的背光亮度
        mInnerFields.mButtonBrightness = -1;//完成布局后的键盘背光亮度
        mInnerFields.mUserActivityTimeout = -1;//ms为单位,在此窗口上发生的ANR超时
        mInnerFields.mObscureApplicationContentOnSecondaryDisplays = false;

        //递增布局序列号
        mTransactionSequence++;

        final DisplayContent defaultDisplay = getDefaultDisplayContentLocked();
        final DisplayInfo defaultInfo = defaultDisplay.getDisplayInfo();
        final int defaultDw = defaultInfo.logicalWidth;
        final int defaultDh = defaultInfo.logicalHeight;

        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
        SurfaceControl.openTransaction();
        try {
            //布局水印和strictMode警告框
            if (mWatermark != null) {
                mWatermark.positionSurface(defaultDw, defaultDh);
            }
            if (mStrictModeFlash != null) {
                mStrictModeFlash.positionSurface(defaultDw, defaultDh);
            }
            if (mCircularDisplayMask != null) {
                mCircularDisplayMask.positionSurface(defaultDw, defaultDh, mRotation);
            }
            if (mEmulatorDisplayOverlay != null) {
                mEmulatorDisplayOverlay.positionSurface(defaultDw, defaultDh, mRotation);
            }

            boolean focusDisplayed = false;
//第一阶段 end

总结下布局前的内容,如下:

  1. 计算焦点,客户端relayout窗口可见性发生变化,则重新计算焦点窗口。
  2. 初始化mInnerFields成员变量
  3. 递增布局序列号,每次布局导致序号递增,AppWindowToken也保存了序号,布局中WMS对比两个序号的值以确定AppWindowToken是否为最新
  4. 布局水印和strictMode警告框:在屏幕上显示一段固定信息,strictMode警告框在任何一个服务/应用违规操作时在屏幕上闪烁一个红色方框而已。

2.2 这里分为两个部分进行分析,布局DisplayContent和布局后处理

2.2.1 布局DisplayContent

//第二阶段,2-1,start 布局DisplayContent
            for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
                final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx);
                boolean updateAllDrawn = false;
				//windows列表中保存了DisplayContent所拥有的所有窗口
                WindowList windows = displayContent.getWindowList();
                DisplayInfo displayInfo = displayContent.getDisplayInfo();
                final int displayId = displayContent.getDisplayId();
				//这里dw、dh表示逻辑尺寸,不同于物理尺寸;当分辨率低时,则获取模拟显示屏尺寸
                final int dw = displayInfo.logicalWidth;
                final int dh = displayInfo.logicalHeight;
				//用于当前显示屏用于显示应用程序的区域尺寸,结果为{逻辑尺寸-系统装饰尺寸}
                final int innerDw = displayInfo.appWidth;
                final int innerDh = displayInfo.appHeight;
                final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);

                // Reset for each display.
                mInnerFields.mDisplayHasContent = false;
                mInnerFields.mPreferredRefreshRate = 0;

                int repeats = 0;
				//关键点1:循环处理,通过设置displayContent.pendingLayoutChanges决定是否重新布局
                do {
                    repeats++;
                    if (repeats > 6) {
                        Slog.w(TAG, "Animation repeat aborted after too many iterations");
                        displayContent.layoutNeeded = false;
                        break;
                    }

                    if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("On entry to LockedInner",
                        displayContent.pendingLayoutChanges);

                    if ((displayContent.pendingLayoutChanges &
                            WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0 &&
                            (adjustWallpaperWindowsLocked() &
                                    ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) {
                        assignLayersLocked(windows);
                        displayContent.layoutNeeded = true;
                    }

                    if (isDefaultDisplay && (displayContent.pendingLayoutChanges
                            & WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) {
                        if (DEBUG_LAYOUT) Slog.v(TAG, "Computing new config from layout");
                        if (updateOrientationFromAppTokensLocked(true)) {
                            displayContent.layoutNeeded = true;
                            mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
                        }
                    }

                    if ((displayContent.pendingLayoutChanges
                            & WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) {
                        displayContent.layoutNeeded = true;
                    }

                    // FIRST LOOP: Perform a layout, if needed.
                    if (repeats < 4) {
                        performLayoutLockedInner(displayContent, repeats == 1,
                                false /*updateInputWindows*/);
                    } else {
                        Slog.w(TAG, "Layout repeat skipped after too many iterations");
                    }

					//清空字段displayContent.pendingLayoutChanges
                    displayContent.pendingLayoutChanges = 0;
					//由PhoneWindowManager来检查布局,结果存放到pendingLayoutChanges中从而决定是否重新布局
                    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);
//第二阶段,2-1 end

@1 这里继续分析performLayoutLockedInner,该方法会对DisplayContent下的所有窗口进行布局,代码实现如下:

private final void performLayoutLockedInner(final DisplayContent displayContent,
boolean initial, boolean updateInputWindows) {
    displayContent.layoutNeeded = false;
	//获取displayContent的所拥有的所有窗口
    WindowList windows = displayContent.getWindowList();
	//...
	//通知WindowPolicyManager,使其为即将开始的布局做准备
    mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mRotation);
	//...
	//对所有顶级窗口进行布局
    for (i = N-1; i >= 0; i--) {
        final WindowState win = windows.get(i);
        final boolean gone = (behindDream && mPolicy.canBeForceHidden(win, win.mAttrs))
                || win.isGoneForLayoutLw();
        if (...) {
            if (!win.mLayoutAttached) {
                //...
                win.mLayoutNeeded = false;
                win.prelayout();
				//对窗口进行布局
                mPolicy.layoutWindowLw(win, null);
                win.mLayoutSeq = seq;
            } else {
                if (topAttached < 0) topAttached = i;
            }
        }
		//...
    }
	//...
	//对所有非顶级窗口进行布局
    for (i = topAttached; i >= 0; i--) {
        final WindowState win = windows.get(i);

        if (...) {
            //...
            if (...) {
                //...
                win.mLayoutNeeded = false;
                win.prelayout();
				//对窗口进行布局
                mPolicy.layoutWindowLw(win, win.mAttachedWindow);
                win.mLayoutSeq = seq;
            }
        } else if (win.mAttrs.type == TYPE_DREAM) {
            attachedBehindDream = behindDream;
        }
    }
	//...
	//通知PolicyManager,本次布局结束
    mPolicy.finishLayoutLw();
}

该部分流程总结如下:

mPolicy.beginLayoutLw,布局前期准备,可见性,旋转,尺寸,等因素来计算布局所使用的参数

mPolicy.layoutWindowLw,对所有顶级窗口进行布局,使用布局参数计算出窗口位置属性,保存在WindowState中

mPolicy.layoutWindowLw,对所有非顶级窗口即子窗口进行布局,同上

mPolicy.finishLayoutLw,通知WindowManagerPolicy,本次布局结束

说明:这里从mPolicy.beginLayoutLw开始计算布局,而这就是上一章节 计算布局中的主要内容,窗口是如何计算出来的

最后整理下performLayoutLockedInner的流程图,如下所示:

最后总结下窗口布局的演算过程:根据DisplayContent属性,状态栏、导航栏、输入法窗口的状态,确定8个布局区域。根据每个窗口的状态,从8个布局区域中选出4个布局参数pf、df、cf、vf,根据这4个参数计算出布局结果并保存在WindowState中。

@2 检查布局结果

窗口的flag影响了系统窗口、锁屏可见性。。。窗口布局过程,然而flag的生效条件是要求窗口能够覆盖整个屏幕,窗口布局也影响了flag的有效性,因此WMS引入了PendingLayoutChanges机制。

PendingLayoutChanges机制:

  1. 基于布局使flag生效
  2. 检查布局变化,记录额外工作,完成后重新布局
  3. 再检查
  4. 如此反复,直到布局不变

关注performLayoutAndPlaceSurfacesLockedInner第二阶段布局DisplayContent部分的三个方法,截取部分代码如下:


//由PhoneWindowManager来检查布局,结果存放到pendingLayoutChanges中从而决定是否重新布局
                    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);
                    }

这里关注三个核心方法,分析如下:

@@2.1 mPolicy.beginPostLayoutPolicyLw(dw, dh);

用于初始化检查所需的状态变量,代码如下:

    public void beginPostLayoutPolicyLw(int displayWidth, int displayHeight) {
        mTopFullscreenOpaqueWindowState = null;
        mAppsToBeHidden.clear();
        mAppsThatDismissKeyguard.clear();
        mForceStatusBar = false;
        mForceStatusBarFromKeyguard = false;
        mForcingShowNavBar = false;
        mForcingShowNavBarLayer = -1;

        mHideLockScreen = false;
        mAllowLockscreenWhenOn = false;
        mDismissKeyguard = DISMISS_KEYGUARD_NONE;
        mShowingLockscreen = false;
        mShowingDream = false;
        mWinShowWhenLocked = null;
        mKeyguardSecure = isKeyguardSecure();
        mKeyguardSecureIncludingHidden = mKeyguardSecure
                && (mKeyguardDelegate != null && mKeyguardDelegate.isShowing());
    }

这里主要是初始化一些变量。

@@2.2 mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs, w.mAttachedWindow);

用于记录可能影响到系统窗口或锁屏界面可见性的窗口,代码如下:

    public void applyPostLayoutPolicyLw(WindowState win, WindowManager.LayoutParams attrs,
            WindowState attached) {
        final int fl = PolicyControl.getWindowFlags(win, attrs);
        if (mTopFullscreenOpaqueWindowState == null
                && win.isVisibleLw() && attrs.type == TYPE_INPUT_METHOD) {
            mForcingShowNavBar = true;
            mForcingShowNavBarLayer = win.getSurfaceLayer();
        }
        if (attrs.type == TYPE_STATUS_BAR && (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
            mForceStatusBarFromKeyguard = true;
        }
        if (mTopFullscreenOpaqueWindowState == null &&
                win.isVisibleOrBehindKeyguardLw() && !win.isGoneForLayoutLw()) {
            if ((fl & FLAG_FORCE_NOT_FULLSCREEN) != 0) {
                if ((attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
                    mForceStatusBarFromKeyguard = true;
                } else {
                    mForceStatusBar = true;
                }
            }
            if ((attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
                mShowingLockscreen = true;
            }
            boolean appWindow = attrs.type >= FIRST_APPLICATION_WINDOW
                    && attrs.type < FIRST_SYSTEM_WINDOW;
            if (attrs.type == TYPE_DREAM) {
                // If the lockscreen was showing when the dream started then wait
                // for the dream to draw before hiding the lockscreen.
                if (!mDreamingLockscreen
                        || (win.isVisibleLw() && win.hasDrawnLw())) {
                    mShowingDream = true;
                    appWindow = true;
                }
            }

            final boolean showWhenLocked = (fl & FLAG_SHOW_WHEN_LOCKED) != 0;
            final boolean dismissKeyguard = (fl & FLAG_DISMISS_KEYGUARD) != 0;
            final IApplicationToken appToken = win.getAppToken();

            // For app windows that are not attached, we decide if all windows in the app they
            // represent should be hidden or if we should hide the lockscreen. For attached app
            // windows we defer the decision to the window it is attached to.
            if (appWindow && attached == null) {
                if (showWhenLocked) {
                    // Remove any previous windows with the same appToken.
                    mAppsToBeHidden.remove(appToken);
                    mAppsThatDismissKeyguard.remove(appToken);
                    if (mAppsToBeHidden.isEmpty()) {
                        if (dismissKeyguard && !mKeyguardSecure) {
                            mAppsThatDismissKeyguard.add(appToken);
                        } else {
                            mWinShowWhenLocked = win;
                            mHideLockScreen = true;
                            mForceStatusBarFromKeyguard = false;
                        }
                    }
                } else if (dismissKeyguard) {
                    if (mKeyguardSecure) {
                        mAppsToBeHidden.add(appToken);
                    } else {
                        mAppsToBeHidden.remove(appToken);
                    }
                    mAppsThatDismissKeyguard.add(appToken);
                } else {
                    mAppsToBeHidden.add(appToken);
                }
                if (attrs.x == 0 && attrs.y == 0
                        && attrs.width == WindowManager.LayoutParams.MATCH_PARENT
                        && attrs.height == WindowManager.LayoutParams.MATCH_PARENT) {
                    if (DEBUG_LAYOUT) Slog.v(TAG, "Fullscreen window: " + win);
                    mTopFullscreenOpaqueWindowState = win;
                    if (!mAppsThatDismissKeyguard.isEmpty() &&
                            mDismissKeyguard == DISMISS_KEYGUARD_NONE) {
                        mDismissKeyguard = mWinDismissingKeyguard == win ?
                                DISMISS_KEYGUARD_CONTINUE : DISMISS_KEYGUARD_START;
                        mWinDismissingKeyguard = win;
                        mForceStatusBarFromKeyguard = mShowingLockscreen && mKeyguardSecure;
                    } else if (mAppsToBeHidden.isEmpty() && showWhenLocked) {
                        if (DEBUG_LAYOUT) Slog.v(TAG,
                                "Setting mHideLockScreen to true by win " + win);
                        mHideLockScreen = true;
                        mForceStatusBarFromKeyguard = false;
                    }
                    if ((fl & FLAG_ALLOW_LOCK_WHILE_SCREEN_ON) != 0) {
                        mAllowLockscreenWhenOn = true;
                    }
                }

                if (mWinShowWhenLocked != null &&
                        mWinShowWhenLocked.getAppToken() != win.getAppToken()) {
                    win.hideLw(false);
                }
            }
        }
    }

这里主要对上一步的参数进行设置。这里关注几个关键参数,如下:

  1. mTopFullscreenOpaqueWindowState:保存第一个满屏窗口,会导致系统窗口被隐藏
  2. mForceStatusBarFromKeyguard和mForceStatusBar:强制显示系统窗口
  3. mHideLockScreen:要求隐藏锁屏界面
  4. mDismissKeyguard:是否执行解锁

@@2.3 mPolicy.finishPostLayoutPolicyLw();

用于修改系统窗口和锁屏界面的可变性,代码如下:

public int finishPostLayoutPolicyLw() {
    mTopFullscreenOpaqueWindowState处理
    int changes = 0;
    boolean topIsFullscreen = false;

    final WindowManager.LayoutParams lp = (mTopFullscreenOpaqueWindowState != null)
            ? mTopFullscreenOpaqueWindowState.getAttrs()
            : null;

    if (!mShowingDream) {
        mDreamingLockscreen = mShowingLockscreen;
    }

	//StatusBar处理...
    if (mTopIsFullscreen != topIsFullscreen) {
        if (!topIsFullscreen) {
            // Force another layout when status bar becomes fully shown.
            changes |= FINISH_LAYOUT_REDO_LAYOUT;
        }
        mTopIsFullscreen = topIsFullscreen;
    }
	//mDismissKeyguard处理...

    if ((updateSystemUiVisibilityLw()&SYSTEM_UI_CHANGING_LAYOUT) != 0) {
        changes |= FINISH_LAYOUT_REDO_LAYOUT;
    }

    // update since mAllowLockscreenWhenOn might have changed
    updateLockScreenTimeout();
    return changes;
}

finishPostLayoutPolicyLw的目的主要是调整系统窗口和锁屏界面的可见性,以及确定PendingLayoutChanges的值。

@3 处理PendingLayoutChanges

布局完成、布局检查也完成、PendingLayoutChanges设置为合适的值,回看performLayoutAndPlaceSurfacesLockedInner布局循环中处理PendingLayoutChanges的流程,代码如下:

do {
	repeats++;
	if (repeats > 6) {
		displayContent.layoutNeeded = false;
		break;
	}
	//通过调用adjustWallpaperWindowsLocked调整壁纸窗口的属性,以及重分显示层级
	if ((displayContent.pendingLayoutChanges &
			WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER) != 0 &&
			(adjustWallpaperWindowsLocked() &
					ADJUST_WALLPAPER_LAYERS_CHANGED) != 0) {
		assignLayersLocked(windows);
		displayContent.layoutNeeded = true;
	}
	//根据最顶层要求,更新屏幕旋转方向
	if (isDefaultDisplay && (displayContent.pendingLayoutChanges
			& WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) {
		if (updateOrientationFromAppTokensLocked(true)) {
			displayContent.layoutNeeded = true;
			mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
		}
	}
	
	if ((displayContent.pendingLayoutChanges
			& WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) {
		displayContent.layoutNeeded = true;
	}
	
	//强制performLayoutLockedInner对所有窗口布局
	if (repeats < 4) {
		performLayoutLockedInner(displayContent, repeats == 1,
				false /*updateInputWindows*/);
	}
	
	// 清除pendingLayoutChanges字段
	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();
	}
} while (displayContent.pendingLayoutChanges != 0);

至此,循环部分的工作原理分析结束。

经过布局处理后,窗口的位置和尺寸都确定,但surface未更新,即窗口并未放置到布局过程中指定的位置。

2.2.2 布局后处理

布局后处理主要是为对应的surface设置尺寸和大小,附加一些动画效果。布局后的处理代码如下所示:

//第二阶段,2-1,start,displayContent布局后处理
根据布局结果设置surface参数,应用一些动画效果,侧重于placeSurfaces
                mInnerFields.mObscured = false;
                mInnerFields.mSyswin = false;//设置窗口遮挡状态
                displayContent.resetDimming();

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

                //遍历所有窗口,windows列表同时描述了显示顺序
                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, currentTime, innerDw, innerDh);
                    }
                    //...
                    // Moved from updateWindowsAndWallpaperLocked().
                    if (w.mHasSurface && !w.isHiddenFromUserLocked()) {
                        // Take care of the window being ready to display.
                        //若当前客户端完成Surface的绘制但尚未被显示,则向动画系统提交绘制完毕通知
                        final boolean committed =
                                winAnimator.commitFinishDrawingLocked(currentTime);
                        if (isDefaultDisplay && committed) {
                            if (w.mAttrs.type == TYPE_DREAM) {
                                displayContent.pendingLayoutChanges |=
                                        WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
                            }
                            if ((w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
                                mInnerFields.mWallpaperMayChange = true;
                                displayContent.pendingLayoutChanges |=
                                        WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
                            }
                        }
                        //更新窗口surface位置和尺寸
                        winAnimator.setSurfaceBoundariesLocked(recoveringMemory);
                        //...
                    }

                    if (isDefaultDisplay && someoneLosingFocus && (w == mCurrentFocus)
                            && w.isDisplayedLw()) {
                        focusDisplayed = true;
                    }
                    //将位置尺寸、ContentInsets或VisibleInsets布局中发生变化的窗口添加到mResizingWindow列表中
                    updateResizingWindows(w);
                }
                //...
            }

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

        } catch (RuntimeException e) {
            Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
        } finally {
            SurfaceControl.closeTransaction();
        }
//第二阶段,2-2 end
        //完成布局后的策略处理...

特殊说明:

  1. 设置surface的尺寸和位置,位置的变化是有动画效果的,出于性能考虑,尺寸的变化没有动画效果。
  2. 如果窗口客户端完成了surface的绘制工作,则显示这个窗口。

总结下流程,DisplayContent的布局分为两个阶段:

  1. 以窗口布局计算为主要任务的布局循环,计算出窗口最终位置
  2. 根据布局计算结果设置surface的尺寸和大小,附加动画效果

DisplayContent的布局就是窗口的布局,窗口的布局实际上就是以窗口布局计算为核心的布局。至此DisplayContent的布局就完成了,所有窗口都准备好了。

猜你喜欢

转载自blog.csdn.net/vviccc/article/details/94563384