在performTraversal,会涉及到View的measure、layout、draw。其中measure用来对View进行测量,给出建议值,layout来确定子控件在父控件中的位置,包括真实大小以及坐标位置,draw负责将View绘制出来。单文只分析与窗口大小相关的逻辑,performTraversal方法会被多次调用到,这个方法是计算窗口大小的起点
Overscan(过扫描区):
Overscan是电视机特有的概念,上图中黄色的区域就是overscan区域,指的是电视机四周一圈的黑色区域,称为overscan(过扫描)区域,这个区域也是显示屏的一部分,但通常不能显示。如果窗口的某些内容画在这个区域里,它在某些电视上就会看不到。为了避免这种情况发生,通常要求UI不要画在屏幕的边角上,而是预留一定的空间。因为Overscan的区域大小随着电视不 同而不同,它一般由终端用户通过指定。注意:手机硬件上不存在这个区域。
OverscanScreen/Screen:
OverscanScreen是包含Overscan区域的屏幕大小,而Screen则为去除Overscan区域后的屏幕区域,OverscanScreen > Screen。
Restricted and Unrestricted:
某些区域是被系统保留的,比如说手机屏幕上方的状态栏(上图绿色区域)和下方的导航栏,根据是否包括这些预留的区域,Android把区域分为Unrestricted Area 和 Resctrited Aread,前者包括这部分预留区域,后者不包含,故Unrestricted area > Rectricted area。
RestrictedOverScanScreen:
包括overscan区,不包含导航栏、因此这个区域上面到屏幕的顶部,下面就到导航栏的顶部。
l RestrictedScreen:
这个区域不包含overscan区域不包含导航栏。
l UnRestrictedScreen:
不包含屏幕的overscan区域、包含状态栏和导航栏。
l stableFullScreen:
包含状态栏、输入法、不包含导航栏。
l Decor区域:
DecorView大小。包含输入法区域,包含状态栏/导航栏区域与否,与Window Flag有关。
l Current区域/Visible区域:
可视区域。不包含状态栏、导航栏和输入法区域。getWindowVisibleDisplayFrame获得的Rect大小。
在大多数情况下,Activity窗口的内容区域和可见区域的大小是一致的,而状态栏和输入法窗口所占用的区域又称为屏幕装饰区(decorations)。WMS服务实际上就是需要根据屏幕以及可能出现的状态栏和输入法窗口的大小来计算出Activity窗口的整体大小及其过扫描区域边衬和可见区域边衬的大小。
int desiredWindowWidth; 期望的宽度值 int desiredWindowHeight; 期望的高度值
// 当Activity的窗口大小需要改变时,WMS会通过W.resized接口来通知客户端,mWinFrame用来记录WMS提供的宽高 final Rect mWinFrame; // frame given by window manager.
final Rect mPendingVisibleInsets = new Rect();//wms提供的,表示可见区域 final Rect mPendingContentInsets = new Rect();//wms提供的,表示内容区域
ContentInsets:内容区域 ,VisibleInsets :可见区域
短信编辑界面:当输入法窗口出现时,应用窗口变小以容纳软键盘,这是内容区域和可见区域显示一样
联系人编辑界面:当输入法窗口出现时,应用窗口没有发生变化,软键盘覆盖了一部分,这是内容区域和可见区域显示不一样
分析一下perfromTraversal中与窗口计算相关的代码,分段分析perfromTraversal
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;//当前window的Decorview
int desiredWindowWidth;
int desiredWindowHeight;
//将mWinFrame赋值给了局部变量frame,mWinFrame保存的为WMS修改后的window的宽度和高度
Rect frame = mWinFrame;
if (mFirst) {//第一次绘制
mFullRedrawNeeded = true;//需要全部绘制
mLayoutRequested = true;//需要全部布局
final Configuration config = mContext.getResources().getConfiguration();
if (shouldUseDisplaySize(lp)) {
//如果是statusbarpanel,音量调节窗口,输入法窗口,获得尺寸为屏幕分辨率的真实尺寸
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
//否在将mWinFrame大小赋值给desiredWindowWidth
desiredWindowWidth = mWinFrame.width();
desiredWindowHeight = mWinFrame.height();
}
........
} else {
//如果不是第一次绘制的,desiredWindowWidth/desiredWindowHeight就为mWindowFrame/frame的宽度和高度
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
//mWidth此时表示上次执行该方法时frame.width(),如果这两个值不相等,说明此视图需要重新绘制
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
mFullRedrawNeeded = true;
mLayoutRequested = true;
windowSizeMayChange = true;//window尺寸发生变化
}
}
这段代码用来获得窗口的当前宽度和desiredWindowWidth和当前高度desiredWindowHeight。
这段代码有一个全局变量mWinFrame,用来保存窗口当前高度和宽度,一个局部变量frame,绘制前将当前窗口大小mWinFrame赋值给frame。另外两个成员变量mWidth和mHeight也用来描述窗口当前的宽度和高度,但是它们与desiredWindowWidth/desiredWindowHeight不同,它保存的是应用程序进程上一次主动请求wms计算得到的,并且会一直保持不变到应用程序进程下一次再请求WindowManagerService服务来重新计算为止。
Activity窗口第一次被请求执行测量、布局和绘制操作,成员变量mFirst为true,如果当前窗口是statusbar窗口、输入法窗口或者音量调节窗口,那么它的当前宽度desiredWindowWidth和当前高度desiredWindowHeight就等于屏幕实际大小,否则的话,desiredWindowWidth/ desiredWindowHeight等于保存在全局变量mWinFrame中的宽度和高度值。
如果Activity窗口不是第一次被请求执行测量、布局和绘制操作,并且mWidth和高度mHeight不等于Activity窗口的当前宽度desiredWindowWidth和当前高度desiredWindowHeight,那么就说明Activity窗口的大小发生了变化,这时候变量windowSizeMayChanged的值就会被标记为true,以便接下来可以对窗口的大小变化进行处理。
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
........
// Ask host how big it wants to be
//通过measureHierarchy来对viewtree进行一次计算
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
.......
if (layoutRequested) {
mLayoutRequested = false;
}
注意:当第一次请求绘制时,mLayoutRequested为true,并且请求绘制的窗口不在stop状态,layoutRequested也为true,的情况下调用measureHierarchy进行第一次view tree的计算。这次计算会确定顶层视图,即window的大小,然后返回的window大小是否改变的布尔值。接着会把mLayoutRequested设置为false,这就说明了measureHierarchy只会被调用一次
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
mForceNextWindowRelayout = false;
........
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString()
+ " overscan=" + mPendingOverscanInsets.toShortString()
+ " content=" + mPendingContentInsets.toShortString()
+ " visible=" + mPendingVisibleInsets.toShortString()
+ " stable=" + mPendingStableInsets.toShortString()
+ " cutout=" + mPendingDisplayCutout.get().toString()
+ " outsets=" + mPendingOutsets.toShortString()
+ " surface=" + mSurface);
满足下面的条件之一进入if判断:
mFirst等于true,即第一次执行测量、布局和绘制操作
windowShouldResize等于true,即窗口的大小发生了变化。
insetsChanged等于true,即窗口的内容区域边衬发生了变化。
viewVisibilityChanged等于true,窗口可见性发生了变化。
params不为空,窗口的属性发生了变化,指向了一个WindowManager.LayoutParams对象。
在满足上述条件之一,并且Activity窗口处于可见状态,那么就需要检查接下来请求WindowManagerService服务计算大小时,是否要告诉WindowManagerService服务它指定了额外的内容区域边衬和可见区域边衬,即insetsPending是否为true。接下来调用成员函数relayoutWindow请求WindowManagerService计算窗口的大小以及内容区域边衬大小和可见区域边衬大小。
Session.relayout
@Override
public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets,
Rect outStableInsets, Rect outsets, Rect outBackdropFrame,
DisplayCutout.ParcelableWrapper cutout, MergedConfiguration mergedConfiguration,
Surface outSurface) {
int res = mService.relayoutWindow(this, window, seq, attrs,
requestedWidth, requestedHeight, viewFlags, flags, frameNumber,
outFrame, outOverscanInsets, outContentInsets, outVisibleInsets,
outStableInsets, outsets, outBackdropFrame, cutout,
mergedConfiguration, outSurface);
}
outFrame:WMS得出的应用窗口大小,对应ViewRootImpl的mWinFrame
outOverscanInsets:WMS得出的过扫描区域
outContentInsets:WMS得出的ContentInsets,对应ViewRootImpl的mPendingContentInsets
outVisibleInsets:WMS得出的VisibleInsets,对应ViewRootImpl的mPendingVisileInsets
outStableInsets:WMS的得出的StableInsets,对应ViewRootImpl的
outSurface:WMS申请的有效的Surface对象,对应ViewRootImpl的mSurface
Displaycontent.performLayout
WMS计算布局最终会调用到DispalyContent.performLayout方法,这个方法是从WindowSurfacePlacer.performSurfacePlacement中通过层层调用的,本文主要介绍确认窗口大小的相关知识,其他的方法先不做介绍,调用逻辑如下:
Displaycontent.applySurfaceChangesTransaction
而在调用此方法的时候会遍例当前屏幕所有的DisplayContent的信息,此时如果通过DisplayConten.getDisplayInfo的信息后,此时获得相关的逻辑尺寸的信息与物理尺寸是有区别的,例如通过修改屏幕的密度和size的信息,此时得到为修改后的屏幕的信息不是实际屏幕的物理尺寸.而默认的屏幕用Display.DEFAULT_DISPLAY来作为标记
boolean applySurfaceChangesTransaction(boolean recoveringMemory) {
final int dw = mDisp