Android Framework 窗口子系统 (04) 确定窗口尺寸

198 篇文章 98 订阅

该系列文章总纲链接:专题总纲目录 Android Framework 总纲


本章关键点总结 & 说明:

该图关注➕思维导图中左边:窗口尺寸&确定 部分即可。首先说明了overscan区域和窗口尺寸的数据结构;之后说明了计算尺寸的流程和计算得到的结果。放大 窗口尺寸&确定 的局部图,效果如下:

说明:虽然可以在创建窗口时指定窗口大小,但更多时候窗口大小是由系统计算得到的,本章节主要分析窗口计算基础和流程。

1 OverScan区域与表示窗口尺寸的数据结构

@1 overScan是边缘区域(四周有一圈黑色的区域),该区域是显示屏的一部分,但通常不显示画面,在PhoneWindowManager中有4个变量与此有关,代码如下:

public class PhoneWindowManager implements WindowManagerPolicy {
    ...
    //分表表示overscan区域上下左右的宽度、高度值
    int mOverscanLeft = 0;
    int mOverscanTop = 0;
    int mOverscanRight = 0;
    int mOverscanBottom = 0;
    ...
}

初始化是在WMS配置显示设备时完成的,代码如下:

private void configureDisplayPolicyLocked(DisplayContent displayContent) {
        mPolicy.setInitialDisplaySize(displayContent.getDisplay(),
                displayContent.mBaseDisplayWidth,
                displayContent.mBaseDisplayHeight,
                displayContent.mBaseDisplayDensity);
        DisplayInfo displayInfo = displayContent.getDisplayInfo();
        mPolicy.setDisplayOverscan(displayContent.getDisplay(),
                displayInfo.overscanLeft, displayInfo.overscanTop,
                displayInfo.overscanRight, displayInfo.overscanBottom);
    }

这里mPolicy,即PhoneWindowManager中setDisplayOverscan的实现(得以完成初始化),代码如下:

public void setDisplayOverscan(Display display, int left, int top, int right, int bottom) {
        if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {//仅支持缺省设备
            mOverscanLeft = left;
            mOverscanTop = top;
            mOverscanRight = right;
            mOverscanBottom = bottom;
        }
    }

@2 表示窗口尺寸的数据结构

public class PhoneWindowManager implements WindowManagerPolicy {
    ...
    //第1组,真实屏幕大小
    int mOverscanScreenLeft, mOverscanScreenTop;
    int mOverscanScreenWidth, mOverscanScreenHeight;
    //第2组,Unrestricted区域,不包含overscan区域,包含导航条
    int mUnrestrictedScreenLeft, mUnrestrictedScreenTop;
    int mUnrestrictedScreenWidth, mUnrestrictedScreenHeight;
    //第3组,RestrictedOverscan区域,包含overscan区域,不包含导航条
    int mRestrictedOverscanScreenLeft, mRestrictedOverscanScreenTop;
    int mRestrictedOverscanScreenWidth, mRestrictedOverscanScreenHeight;
    //第4组,Restricted区域,不包含overscan区域,不包含导航条
    int mRestrictedScreenLeft, mRestrictedScreenTop;
    int mRestrictedScreenWidth, mRestrictedScreenHeight;
    //第5组,System区域
    int mSystemLeft, mSystemTop, mSystemRight, mSystemBottom;
    //第6组,Stable区域
    int mStableLeft, mStableTop, mStableRight, mStableBottom;
    //第7组,StableFullscreen区域
    int mStableFullscreenLeft, mStableFullscreenTop;
    int mStableFullscreenRight, mStableFullscreenBottom;
    //第8组,Current区域,即除去装饰区域(状态栏,输入法,导航条)以外的中心显示区域
    int mCurLeft, mCurTop, mCurRight, mCurBottom;
    //第9组,Content区域
    int mContentLeft, mContentTop, mContentRight, mContentBottom;
    //第10组,Decor区域
    int mDockLeft, mDockTop, mDockRight, mDockBottom;
    ...
}

屏幕有overscan区域、状态栏、导航栏、输入法,PhoneWindowManager定义了这些区域代表了屏幕上不同的组合,如图所示:

将成员变量与区域对应,如下所示:

  1. overscanScreen区域,包含了overscan区域,相当于整个屏幕大小
  2. RestrictedOverscanScreen区域,不包含导航栏
  3. RestrictedScreen区域,不包含屏幕上的overscan区域,不包含导航条
  4. UnrestrictedScreen区域,不包含overscan区域,包含状态条和导航栏
  5. StableFullScreen区域,包含状态栏,不包含导航栏
  6. Decor区域,不包含状态栏,不包含导航栏,包含输入法的区域
  7. Current区域,不包含状态栏,不包含导航栏,不包含输入法的区域

