Android 7.1 GUI系统-窗口管理WMS-窗口大小计算(五)

来源:互联网 发布:行知小学 编辑:程序博客网 时间:2024/06/06 03:55

窗口大小的计算

一个应用窗口,除了应用程序本身的内容外,还有状态栏,可能还有输入法窗口,状态栏的大小是固定的,输入法窗口可以在AndroidManifest.xml中配置,相关属性如下:

state开头的表示当Activity成为焦点时软键盘是隐藏还是可见,以adjust开头的表示如何调整Activity窗口以容纳软键盘。

frameworks/base/core/res/res/values/Attrs.xml<!-- Defines the default soft input state that this window would             like when it is displayed.  Corresponds             to {@link android.view.WindowManager.LayoutParams#softInputMode}. -->        <attr name="windowSoftInputMode">            <!-- Not specified, use what the system thinks is best.  This                 is the default. -->            <flag name="stateUnspecified" value="0" />            <!-- Leave the soft input window as-is, in whatever state it                 last was. -->            <flag name="stateUnchanged" value="1" />            <!-- Make the soft input area hidden when normally appropriate                 (when the user is navigating forward to your window). -->            <flag name="stateHidden" value="2" />            <!-- Always make the soft input area hidden when this window                 has input focus. -->            <flag name="stateAlwaysHidden" value="3" />            <!-- Make the soft input area visible when normally appropriate                 (when the user is navigating forward to your window). -->            <flag name="stateVisible" value="4" />            <!-- Always make the soft input area visible when this window                 has input focus. -->            <flag name="stateAlwaysVisible" value="5" />            <!-- The window resize/pan adjustment has not been specified,                 the system will automatically select between resize and pan                 modes, depending                 on whether the content of the window has any layout views                 that can scroll their contents.  If there is such a view,                 then the window will be resized, with the assumption being                 that the resizeable area can be reduced to make room for                 the input UI. -->            <flag name="adjustUnspecified" value="0x00" />            <!-- Always resize the window: the content area of the window is                 reduced to make room for the soft input area. -->            <flag name="adjustResize" value="0x10" />            <!-- Don't resize the window to make room for the soft input area;                 instead pan the contents of the window as focus moves inside                 of it so that the user can see what they are typing.  This is                 generally less desireable than panning because the user may                 need to close the input area to get at and interact with                 parts of the window. -->            <flag name="adjustPan" value="0x20" />            <!-- Don't resize <em>or</em> pan the window to make room for the                 soft input area; the window is never adjusted for it. -->            <flag name="adjustNothing" value="0x30" />        </attr>

ViewRootImpl中的performTraversals方法,是计算应用窗口大小的起点,在程序运行过程中这个方法会被调用多次。

如果是第一次(mFirst==true)执行遍历,或者窗口内容有变化会调用dispatchApplyInsets,然后会调用到ViewonApplyWindowInsets,进一步调用到fitSystemWindows(Rectinsets),来通知一个窗口的insets发生了改变,让View有机会适应最新的变化,他会沿着ViewTree从上到下传递给各个View对象。通常不需要处理这个函数,但是如果使用SYSTEM_UI_FLAG_LAYOUT_FULLSCREENSYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION这两个属性除外,因为应用窗口的内容可能会在系统元素之下。

如果mFirst==false,不是第一次调用这个函数(performTraversals),那么:desiredWindowWidth= frame.width();desiredWindowHeight =frame.height();这两个值是期望的宽高,和最终的值可能有差异。frame也就是mWinFrame它是用于记录WMS提供的宽高,当Activity的窗口大小需要改变时,WMS会通过dispatchResized,调用Iwindowresized函数(也就是ViewRootImpl.java的内部类Wresized函数)。如果(desiredWindowWidth!= mWidth || desiredWindowHeight !=mHeight),就是说WMS的期望值跟当前真实的宽高(mWidthmHeight),那么变量mFullRedrawNeeded= true;mLayoutRequested = true;windowSizeMayChange =true;这三个变量在后面的流程中可能触发invalidate,或者触发layout请求。

performTraversals继续往下走,如果(mFirst|| windowShouldResize || insetsChanged ||viewVisibilityChanged ||params != null || mForceNextWindowRelayout)条件满足,说明窗口属性发生了变化,会通过调用relayoutWindow,进一步调用mWindowSession.relayout,请求WMS计算窗口属性,WMS计算的计算的结果将通过relayout的参数带回。这些参数包括:mWinFrameWMS得出的应用窗口的大小,对应WMSrelayoutWindow中的outFrame

mPendingContentInsetsWMS得出的contentinsets,对应WMSrelayoutWindow中的outContentInsets

mPendingVisibleInsetsWMS得出的visibleinsets,对应WMSrelayoutWindow中的outVisibleInsets

mPendingVisibleInsetsWMS得出的visibleinsets,对应WMSrelayoutWindow中的outVisibleInsets

mPendingConfigurationWMS得出的visibleinsets,对应WMSrelayoutWindow中的outConfig

mPendingConfigurationWMS得出的visibleinsets,对应WMSrelayoutWindow中的outConfig

mSurfaceWMS申请的有效的Surface对象,对应WMSrelayoutWindow中的outSurface

WMSrelayoutWindow中的这些参数都是out开头的,表明他们是出参。


WMSrelayoutWindow有关窗口大小的计算,调用流程会走到WindowSurfacePlacer.java中的applySurfaceChangesTransaction


private void applySurfaceChangesTransaction(boolean recoveringMemory, int numDisplays,            int defaultDw, int defaultDh) @WindowSurfacePlacer.java{int repeats = 0;do {repeats++;//循环6次退出。if (repeats > 6) {displayContent.layoutNeeded = false;break;}//重新计算config。if (isDefaultDisplay&& (displayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {displayContent.layoutNeeded = true;mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);}//重新layout。if ((displayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_LAYOUT) != 0) {displayContent.layoutNeeded = true;}//循环小于4次,每次都会调用这个函数,窗口大小计算的关键函数。if (repeats < LAYOUT_REPEAT_THRESHOLD) {performLayoutLockedInner(displayContent, repeats == 1,false /* updateInputWindows */);}//对每个窗口应用窗口策略。WindowState w = windows.get(i);if (w.mHasSurface) {mService.mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs, w.mAttachedWindow);}// finishPostLayoutPolicyLw返回值赋给displayContent.pendingLayoutChanges ,会影响到是退出while循环,还是根据pendingLayoutChanges 的值再次执行layout,或者重新计算configuration。displayContent.pendingLayoutChanges |=mService.mPolicy.finishPostLayoutPolicyLw();//这个while循环退出的另一个条件是 displayContent.pendingLayoutChanges ==0,}while (displayContent.pendingLayoutChanges != 0);}

看下displayContent.pendingLayoutChanges的值都有那些情况,有注释不再翻译了:

WindowManagerPolicy.java

/** Layout state may havechanged (so another layout will be performed) */

static final intFINISH_LAYOUT_REDO_LAYOUT = 0x0001;

/** Configuration statemay have changed */

static final intFINISH_LAYOUT_REDO_CONFIG = 0x0002;

/** Wallpaper may need tomove */

static final intFINISH_LAYOUT_REDO_WALLPAPER = 0x0004;

/** Need to recomputeanimations */

static final intFINISH_LAYOUT_REDO_ANIM = 0x0008;


再次执行layout的核心代码是performLayoutLockedInner

final void performLayoutLockedInner(final DisplayContent displayContent,            boolean initial, boolean updateInputWindows) @WindowSurfacePlacer.java{//所有的窗口列表。WindowList windows = displayContent.getWindowList();//显示屏相关的信息。boolean isDefaultDisplay = displayContent.isDefaultDisplay;DisplayInfo displayInfo = displayContent.getDisplayInfo();final int dw = displayInfo.logicalWidth;final int dh = displayInfo.logicalHeight;final int N = windows.size();//循环处理每个窗口前,对显示屏相关的变量进行初始化。mService.mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mService.mRotation,mService.mCurConfiguration.uiMode);//首先执行layout的是根窗口(没有附加到别的窗口的那些窗口)。int topAttached = -1;for (i = N-1; i >= 0; i--) {final WindowState win = windows.get(i);//如果一个窗口不可见,就不用浪费时间去计算了。final boolean gone = (behindDream && mService.mPolicy.canBeForceHidden(win, win.mAttrs))                    || win.isGoneForLayoutLw();// gone表示这个窗口是不是还存在, mHaveFrame表示窗口是否已经执行过computeFrameLw,if (!gone || !win.mHaveFrame || win.mLayoutNeeded                    || ((win.isConfigChanged() || win.setReportResizeHints())                            && !win.isGoneForLayoutLw() &&                            ((win.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||                            (win.mHasSurface && win.mAppToken != null &&                            win.mAppToken.layoutConfigChanges)))) {if (!win.mLayoutAttached) {//win.mLayoutAttached 为false是跟窗口,执行具体的计算。win.prelayout();mService.mPolicy.layoutWindowLw(win, null);win.mLayoutSeq = seq;}else{//这里是子窗口的情况。if (topAttached < 0) topAttached = i;}}}//从上一步计算的结果topAttached 开始,第一个有 attached窗口的情况,计算它的父窗口。for (i = topAttached; i >= 0; i--) {final WindowState win = windows.get(i);if (win.mLayoutAttached) {mService.mPolicy.layoutWindowLw(win, win.mAttachedWindow);}}//结束layout。mService.mPolicy.finishLayoutLw();}


窗口的计算过程是:

这三个方法跟具体的窗口有关,手机默认的窗口策略是PhoneWindowManager.java

mService.mPolicy.beginLayoutLw(….);

mService.mPolicy.layoutWindowLw(win,win.mAttachedWindow);

mService.mPolicy.finishLayoutLw();


beginLayoutLw会做跟窗口大小相关的变量的初始化。比如:

PhoneWindowManager.java

public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,                              int displayRotation, int uiMode) @PhoneWindowManager.java{//overscan是过扫描,根据屏幕的角度赋值。有些显示屏可能存在失真现象,且越靠近边缘越严重,为了避开这个问题,厂商可能把扫描调整到画面的5%-10%,这样造成的结果是画面可能显示不全,损失这部分成为overscan。final int overscanLeft, overscanTop, overscanRight, overscanBottom;//屏幕的真实大小,包含了overscan区域。mOverscanScreenLeft = mRestrictedOverscanScreenLeft = 0;mOverscanScreenTop = mRestrictedOverscanScreenTop = 0;mOverscanScreenWidth = mRestrictedOverscanScreenWidth = displayWidth;mOverscanScreenHeight = mRestrictedOverscanScreenHeight = displayHeight;//所有可见的系统UI元素区域。        mSystemLeft = 0;        mSystemTop = 0;        mSystemRight = displayWidth;        mSystemBottom = displayHeight;//屏幕的真实大小,不管状态栏是否可见,不包含overscan区域。        mUnrestrictedScreenLeft = overscanLeft;        mUnrestrictedScreenTop = overscanTop;        mUnrestrictedScreenWidth = displayWidth - overscanLeft - overscanRight;        mUnrestrictedScreenHeight = displayHeight - overscanTop – overscanBottom;//跟mOverscanScreen类似,适当的时候可以移动到overscan区域。    int mRestrictedOverscanScreenLeft, mRestrictedOverscanScreenTop;    int mRestrictedOverscanScreenWidth, mRestrictedOverscanScreenHeight;//屏幕大小,如果状态栏不隐藏,他和其他的变量是不同的。        mRestrictedScreenLeft = mUnrestrictedScreenLeft;        mRestrictedScreenTop = mUnrestrictedScreenTop;        mRestrictedScreenWidth = mSystemGestures.screenWidth = mUnrestrictedScreenWidth;        mRestrictedScreenHeight = mSystemGestures.screenHeight = mUnrestrictedScreenHeight;//包括状态栏、输入法窗口在内的外围尺寸。int mCurLeft, mCurTop, mCurRight, mCurBottom;//内容区域。int mContentLeft, mContentTop, mContentRight, mContentBottom;//输入法窗口区域。        mDockLeft = mContentLeft = mVoiceContentLeft = mStableLeft = mStableFullscreenLeft                = mCurLeft = mUnrestrictedScreenLeft;        mDockTop = mContentTop = mVoiceContentTop = mStableTop = mStableFullscreenTop                = mCurTop = mUnrestrictedScreenTop;        mDockRight = mContentRight = mVoiceContentRight = mStableRight = mStableFullscreenRight                = mCurRight = displayWidth - overscanRight;        mDockBottom = mContentBottom = mVoiceContentBottom = mStableBottom = mStableFullscreenBottom                = mCurBottom = displayHeight – overscanBottom;//计算导航栏的大小。boolean updateSysUiVisibility = layoutNavigationBar(displayWidth, displayHeight,                    displayRotation, uiMode, overscanLeft, overscanRight, overscanBottom, dcf, navVisible, navTranslucent,                    navAllowedHidden, statusBarExpandedNotKeyguard);//计算状态栏的大小。          updateSysUiVisibility |= layoutStatusBar(pf, df, of, vf, dcf, sysui, isKeyguardShowing);            if (updateSysUiVisibility) {                updateSystemUiVisibilityLw();            }}

初始化后,根据具体的配置执行layoutWindowLw,计算父窗口大小,屏幕大小,可见区域,内容区域,overscan区域,decor区域等。

public void layoutWindowLw(WindowState win, WindowState attached) @PhoneWindowManager.java{//在beginLayout时已经处理过导航栏、状态栏,如果状态栏收到input事件,就需要再次layout以适应输入法窗口。if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar) {return;}final WindowManager.LayoutParams attrs = win.getAttrs();//窗口属性。final int fl = PolicyControl.getWindowFlags(win, attrs);final int sysUiFl = PolicyControl.getSystemUiVisibility(win, null);//主要计算就是下面这几个变量。        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;//是否有导航条,手机底部有物理按键的,通常就不用导航条了。final boolean hasNavBar = (isDefaultDisplay && mHasNavigationBar                && mNavigationBar != null && mNavigationBar.isVisibleLw());只关注应用窗口的计算,其他窗口如输入法、状态栏、wallpaper等略过。final boolean isAppWindow =                    attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW &&                    attrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;if (isAppWindow && !inheritTranslucentDecor && !topAtRest) {//设置了全屏,确保decorView包含状态栏。 if ((sysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0                        && (fl & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0                        && (fl & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) == 0                        && (fl & WindowManager.LayoutParams.                                FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0                        && (pfl & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) == 0) {                    // Ensure policy decor includes status bar                    dcf.top = mStableTop;                }//设置了导航栏透明,确保DecorView包含导航栏。if ((fl & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) == 0                        && (sysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0                        && (fl & WindowManager.LayoutParams.                                FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {                    // Ensure policy decor includes navigation bar                    dcf.bottom = mStableBottom;                    dcf.right = mStableRight;                }}//这是常见的Activity窗口的情况,想要覆盖所有的屏幕空间,要移动窗口的内容以适应屏幕装饰条在内的系统元素。if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR))                    == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {//如果是子窗口,各个变量的值跟其父窗口一致if (attached != null) {setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf);}else{ //非子窗口的情况//状态栏的下拉面板,这是唯一可以在状态栏之上的窗口,他们受STATUS_BAR_SERVICE权限保护,跟状态栏有相同的优先级。if (attrs.type == TYPE_STATUS_BAR_PANEL|| attrs.type == TYPE_STATUS_BAR_SUB_PANEL) {pf.left = df.left = of.left = hasNavBar                                ? mDockLeft : mUnrestrictedScreenLeft;                        pf.top = df.top = of.top = mUnrestrictedScreenTop;                        pf.right = df.right = of.right = hasNavBar                                ? mRestrictedScreenLeft+mRestrictedScreenWidth                                : mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;                        pf.bottom = df.bottom = of.bottom = hasNavBar                                ? mRestrictedScreenTop+mRestrictedScreenHeight                                : mUnrestrictedScreenTop + mUnrestrictedScreenHeight;}}else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0                            && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW                            && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {//要求占据overscan区域,所以给定的是真正不受限制的区域。pf.left = df.left = of.left = mOverscanScreenLeft;                        pf.top = df.top = of.top = mOverscanScreenTop;                        pf.right = df.right = of.right = mOverscanScreenLeft + mOverscanScreenWidth;                        pf.bottom = df.bottom = of.bottom = mOverscanScreenTop                                + mOverscanScreenHeight;}else if (canHideNavigationBar()                            && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0                            && attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW                            && attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {//假设导航条是隐藏的,让应用窗口可以布局到无限制的overscan区域。只对应用窗口这么做,是要确保没有别的窗口能在导航条之上。pf.left = df.left = mOverscanScreenLeft;                        pf.top = df.top = mOverscanScreenTop;                        pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth;                        pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight;//我们需要告知应用程序过扫描的内侧在哪里,以便根据这个值嵌入它的内容,而不至于实际的超出overscan的范围。of.left = mUnrestrictedScreenLeft;                        of.top = mUnrestrictedScreenTop;                        of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;                        of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;}else{//然后是其他情况。}}else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0 || (sysUiFl                    & (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) {//窗口希望全屏显示,可能是有系统UI的全屏,也可能是没有系统UI的全屏。}else if (attached != null) {//子窗口的情况。setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, of, cf, vf);}else{//其他的窗口类型,如:TYPE_STATUS_BAR_PANEL,TYPE_VOLUME_OVERLAY,TYPE_TOAST,TYPE_SYSTEM_ALERT等。}// pf, df, of, cf, vf, dcf, sf, osf这些都是layoutWindowLw函数中的临时变量,最终还有通过windowState的 computeFrameLw的确认。win.computeFrameLw(pf, df, of, cf, vf, dcf, sf, osf);}

layoutWindowLw中的pf,df, of, cf, vf, dcf, sf, osf这些都是临时变量,最终还有通过windowStatecomputeFrameLw的确认。也就是分别设置到WindowState中的变量mOverscanFramemContentFramemVisibleFramemDecorFramemStableFrame中,最后把WindowState中的这些变量在relayoutWindowWindowManagerService.java)函数中通过outFrame.set(win.mCompatFrame);outOverscanInsets.set(win.mOverscanInsets);outContentInsets.set(win.mContentInsets);outVisibleInsets.set(win.mVisibleInsets);outStableInsets.set(win.mStableInsets);outOutsets.set(win.mOutsets);这些出参告知ViewRootImpl



阅读全文
0 0
原创粉丝点击