WMS:窗口大小的计算
android系统中,界面上除了应用程序外,至少还可能有两个元素:状态栏和输入法窗口##
状态栏和软键盘都会影响应用程序窗口大小的计算,前者所占的区域相对固定,输入法窗口的表现则取决于activity属性的设置。
1.1 windowSoftInputMode
大致分为两类,state开头表示当activity成为焦点时软键盘是隐藏还是可见;adjust开头表示如何调整activity窗口以容纳软键盘,所以这两个大类之间是或的关系
通讯录编辑界面:软键盘出现时,程序窗口不变
短信息编辑界面:软键盘出现时,程序窗口缩小。
1.2 ViewRootImpl中和窗口大小相关的量
int mWidth,int mHeight:当前真实的宽和高。
Rect mWinFrame:当Activity的窗口大小需要改变时,WMS会通过W.resized接口来通知客户端,mWinFrame就用于记录WMS提供的宽高。
Rect mPendingVisibleInsets:WMS提供的,用于表示可见区域
Rect mPendingContentInsets:WMS提供的,用于表示内容区域
int desiredWindowWidth,int desiredWindowHeight:期望的宽高值
ContentInsets:内容区域,VisibleInsets:可见区域
短信编辑界面,当输入法窗口出现时,应用窗口减小以容纳软键盘,这时内容区域和可见区域是一样的;
通讯录编辑界面,当输入法窗口出现时,内容区域没有变化,软键盘覆盖了一部分,所以此时两者不一样。
ViewRootImpl会在程序运行期间多次调用performTraversals方法,这个同时也是计算应用窗口大小的起点,如果是第一次调用该函数的话,那么desiredWindowWidth和desiredWindowHeight的默认值就是屏幕大小或者状态栏的配置大小(窗口类型是TYPE_STATUS_BAR_PANEL),此时应用程序会调用host.fitSystemWindows(mFitSystemWindowsInsets)来设置四个内边距,其中mFitSystemWindowsInsets的值来源于mAttachInfo,mContentInsets,(mContentInsets表达的是内容区域的真实大小,mPendingContentInsets表示的是WMS提供的值)。
fitSystemWindows用来通知一个窗口的内容区域发生了变化,以使View有机会来适应新的变化,它会沿着ViewTree由上而下传递各个View对象。
区分###
SYSTEM_UI_FLAG_FULLSCREEN/SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
SYSTEM_UI_FLAG_HIDE_NAVIGATION/SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
不带layout表示用户要求全屏/隐藏状态栏,这样最终就是全屏/隐藏状态栏
带layout则只是假设当前已经全屏/隐藏状态栏
如果不是第一次调用performTraversals方法,那么desiredWindowWidth/desiredWindowHeight就分别等于mWinFrame.width/mWinFrame.height,也就是说这两个变量表达的是WMS的“desire”,一旦期望值与当前的mWidth/mHeight不一致,那么mFullRedrawNeeded,mLayoutRequested,windowSizeMayChange就会被置为true,这会影响后面的流程。
1.3开始计算
performTraversals有一个核心的判断,这几个条件满足任意一项,就说明窗口尺寸和关键属性发生率变化,此时就需要向WMS传递这个消息,也就是调用relaoutWindow然后调用relayout方法
if (mFirst || windowShouldResize || insetsChanged ||
viewVisibilityChanged || params != null) {
1.4 Session的relayout方法
public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags,
int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
Rect outVisibleInsets, Configuration outConfig, Surface outSurface) {
int res = mService.relayoutWindow(this, window, seq, attrs,
requestedWidth, requestedHeight, viewFlags, flags,
outFrame, outOverscanInsets, outContentInsets, outVisibleInsets,
outConfig, outSurface);
return res;
}
outFrame:WMS得出的应用窗口大小,对应ViewRootImpl的成员变量mWinFrame
outOverScanInsets:WMS得出的过扫描区域
outContentInsets:WMS得出的ContentInsets,对应ViewRootImpl的mPendingContentInsets
outVisibleInsets:WMS得出的VisibleInsets,对应ViewRootImpl的mPendingVisileInsets
outConfig:WMS得出的窗口配置信息,对应ViewRootImpl的mPendingConfiguration
outSurface:WMS申请的有效的Surface对象,对应ViewRootImpl的mSurface
1.5 relayoutWindow-performLayoutAndPlaceSurfacesLocked-performLayoutAndPlaceSurfacesLockedLoop-performLayoutAndPlaceSurfacesInner
private final void performLayoutAndPlaceSurfacesLockedInner(boolean recoveringMemory) {
SurfaceControl.openTransaction();
try {
int repeats = 0;
do {//退出的条件是displayContent.pendingLayoutChanges == 0
repeats++;
if (repeats > 6) {//循环的最多次数
displayContent.layoutNeeded = false;
break;
}
if (isDefaultDisplay && (displayContent.pendingLayoutChanges
& WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG) != 0) {
//重新计算Config
if (updateOrientationFromAppTokensLocked(true)) {
displayContent.layoutNeeded = true;
mH.sendEmptyMessage(H.SEND_NEW_CONFIGURATION);
}
}
if ((displayContent.pendingLayoutChanges
& WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT) != 0) {
//重新计算Layout
displayContent.layoutNeeded = true;
}
if (repeats < 4) {//这个函数最多调用四次
performLayoutLockedInner(displayContent, repeats == 1,
false /*updateInputWindows*/);
} else {
}
displayContent.pendingLayoutChanges = 0;
if (isDefaultDisplay) {
//begin
mPolicy.beginPostLayoutPolicyLw(dw, dh);
for (i = windows.size() - 1; i >= 0; i--) {
WindowState w = windows.get(i);
if (w.mHasSurface) {
//apply
mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs);
}
}
displayContent.pendingLayoutChanges |= mPolicy.finishPostLayoutPolicyLw();//finish...
}
} while (displayContent.pendingLayoutChanges != 0);
}
pendingLayoutChanges###的可选值
FINISH_LAYOUT_REDO_LAYOUT=0x0001:Layout状态可能已经改变,所以还要发起一次layout
FINISH_LAYOUT_REDO_CONFIG=0x0002:Configuration状态已经改变
FINISH_LAYOUT_REDO_WALLPAPER=0x0004:壁纸可能需要挪动
FINISH_LAYOUT_REDO_ANIM=0x0008:需要重新计算动画
只有当displayContent.pendingLayoutChanges == 0也就是没有需要处理的layoutChanges时或者循环超过次数时,循环才退出,如果有FINISH_LAYOUT_REDO_CONFIG,那么就通过handler向WMS发送一个SEND_NEW_CONFIGURATION,重新计算Configuration,如果带有FINISH_LAYOUT_REDO_LAYOUT那么就将layoutNeeded置为true,如果这个变量为false,那么performLayoutLockedInner会直接返回。repeats小于4就会调用performLayoutLockedInner
1.6 performLayoutLockedInner
private final void performLayoutLockedInner(final DisplayContent displayContent,
boolean initial, boolean updateInputWindows) {
if (!displayContent.layoutNeeded) {//如果此变量为false直接返回,不需要layout
return;
}
displayContent.layoutNeeded = false;//已经执行了本次layout,状态关闭
//窗口列表
WindowList windows = displayContent.getWindowList();
//是否是默认display
boolean isDefaultDisplay = displayContent.isDefaultDisplay;
//Display的详细信息
DisplayInfo displayInfo = displayContent.getDisplayInfo();
//逻辑宽高
final int dw = displayInfo.logicalWidth;
final int dh = displayInfo.logicalHeight;
//窗口数量
final int NFW = mFakeWindows.size();
for (int i=0; i<NFW; i++) {
mFakeWindows.get(i).layout(dw, dh);
}
final int N = windows.size();
int i;
WindowStateAnimator universeBackground = null;
mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mRotation);
//用于与客户端同步
int seq = mLayoutSeq+1;
if (seq < 0) seq = 0;//当seq超过范围的时候重新从0开始计算
mLayoutSeq = seq;//把mLayoutSeq更新为当前的seq值
int topAttached = -1;
for (i = N-1; i >= 0; i--) {
final WindowState win = windows.get(i);
//加入窗口不可见或者即将不可见,那么就不去计算
final boolean gone = (behindDream && mPolicy.canBeForceHidden(win, win.mAttrs))
|| win.isGoneForLayoutLw();
//mHaveFrame表示窗口是否参与过计算
if (!gone || !win.mHaveFrame || win.mLayoutNeeded
|| (win.mAttrs.type == TYPE_KEYGUARD && win.isConfigChanged())
|| win.mAttrs.type == TYPE_UNIVERSE_BACKGROUND) {
if (!win.mLayoutAttached) {//为true则表示是子窗口留到后一步计算
if (initial) {
win.mContentChanged = false;
}
if (win.mAttrs.type == TYPE_DREAM) {
behindDream = true;
}
win.mLayoutNeeded = false;
win.prelayout();
//执行具体的计算
mPolicy.layoutWindowLw(win, win.mAttrs, null);
win.mLayoutSeq = seq;
} else {//记录attachWindow,共下一步使用
if (topAttached < 0) topAttached = i;
}
}
}
//计算attachedWindow的情况,这些窗口和他们的父窗口关系很大
for (i = topAttached; i >= 0; i--) {
final WindowState win = windows.get(i);
if (win.mLayoutAttached) {
if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled)
|| !win.mHaveFrame || win.mLayoutNeeded) {
win.mLayoutNeeded = false;
win.prelayout();
mPolicy.layoutWindowLw(win, win.mAttrs, win.mAttachedWindow);
win.mLayoutSeq = seq;
}
} else if (win.mAttrs.type == TYPE_DREAM) {
attachedBehindDream = behindDream;
}
}
mPolicy.finishLayoutLw();
}
1.先处理RootWindows也就是没有依附于其他窗口的窗口,因为下一步子窗口的计算需要这些父窗口
2.处理AttachedWindows,
整体流程
mPolicy.beginLayoutLw初始化
mPolicy.layoutWindowLw执行计算过程
mPolicy.finishLayoutLw清理工作
2.三个重要的接口
2.1 beginLayoutLw
PhoneWindowManger中与窗口大小相关的变量
int mOverScanLeft = 0;
int mOverScanRight = 0;
int mOverScanTop = 0;
int mOverScanBottom = 0;
(http://www.cnblogs.com/all-for-fiona/p/4054527.html)
int mOverScanScreenLeft;
int mOverScanScreenTop;
int mOverScanScreenRight;
int mOverScanScreenBotom;
屏幕真实大小,包括overscan区域
int mRestrictedOverscanScreenLeft,
int mRestrictedOverscanScreenTop,
int mRestrictedOverscanScreenRight,
int mRestrictedOverscanScreenHeight,
类似于上面的,适当的时候可以移动到OverScan区域
int mUnrestrictedScreenLeft,
int mUnrestrictedScreenTop,
int mUnrestrictedScreenWidth,
int mUnrestrictedScreenHeight,
屏幕真实大小,不包含overscan区域
int mRestrictedScreenLeft,
int mRestrictedScreenTop,
int mRestrictedScreenWidth,
int mRestrictedScreenHeight,
屏幕大小,如果状态栏不能隐藏,他和上述的变量不同
int mCurLeft,mCurTop,mCurRight,mCurBottom
包括状态栏输入法在内的外围尺寸
int mContentLeft,mContentTop,mContentRight,mContentBottom
内容区域
int mDockLeft,mDockTop,mDockRight,mDockBottom
输入法窗口区域
beginLayoutLw方法的作用就是对这些变量进行初始化,left,top这些是0,right是屏幕宽度,bottom是屏幕高度
也就是说他们都被设置成了满屏。接下来的过程分为两部分,是否有状态栏是否有导航栏
导航栏:竖屏在下方,横屏在右方;
状态栏相对固定;
2.2 layoutWindowLw
根据当前的具体配置进行细化操作
public void layoutWindowLw(WindowState win, WindowManager.LayoutParams attrs,
WindowState attached) {
//如果是导航栏和状态栏,在其他地方已经处理过了,直接返回
if (win == mStatusBar || win == mNavigationBar) {
return;
}
final int fl = attrs.flags;//窗口属性
final int sim = attrs.softInputMode;//输入法模式
//系统ui的设置值
final int sysUiFl = win.getSystemUiVisibility();
final Rect pf = mTmpParentFrame;//父窗口大小
final Rect df = mTmpDisplayFrame;//屏幕大小
final Rect cf = mTmpContentFrame;//内容区域
final Rect vf = mTmpVisibleFrame;//可见区域
//是否有导航栏的标志位
final boolean hasNavBar = (isDefaultDisplay && mHasNavigationBar
&& mNavigationBar != null && mNavigationBar.isVisibleLw());
final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
if (!isDefaultDisplay) {
//......
} else if (attrs.type == TYPE_INPUT_METHOD) {
//输入法窗口的情况
} else {
if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR))
== (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {
//case 1.应用窗口的情况
if (attached != null) {
// 1.1子窗口,
} else {
// 1.2 非子窗口
if (attrs.type == TYPE_STATUS_BAR_PANEL
|| attrs.type == TYPE_STATUS_BAR_SUB_PANEL) {
//1.2.1 这是两个唯一可以在状态栏上方显示的窗口类型,收到StatusBar Service的权限保护
} else if ((attrs.flags&FLAG_LAYOUT_IN_OVERSCAN) != 0
&& attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
&& attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
// 1.2.2 普通的应用窗口,并且明确指定了FLAG_LAYOUT_IN_OVERSCAN,说明他希望占据overscan区域
} else if (mCanHideNavigationBar
&& (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
&& attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
&& attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
//1.2.3 普通应用窗口类型并指定SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,也就是假设当前导航栏是隐藏的
} else {
//其他情况
}
}
} else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0 || (sysUiFl
& (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) {
//窗口希望满屏显示
if (attrs.type == TYPE_STATUS_BAR_PANEL
|| attrs.type == TYPE_STATUS_BAR_SUB_PANEL) {
} else if (attrs.type == TYPE_NAVIGATION_BAR
|| attrs.type == TYPE_NAVIGATION_BAR_PANEL) {
} else if ((attrs.type == TYPE_SECURE_SYSTEM_OVERLAY
|| attrs.type == TYPE_BOOT_PROGRESS)
&& ((fl & FLAG_FULLSCREEN) != 0)) {
} else if (attrs.type == TYPE_BOOT_PROGRESS
|| attrs.type == TYPE_UNIVERSE_BACKGROUND) {
} else if (attrs.type == WindowManager.LayoutParams.TYPE_WALLPAPER) {
} else if ((attrs.flags & FLAG_LAYOUT_IN_OVERSCAN) != 0
&& attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
&& attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
} else if (mCanHideNavigationBar
&& (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
&& attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
&& attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
} else {
}
} else if (attached != null) {
//子窗口的情况
setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, of, cf, vf);
} else {
}
}
//此时得到WindowState的mParentFrame,mDisplayFrame,mContentFrame,mVisibleFrame
win.computeFrameLw(pf, df, of, cf, vf);
}
2.3 finishLayoutLw
空实现
经过这一系列的调用之后,relayoutWindow才算真正结束,它会负责将计算结果通过几个出参outContentInsets,outVisibleInsets,outCofig,outSurface告知ViewRootImpl,从而影响UI程序的显示结果。