特殊说明:

  1. 对于System区域、Stable区域、Content区域的范围和Decor区域的范围相同,用在不同的场合含义不同,但值是相同的
  2. 以后屏幕插入更多的区域,这些屏幕的区域可能发生变化(为以后的可维护性做准备)

@3 初始化窗口尺寸(即这些区域的值)
在PhoneWindowManager类中,是使用方法beginLayoutLw来初始化这些区域的值的,代码如下:

public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
                              int displayRotation) {
        final int overscanLeft, overscanTop, overscanRight, overscanBottom;
        ...//这段代码根据屏幕方向调整4个overscan区域
        //根据各个区域是否延伸到屏幕的overscan区域来对各个区域进行初始化
        mOverscanScreenLeft = mRestrictedOverscanScreenLeft = 0;
        mOverscanScreenTop = mRestrictedOverscanScreenTop = 0;
        mOverscanScreenWidth = mRestrictedOverscanScreenWidth = displayWidth;
        mOverscanScreenHeight = mRestrictedOverscanScreenHeight = displayHeight;
        ...
        if (isDefaultDisplay) {
            ...
            if (mNavigationBar != null) {//计算导航栏尺寸并调整相关区域的值
                ...
                mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame,
                        mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame, dcf,
                        mTmpNavigationFrame);
                ...//之后根据导航栏尺寸调整其他区域尺寸
            }
            if (mStatusBar != null) {//计算状态条尺寸并调整相关区域的值
                ...
                mStatusBar.computeFrameLw(pf, df, vf, vf, vf, dcf, vf);
                ...//之后根据状态条尺寸调整其他区域尺寸
            }
            if (updateSysUiVisibility) {
                updateSystemUiVisibilityLw();
            }
        }
    }

经过beginLayoutLw方法的调整,各个布局参数就准备好了。

2 计算窗口尺寸

WMS中计算窗口的过程中很繁琐,不仅仅局限在计算窗口的大小。
@1 下图表示了从performTraversals开始分析一个Activity窗口大小的计算过程,如下所示:

@2 这里仅对针对窗口计算部分进行分析,所以只分析layoutWindowLw。代码如下:

