android 多窗口 framework,Android Framework 窗口子系统 (04)?确定窗口...

系列文章解读&说明:

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

(01)WindowMangerService基础知识

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

(03)窗口显示次序

(04)确定窗口尺寸

(05)窗口布局说明

(06)窗口动画之Choreographer机制

(07)窗口动画之Animation & Animator

(08)窗口动画之动画系统框架

本模块分享的内容:确定窗口尺寸

本章关键点总结 & 说明:

2019070515535131.png

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

20190705155418463.png

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

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定义了这些区域代表了屏幕上不同的组合,如图所示:

20190628094221622.png

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

overscanScreen区域,包含了overscan区域,相当于整个屏幕大小

RestrictedOverscanScreen区域,不包含导航栏

RestrictedScreen区域,不包含屏幕上的overscan区域,不包含导航条

UnrestrictedScreen区域,不包含overscan区域,包含状态条和导航栏

StableFullScreen区域,包含状态栏,不包含导航栏

Decor区域,不包含状态栏,不包含导航栏,包含输入法的区域

Current区域,不包含状态栏,不包含导航栏,不包含输入法的区域

特殊说明:

对于System区域、Stable区域、Content区域的范围和Decor区域的范围相同,用在不同的场合含义不同,但值是相同的

以后屏幕插入更多的区域,这些屏幕的区域可能发生变化(为以后的可维护性做准备)

@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窗口大小的计算过程,如下所示:

20190628094423567.png

@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个矩形区域的大小,分别是:?pf(ParentFrame):本窗口的父窗口大小

?df(DeviceFrame):设备的屏幕大小

?of(OverscanFrame):设备的屏幕大小,=df

?cf(ContentFrame):窗口内容区域大小

?vf(VisibleFrame):可见区域大小

?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对窗口布局的产出主要是以下几个变量,如下所示:

mFrame:描述窗口的位置和尺寸

mContainingFrame和mParentFrame:这两个矩形相同,保存了pf参数

mDisplayFrame:保存了df参数

mContentFrame和mContentInsets:mContentFrame表示当前窗口中显示内容的区域,mContentInsets表示mContentFrame与mFrame的4条边界之间的距离

mVisibleFrame和mVisibleInsets:mVisibleFrame表示当前不被系统遮挡的区域,mVisibleInsets表示mVisibleFrame与mFrame的4条边界之间的距离

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

?

?

?

?

?

?

?