public void layoutWindowLw(WindowState win, WindowState attached) {
    final WindowManager.LayoutParams attrs = win.getAttrs();
    if ((win == mStatusBar && (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) == 0) ||
            win == mNavigationBar) {
        return;
    }
    ...
    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;
    dcf.setEmpty();
    if (!isDefaultDisplay) {
        ...
    } else  if (attrs.type == TYPE_INPUT_METHOD) {
        ...
    } else if (win == mStatusBar && (attrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0) {
        ...
    } else {
        ...
        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 {
                ...
            }
        } else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0 || (sysUiFl
                & (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) {
           ...
        } else if (attached != null) {
          ...
        } else {
          ...
        }
    }
    ...
    win.computeFrameLw(pf, df, of, cf, vf, dcf, sf);//最关键的地方
    if (attrs.type == TYPE_INPUT_METHOD && !win.getGivenInsetsPendingLw()) {
        setLastInputMethodWindowLw(null, null);
        offsetInputMethodWindowLw(win);
    }
    if (attrs.type == TYPE_VOICE_INTERACTION && win.isVisibleOrBehindKeyguardLw()
            && !win.getGivenInsetsPendingLw()) {
        offsetVoiceInputWindowLw(win);
    }
}

layoutWindowLw的主要内容如下:

  • 先计算出窗口能够扩展的最大空间,即6个矩形区域的大小,分别是:
  1.  pf(ParentFrame):本窗口的父窗口大小
  2.  df(DeviceFrame):设备的屏幕大小
  3.  of(OverscanFrame):设备的屏幕大小,=df
  4.  cf(ContentFrame):窗口内容区域大小
  5.  vf(VisibleFrame):可见区域大小
  6.  dcf(DecorFrame):装饰区域大小,除去状态栏和导航栏
  • 得到6个区域的值(关键参数)后,调用WS的computeFrameLw计算窗口的最终大小

注意:这些只是临时值,未计算窗口大小服务,与之前的数据结构有关联,但并不完全等同于那些值
特殊说明:窗口大小还要考虑软键盘的影响,如果属性中带有SOFT_ADJUST_RESIZE,表示窗口大小会随着软键盘调整

@3 分析关键点 
@@3.1 回顾WS中定义了一些和窗口尺寸相关的矩形变量,如下所示:

final class WindowState implements WindowManagerPolicy.WindowState {
    static final String TAG = "WindowState";
    ...
    final Rect mFrame = new Rect();//真实窗口大小
    final Rect mCompatFrame = new Rect();//兼容窗口大小
    final Rect mParentFrame = new Rect();//父类窗口大小
    final Rect mDisplayFrame = new Rect();//显示设备大小
    final Rect mOverscanFrame = new Rect();//overscan区域大小
    final Rect mDecorFrame = new Rect();  //装饰区域大小
    final Rect mContentFrame = new Rect();//内容区域大小
    final Rect mVisibleFrame = new Rect();//可见区域大小
    ...
}

@@3.2 接下来WS的方法win.computeFrameLw(pf, df, of, cf, vf, dcf, sf)会用到这些变量,代码如下:

public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf) {
        mHaveFrame = true;
        TaskStack stack = mAppToken != null ? getStack() : null;
        if (stack != null && !stack.isFullscreen() && !isFloatingWindow()) {
            getStackBounds(stack, mContainingFrame);
            if (mUnderStatusBar) {
                mContainingFrame.top = pf.top;
            }
        } else {
            mContainingFrame.set(pf);
        }
        mDisplayFrame.set(df);
        final int pw = mContainingFrame.width();
        final int ph = mContainingFrame.height();
        int w,h;//计算窗口的高度、宽度
        
        if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0) {
            //如果指定了FLAG_SCALED标志,说明窗口中属性已经指定了大小,直接使用attr的宽度和高度
            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 {
            //如果指定了MATCH_PARENT标志,说明窗口大小==父类窗口大小,否则指定mRequestedWidth或mRequestedHeight
            if (mAttrs.width == WindowManager.LayoutParams.MATCH_PARENT) {
                w = pw;
            } else if (mEnforceSizeCompat) {
                w = (int)(mRequestedWidth * mGlobalScale + .5f);
            } else {
                w = mRequestedWidth;
            }
            if (mAttrs.height == WindowManager.LayoutParams.MATCH_PARENT) {
                h = ph;
            } else if (mEnforceSizeCompat) {
                h = (int)(mRequestedHeight * mGlobalScale + .5f);
            } else {
                h = mRequestedHeight;
            }
        }
        //使用参数设置下面的变量
        if (!mParentFrame.equals(pf)) {
            mParentFrame.set(pf);
            mContentChanged = true;
        }
        if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) {
            mLastRequestedWidth = mRequestedWidth;
            mLastRequestedHeight = mRequestedHeight;
            mContentChanged = true;
        }

        //采用直接赋值的方法
        mOverscanFrame.set(of);
        mContentFrame.set(cf);
        mVisibleFrame.set(vf);
        mDecorFrame.set(dcf);
        mStableFrame.set(sf);
        final int fw = mFrame.width();
        final int fh = mFrame.height();

        float x, y;
        //对于兼容模式,Android采用的策略就是把窗口进行缩放,使之充满屏幕。
        if (mEnforceSizeCompat) {//如果初始化窗口时带有PRIVATE_FLAG_COMPATIBLE_WINDOW,则该值为true
            x = mAttrs.x * mGlobalScale;
            y = mAttrs.y * mGlobalScale;
        } else {
            x = mAttrs.x;
            y = mAttrs.y;
        }
        //考虑Gravity属性对窗口大小的影响
        //根据窗口属性中的对齐方式以及各种标志位来重新调整窗口大小
        Gravity.apply(mAttrs.gravity, w, h, mContainingFrame,
                (int) (x + mAttrs.horizontalMargin * pw),
                (int) (y + mAttrs.verticalMargin * ph), mFrame);
        //将计算出来的窗口大小限制在显示设备的大小范围内
        Gravity.applyDisplay(mAttrs.gravity, df, mFrame);
        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));
        ...
        //兼容模式的窗口大小==mFrame的大小进行mInvGlobalScale倍的缩放
        mCompatFrame.set(mFrame);
        if (mEnforceSizeCompat) {
            mOverscanInsets.scale(mInvGlobalScale);
            mContentInsets.scale(mInvGlobalScale);
            mVisibleInsets.scale(mInvGlobalScale);
            mStableInsets.scale(mInvGlobalScale);
            mCompatFrame.scale(mInvGlobalScale);
        }
        ...
    }

computeFrameLw对窗口布局的产出主要是以下几个变量,如下所示:

  1. mFrame:描述窗口的位置和尺寸
  2. mContainingFrame和mParentFrame:这两个矩形相同,保存了pf参数
  3. mDisplayFrame:保存了df参数
  4. mContentFrame和mContentInsets:mContentFrame表示当前窗口中显示内容的区域,mContentInsets表示mContentFrame与mFrame的4条边界之间的距离
  5. mVisibleFrame和mVisibleInsets:mVisibleFrame表示当前不被系统遮挡的区域,mVisibleInsets表示mVisibleFrame与mFrame的4条边界之间的距离

至此,单个窗口的布局计算完成,布局后,窗口的位置尺寸、内容区域的位置尺寸、可视区域的位置尺寸都得到了更新,这些更新会影响到窗口surface的位置尺寸,还会以回调方式通知窗口客户端,影响窗口内容的绘制。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

图王大胜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值