记得之前在小米的时候,有个同学说PhoneWindowManager是窗口最乱的地方,好像很多杂活都是PhoneWindowManager去干,其实我不太认同这个说法。 仔细分析一下,PhoneWindowManager实现了WindowManagerPolicy, WindowManagerPolicy注释写的很清楚:
* This interface supplies all UI-specific behavior of the window manager. An
* instance of it is created by the window manager when it starts up, and allows
* customization of window layering, special window types, key dispatching, and
* layout.
也就是PhoneWindowManager主要管理Android 手机的特定UI行为、包括定义窗口的分层、窗口的类型、input事件的调度和窗口的布局。按照这些职责来划分,其实PhoneWindowManager的结构就变得比较清晰。今天我们重点聊聊PhoneWindowManager对Android的窗口布局管理。
要聊窗口必须了解显示设备。 这里要引入一个过扫描(OverScan)的概念:
过扫描是指电视屏幕上的裁剪图像。 以下是一些示例: 电视上的设置放大了电影内容,因此您看不到电影的最外边缘。 电视的塑料边框挡住了部分电视屏幕,因此您看不到内容的边缘。
也就是屏幕有一部分是用户看不到的,所以应用程序窗口内容要想被用户看到,就应该避开过扫描区域去渲染内容。
除了过扫描区域,为了和系统交互,Android系统还在屏幕上绘制装饰区,像我们常见的状态栏(statusbar) 和 导航栏navbar就是系统装饰区, 这些装饰区主要用于显示系统状态 和 系统交互。但是系统装饰区有可以根据应用的需求显示和隐藏。也可以根据应用的需求做透明处理,以获得沉浸式体验。应用还需要根据装饰区来移动内容绘制的区域,防止内容和装饰区重叠,造成阅读障碍。
所以PhoneWindowManager的窗口布局,既要能满足应用对窗口大小的需求(通常是填充整个屏幕),还要能告知应用屏幕的过扫描区域大小和装饰区大小,使应用可以避开这些区域绘制主要内容,或者为这些区域绘制背景(我们可以为透明装饰区绘制背景,获取沉浸体验,但是内容应该避开装饰区)。
关于装饰区的设置推荐你看下google的官方文档控制系统界面可见度
除了系统装饰区还要考虑输入法的影响,窗口还需要根据输入法向上平移内容或者滚动内容以不受到输入法影响。所以应用程序还需要知道输入法所占用的区域。
说了平面的窗口之间的影响,PhoneWindowManager还需要考虑的一部分是系统窗口的层级,Android大概将系统窗口分为三大类型:
- 应用窗口(层级低)
- 系统窗口(层加高)
- 子窗口(层级取决于父窗口)
对于应用窗口总能被系统窗口遮盖,,用户常见的系统窗口主要是状态栏和导航栏,由于导航栏是承接系统级用户交互的主要部件,所以系统设计的主要原则是窗口尽量不能遮盖导航栏。
有了上面设计背景的了解,我们来看下PhoneWindowManager的实现。首先PhoneWindowManager定义了上面8个区域,用于限制窗口的范围,我们来一一介绍:
static final Rect mTmpParentFrame = new Rect();
static final Rect mTmpDisplayFrame = new Rect();
static final Rect mTmpOverscanFrame = new Rect();
static final Rect mTmpContentFrame = new Rect();
static final Rect mTmpVisibleFrame = new Rect();
static final Rect mTmpDecorFrame = new Rect();
static final Rect mTmpStableFrame = new Rect();
static final Rect mTmpOutsetFrame = new Rect();
- df 屏幕区域
- pf 父窗口区域(主要用于父窗口限制子窗口,对于非子窗口基本和df一致)
- of 过扫描区域内,不包括过扫描区域,但是不能被可见的导航栏截断
- cf 应用可以绘制主要内容的区域(一般不包括可见系统装饰区)
- vf 可见区域,应用可以根据vf滚动视图以使焦点控件不被输入法遮挡(不被输入法遮挡的cf区域)
- dcf 不包含不透明的状态栏的区域(主要用于防止装饰区和窗口过度绘制,裁剪surface)
- sf 不会受到任何装饰区影响的区域(全屏状态忽略状态栏,但是不能忽略任务栏)
- osf 是cf去除下巴的地方(比如很多手表有下巴,这部分区域无法显示,用osf表示此区域)
这些区域有的是用于限制窗口大小的,比如pf和df,窗口必须绘制在屏幕和父容器内。有的是告诉应用系统装饰区、输入法位置的比如cf、vf、sf。有的是告诉应用过扫描区边界比如of, 还有告知应用下巴区域的osf。 最后dcf是用于裁剪surface和不透明系统装饰区重叠部分的,防止过度渲染。
那么系统如何计算这些区域呢,PhoneWindowManager定义了如下变量
// The current size of the screen; really; extends into the overscan area of
// the screen and doesn't account for any system elements like the status bar.
int mOverscanScreenLeft, mOverscanScreenTop;
int mOverscanScreenWidth, mOverscanScreenHeight;
// The current visible size of the screen; really; (ir)regardless of whether the status
// bar can be hidden but not extending into the overscan area.
int mUnrestrictedScreenLeft, mUnrestrictedScreenTop;
int mUnrestrictedScreenWidth, mUnrestrictedScreenHeight;
// Like mOverscanScreen*, but allowed to move into the overscan region where appropriate.
int mRestrictedOverscanScreenLeft, mRestrictedOverscanScreenTop;
int mRestrictedOverscanScreenWidth, mRestrictedOverscanScreenHeight;
// The current size of the screen; these may be different than (0,0)-(dw,dh)
// if the status bar can't be hidden; in that case it effectively carves out
// that area of the display from all other windows.
int mRestrictedScreenLeft, mRestrictedScreenTop;
int mRestrictedScreenWidth, mRestrictedScreenHeight;
// During layout, the current screen borders accounting for any currently
// visible system UI elements.
int mSystemLeft, mSystemTop, mSystemRight, mSystemBottom;
// For applications requesting stable content insets, these are them.
int mStableLeft, mStableTop, mStableRight, mStableBottom;
// For applications requesting stable content insets but have also set the
// fullscreen window flag, these are the stable dimensions without the status bar.
int mStableFullscreenLeft, mStableFullscreenTop;
int mStableFullscreenRight, mStableFullscreenBottom;
// During layout, the current screen borders with all outer decoration
// (status bar, input method dock) accounted for.
int mCurLeft, mCurTop, mCurRight, mCurBottom;
// During layout, the frame in which content should be displayed
// to the user, accounting for all screen decoration except for any
// space they deem as available for other content. This is usually
// the same as mCur*, but may be larger if the screen decor has supplied
// content insets.
int mContentLeft, mContentTop, mContentRight, mContentBottom;
// During layout, the frame in which voice content should be displayed
// to the user, accounting for all screen decoration except for any
// space they deem as available for other content.
int mVoiceContentLeft, mVoiceContentTop, mVoiceContentRight, mVoiceContentBottom;
// During layout, the current screen borders along which input method
// windows are placed.
int mDockLeft, mDockTop, mDockRight, mDockBottom;
// During layout, the layer at which the doc window is placed.
int mDockLayer;
我准备了四张图描述这些区域,分别是在装饰区透明、装饰区不透明和装饰区隐藏的情况,后面代码分析的时候请对着这些图去看,但是一定要理解设计的思想:
图示比较清楚,所有的区域都描述成一个矩形,我说一下这里面包含的准则:
-
Stable区域不包含任何装饰区,即使装饰区不可见也按照导装饰区可见来设置Stable区域,它的作用是告知应用一个稳定区域,如果应用将内容绘制在此区域内(这完全取决于应用),在装饰区从不可见到可见的过程中,不会引起应用内容的跳动。
-
Dock区域描述的是输入法窗口能放置的区域,不包含可见导航栏, 标准就是无论如何不能覆盖可见装饰区,这很好理解,输入法不能覆盖到导航栏和状态栏
-
Content区域在没有输入法的情况是和Dock*区域一样的,也就是内容不能绘制在装饰区,在有输入法的情况下,应该是在dock区域内,但是不包含输入法content区域
-
Cur在没有输入法的情况是和Dock*区域一样的,在有输入法的情况下,不能包含输入法Visiable区域,这用于描述应用的Visiable 区域
-
System 是不被装饰区遮挡区域,用于计算dcf(不透明装饰区)
这些是PhoneWindowManager的内部定义,外部如何告知系统对装饰区的要求呢,比如隐藏状态栏,显示状态栏? 最好的说明还是官方文档。 请仔细阅读google的官方文档启用全屏模式。
简单来说隐藏状态栏在Android 4.0 及更低版本中使用WindowManager.LayoutParams.FLAG_FULLSCREEN 标志, 更高版本则使用View.SYSTEM_UI_FLAG_FULLSCREEN 标志。 (注意后者并不是Window的flag)。
导航栏的隐藏到Android 4.0开始支持,使用View.SYSTEM_UI_FLAG_HIDE_NAVIGATION 标志。
另外还有几个和PhoneWindowManager 布局相关的属性:
// 表示应用窗口希望填满整个屏幕,应用自行根据cf、of、vf、sf来解决遮挡问题, 没有该标志表示窗口希望在装饰区内部
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
// 表示应用希望得到装饰区的内边距, 同FLAG_LAYOUT_IN_SCREEN一起使用才有意义
WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR
// 希望窗口大小不受屏幕大小限制
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
// 希望应用使用过扫描区域,也就是不希望系统告知应用过扫描区范围
WindowManager.FLAG_LAYOUT_IN_OVERSCAN
// 像隐藏状态栏一样计算该窗口布局(还有个隐含意思就是窗口希望填满整个屏幕)
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
// 表示内容区希望cf 和 sf一样,这样在状态栏和导航栏出现的时候内容不至于跳动
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
// 像隐藏导航栏和状态栏一样layout,主要影响cf(还有个隐含意思就是窗口希望填满整个屏幕)
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
看了这么多相信你已经大概明白了PhoneWindowManager布局部分的设计思想了,我们再简单过一遍代码。
布局的第一部分是确定装饰区所占的位置
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
/** {@inheritDoc} */
@Override
public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
int displayRotation) {
mDisplayRotation = displayRotation;
final int overscanLeft, overscanTop, overscanRight, overscanBottom;
if (isDefaultDisplay) {
switch (displayRotation) {
case Surface.ROTATION_90:
overscanLeft = mOverscanTop;
overscanTop = mOverscanRight;
overscanRight = mOverscanBottom;
overscanBottom = mOverscanLeft;
break;
case Surface.ROTATION_180:
overscanLeft = mOverscanRight;
overscanTop = mOverscanBottom;
overscanRight = mOverscanLeft;
overscanBottom = mOverscanTop;
break;
case Surface.ROTATION_270:
overscanLeft = mOverscanBottom;
overscanTop = mOverscanLeft;
overscanRight = mOverscanTop;
overscanBottom = mOverscanRight;
break;
default:
overscanLeft = mOverscanLeft;
overscanTop = mOverscanTop;
overscanRight = mOverscanRight;
overscanBottom = mOverscanBottom;
break;
}
} else {
overscanLeft = 0;
overscanTop = 0;
overscanRight = 0;
overscanBottom = 0;
}
mOverscanScreenLeft = mRestrictedOverscanScreenLeft = 0;
mOverscanScreenTop = mRestrictedOverscanScreenTop = 0;
mOverscanScreenWidth = mRestrictedOverscanScreenWidth = displayWidth;
mOverscanScreenHeight = mRestrictedOverscanScreenHeight = displayHeight;
mSystemLeft = 0;
mSystemTop = 0;
mSystemRight = displayWidth;
mSystemBottom = displayHeight;
// UnrestrictedScreen区域不包含过扫描区
mUnrestrictedScreenLeft = overscanLeft;
mUnrestrictedScreenTop = overscanTop;
mUnrestrictedScreenWidth = displayWidth - overscanLeft - overscanRight;
mUnrestrictedScreenHeight = displayHeight - overscanTop - overscanBottom;
// RestrictedScreen区域默认和UnrestrictedScreen区域一样,因为现在还不知道装饰区占用空间
mRestrictedScreenLeft = mUnrestrictedScreenLeft;
mRestrictedScreenTop = mUnrestrictedScreenTop;
mRestrictedScreenWidth = mSystemGestures.screenWidth = mUnrestrictedScreenWidth;
mRestrictedScreenHeight = mSystemGestures.screenHeight = mUnrestrictedScreenHeight;
// Dock 、 Content 、Stable 、 StableFullscreen、 Cur 和 UnrestrictedScreen 一样,因为现在还不知道 装饰区占用空间
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;
mDockLayer = 0x10000000;
mStatusBarLayer = -1;
// start with the current dock rect, which will be (0,0,displayWidth,displayHeight)
final Rect pf = mTmpParentFrame;
final Rect df = mTmpDisplayFrame;
final Rect of = mTmpOverscanFrame;
final Rect vf = mTmpVisibleFrame;
final Rect dcf = mTmpDecorFrame;
final Rect osf = mTmpOutsetFrame;
// pf、df、vf 、of 当前就是UnrestrictedScreen区域,也就是不包含过扫描区
pf.left = df.left = of.left = vf.left = mDockLeft;
pf.top = df.top = of.top = vf.top = mDockTop;
pf.right = df.right = of.right = vf.right = mDockRight;
pf.bottom = df.bottom = of.bottom = vf.bottom = mDockBottom;
dcf.setEmpty(); // Decor frame N/A for system bars.
if (isDefaultDisplay) {
// 只有默认屏幕有装饰区,其他屏幕都使用Unrestricted区域作为限制
// For purposes of putting out fake window up to steal focus, we will
// drive nav being hidden only by whether it is requested.
final int sysui = mLastSystemUiFlags; // 获取当前装饰区状态
// 导航区可见
boolean navVisible = (sysui & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
// 导航区透明状态
boolean navTranslucent = (sysui
& (View.NAVIGATION_BAR_TRANSLUCENT | View.SYSTEM_UI_TRANSPARENT)) != 0;
// 沉浸模式
boolean immersive = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0;
// 粘性沉浸模式
boolean immersiveSticky = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
// 只有沉浸模式才允许隐藏导航栏
boolean navAllowedHidden = immersive || immersiveSticky;
// 沉浸状态相当于状态栏不存在
navTranslucent &= !immersiveSticky; // transient trumps translucent
boolean isKeyguardShowing = isStatusBarKeyguard() && !mHideLockScreen;
if (!isKeyguardShowing) {
navTranslucent &= areTranslucentBarsAllowed();
}
// When the navigation bar isn't visible, we put up a fake
// input window to catch all touch events. This way we can
// detect when the user presses anywhere to bring back the nav
// bar and ensure the application doesn't see the event.
if (navVisible || navAllowedHidden) {
// mInputConsumer 用于状态栏隐藏的时候响应交互唤出状态栏的, 这两种情况有其他办法唤出
if (mInputConsumer != null) {
mInputConsumer.dismiss();
mInputConsumer = null;
}
} else if (mInputConsumer == null) {
mInputConsumer = mWindowManagerFuncs.addInputConsumer(mHandler.getLooper(),
mHideNavInputEventReceiverFactory);
}
// For purposes of positioning and showing the nav bar, if we have
// decided that it can't be hidden (because of the screen aspect ratio),
// then take that into account.
navVisible |= !canHideNavigationBar(); // 不允许隐藏导航栏,必须为显示状态
boolean updateSysUiVisibility = false;
if (mNavigationBar != null) {
//导航栏 是否为瞬态
boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
// Force the navigation bar to its appropriate place and
// size. We need to do this directly, instead of relying on
// it to bubble up from the nav bar, because this needs to
// change atomically with screen rotations.
// 导航栏是否在屏幕底部(横屏的时候在右边)
mNavigationBarOnBottom = (!mNavigationBarCanMove || displayWidth < displayHeight);
if (mNavigationBarOnBottom) { // 在顶部的情况
// It's a system nav bar or a portrait screen; nav bar goes on bottom.
int top = displayHeight - overscanBottom
- mNavigationBarHeightForRotation[displayRotation];
// 设置在屏幕上的位置,也就是UnrestrictedScreen的底部
mTmpNavigationFrame.set(0, top, displayWidth, displayHeight - overscanBottom);
mStableBottom = mStableFullscreenBottom = mTmpNavigationFrame.top; // 不论是否可见,mStableBottom 和 mStableFullscreenBottom 都不包含导航区
if (transientNavBarShowing) { // 瞬态显示,设置显示状态,另外瞬态在布局中相当于导航栏不存在
mNavigationBarController.setBarShowingLw(true);
} else if (navVisible) {// 正常显示
mNavigationBarController.setBarShowingLw(true);
// 设置mDockBottom 为导航栏顶部,使Dock区域不遮挡导航栏
mDockBottom = mTmpNavigationFrame.top;
// RestrictedScreen 和RestrictedOverscanScreen 区域不包含导航栏
mRestrictedScreenHeight = mDockBottom - mRestrictedScreenTop;
mRestrictedOverscanScreenHeight = mDockBottom - mRestrictedOverscanScreenTop;
} else {
// 设置导航栏不可见
// We currently want to hide the navigation UI.
mNavigationBarController.setBarShowingLw(false);
}
if (navVisible && !navTranslucent && !navAllowedHidden
&& !mNavigationBar.isAnimatingLw()
&& !mNavigationBarController.wasRecentlyTranslucent()) {
// If the opaque nav bar is currently requested to be visible,
// and not in the process of animating on or off, then
// we can tell the app that it is covered by it.
// 导航栏不透明显示,SystemBottom在导航栏之上,用于防止过度绘制裁剪到导航栏
mSystemBottom = mTmpNavigationFrame.top;
}
} else {
// 导航栏在右侧,基本逻辑和在底部一样
// Landscape screen; nav bar goes to the right.
int left = displayWidth - overscanRight
- mNavigationBarWidthForRotation[displayRotation];
mTmpNavigationFrame.set(left, 0, displayWidth - overscanRight, displayHeight);
mStableRight = mStableFullscreenRight = mTmpNavigationFrame.left;
if (transientNavBarShowing) {
mNavigationBarController.setBarShowingLw(true);
} else if (navVisible) {
mNavigationBarController.setBarShowingLw(true);
mDockRight = mTmpNavigationFrame.left;
mRestrictedScreenWidth = mDockRight - mRestrictedScreenLeft;
mRestrictedOverscanScreenWidth = mDockRight - mRestrictedOverscanScreenLeft;
} else {
// We currently want to hide the navigation UI.
mNavigationBarController.setBarShowingLw(false);
}
if (navVisible && !navTranslucent && !navAllowedHidden
&& !mNavigationBar.isAnimatingLw()
&& !mNavigationBarController.wasRecentlyTranslucent()) {
// If the nav bar is currently requested to be visible,
// and not in the process of animating on or off, then
// we can tell the app that it is covered by it.
mSystemRight = mTmpNavigationFrame.left;
}
}
// Make sure the content and current rectangles are updated to
// account for the restrictions from the navigation bar.
// 确保更新内容和当前矩形以考虑导航栏的限制。, 这里设置Content、VoiceContent 和Cur* 都和Dock一致,也就是不包含可见导航栏
mContentTop = mVoiceContentTop = mCurTop = mDockTop;
mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom;
mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft;
mContentRight = mVoiceContentRight = mCurRight = mDockRight;
mStatusBarLayer = mNavigationBar.getSurfaceLayer();
// And compute the final frame.
mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame,
mTmpNavigationFrame, mTmpNavigationFrame, mTmpNavigationFrame, dcf,
mTmpNavigationFrame, mTmpNavigationFrame);
if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + mTmpNavigationFrame);
if (mNavigationBarController.checkHiddenLw()) {
updateSysUiVisibility = true;
}
}
if (DEBUG_LAYOUT) Slog.i(TAG, String.format("mDock rect: (%d,%d - %d,%d)",
mDockLeft, mDockTop, mDockRight, mDockBottom));
// 前边处理了导航栏这里该处理状态栏了
// decide where the status bar goes ahead of time
if (mStatusBar != null) {
// 这里重新初始化pf、of、df,将statusbar窗口约束在Unrestricted区域,vf则去除导航栏
// apply any navigation bar insets
pf.left = df.left = of.left = mUnrestrictedScreenLeft;
pf.top = df.top = of.top = mUnrestrictedScreenTop;
pf.right = df.right = of.right = mUnrestrictedScreenWidth + mUnrestrictedScreenLeft;
pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenHeight
+ mUnrestrictedScreenTop;
vf.left = mStableLeft;
vf.top = mStableTop;
vf.right = mStableRight;
vf.bottom = mStableBottom;
mStatusBarLayer = mStatusBar.getSurfaceLayer();
// Let the status bar determine its size.
mStatusBar.computeFrameLw(pf /* parentFrame */, df /* displayFrame */,
vf /* overlayFrame */, vf /* contentFrame */, vf /* visibleFrame */,
dcf /* decorFrame */, vf /* stableFrame */, vf /* outsetFrame */);
// For layout, the status bar is always at the top with our fixed height.
// Stable 区域总是去除状态栏高度
mStableTop = mUnrestrictedScreenTop + mStatusBarHeight;
boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
boolean statusBarTranslucent = (sysui
& (View.STATUS_BAR_TRANSLUCENT | View.SYSTEM_UI_TRANSPARENT)) != 0;
if (!isKeyguardShowing) {
statusBarTranslucent &= areTranslucentBarsAllowed();
}
// If the status bar is hidden, we don't want to cause
// windows behind it to scroll.
// statusBar 可见,并且不是瞬态
if (mStatusBar.isVisibleLw() && !statusBarTransient) {
// Status bar may go away, so the screen area it occupies
// is available to apps but just covering them when the
// status bar is visible.
// 这种情况Dock、Content、VoiceContent总是不包含状态栏(很好理解,输入法和应用内容都应该避开状态栏绘制,瞬态相当于状态栏不存在)
mDockTop = mUnrestrictedScreenTop + mStatusBarHeight;
mContentTop = mVoiceContentTop = mCurTop = mDockTop;
mContentBottom = mVoiceContentBottom = mCurBottom = mDockBottom;
mContentLeft = mVoiceContentLeft = mCurLeft = mDockLeft;
mContentRight = mVoiceContentRight = mCurRight = mDockRight;
if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar: " +
String.format(
"dock=[%d,%d][%d,%d] content=[%d,%d][%d,%d] cur=[%d,%d][%d,%d]",
mDockLeft, mDockTop, mDockRight, mDockBottom,
mContentLeft, mContentTop, mContentRight, mContentBottom,
mCurLeft, mCurTop, mCurRight, mCurBottom));
}
if (mStatusBar.isVisibleLw() && !mStatusBar.isAnimatingLw()
&& !statusBarTransient && !statusBarTranslucent
&& !mStatusBarController.wasRecentlyTranslucent()) {
// If the opaque status bar is currently requested to be visible,
// and not in the process of animating on or off, then
// we can tell the app that it is covered by it.
// 不透明状态栏设置mSystemTop 用于surface裁剪
mSystemTop = mUnrestrictedScreenTop + mStatusBarHeight;
}
if (mStatusBarController.checkHiddenLw()) {
updateSysUiVisibility = true;
}
}
if (updateSysUiVisibility) {
updateSystemUiVisibilityLw();
}
}
}
beginLayoutLw 函数的逻辑很简单,在layout所有窗口的时候先确定装饰区,也就是StatusBar 和 NavBar的可见性。根据前面介绍的逻辑, 这和我前面画的三张图是一致的。
beginLayoutLw 在一轮layout中执行一次, 执行完beginLayoutLw之后会对每个可见窗口执行layoutWindowLw 来为每一个窗口进行布局(确定大小、稳定区、可见区、内容区和位置),我们来看一下layoutWindowLw的代码
frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
/** {@inheritDoc} */
@Override
public void layoutWindowLw(WindowState win, WindowState attached) {
// We've already done the navigation bar and status bar. If the status bar can receive
// input, we need to layout it again to accomodate for the IME window.
// statusBar 被展开的时候可以接收输入法,还需要重新layout。 其他情况StatusBar 和 NavBar已经layout过了,直接返回
if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar) {
return;
}
final WindowManager.LayoutParams attrs = win.getAttrs();
final boolean isDefaultDisplay = win.isDefaultDisplay();
final boolean needsToOffsetInputMethodTarget = isDefaultDisplay &&
(win == mLastInputMethodTargetWindow && mLastInputMethodWindow != null);
if (needsToOffsetInputMethodTarget) {
if (DEBUG_LAYOUT) Slog.i(TAG, "Offset ime target window by the last ime window state");
// 根据输入法窗口调整Current、Content(也就是排除掉输入法的内容区和可见区)
offsetInputMethodWindowLw(mLastInputMethodWindow);
}
final int fl = PolicyControl.getWindowFlags(win, attrs);
final int sim = attrs.softInputMode;
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;
Rect osf = null;
dcf.setEmpty();
final boolean hasNavBar = (isDefaultDisplay && mHasNavigationBar
&& mNavigationBar != null && mNavigationBar.isVisibleLw());
final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
if (isDefaultDisplay) {
// 默认屏幕有装饰区,设置稳定区
sf.set(mStableLeft, mStableTop, mStableRight, mStableBottom);
} else {
// 非默认屏幕没有装饰区,设置稳定区为全屏幕
sf.set(mOverscanLeft, mOverscanTop, mOverscanRight, mOverscanBottom);
}
if (!isDefaultDisplay) {
if (attached != null) {
// 非默认窗口如果是子窗口则根据父窗口限制子窗口大小
// If this window is attached to another, our display
// frame is the same as the one we are attached to.
setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf);
} else {
// 非子窗口设置全屏
// Give the window full screen.
pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
pf.right = df.right = of.right = cf.right
= mOverscanScreenLeft + mOverscanScreenWidth;
pf.bottom = df.bottom = of.bottom = cf.bottom
= mOverscanScreenTop + mOverscanScreenHeight;
}
} else if (attrs.type == TYPE_INPUT_METHOD) {
// 输入法窗口 窗口限制在dock区域(也就是不含很可见的装饰区)
pf.left = df.left = of.left = cf.left = vf.left = mDockLeft;
pf.top = df.top = of.top = cf.top = vf.top = mDockTop;
pf.right = df.right = of.right = cf.right = vf.right = mDockRight;
// IM dock windows layout below the nav bar...
pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
// ...with content insets above the nav bar
// 输入法的内容区和可见区都在 stable区域,并且在屏幕下面
cf.bottom = vf.bottom = mStableBottom;
// IM dock windows always go to the bottom of the screen.
attrs.gravity = Gravity.BOTTOM;
mDockLayer = win.getSurfaceLayer();
} else if (attrs.type == TYPE_VOICE_INTERACTION) {
// 语音交互窗口,窗口大小被限制不包含过扫描区
pf.left = df.left = of.left = mUnrestrictedScreenLeft;
pf.top = df.top = of.top = mUnrestrictedScreenTop;
pf.right = df.right = of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
// 内容区和可见区都在Stable*内
cf.bottom = vf.bottom = mStableBottom;
// Note: In Phone landscape mode, the button bar should also be excluded.
cf.right = vf.right = mStableRight;
cf.left = vf.left = mStableLeft;
cf.top = vf.top = mStableTop;
} else if (win == mStatusBar) {
// statusBar窗口在Unrestricted内, 内容区和可见区上左右和Stable一样,底部涉及到导航栏,可见区域不覆盖导航栏,但是内容区可以覆盖导航栏? 这好像没什么道理
pf.left = df.left = of.left = mUnrestrictedScreenLeft;
pf.top = df.top = of.top = mUnrestrictedScreenTop;
pf.right = df.right = of.right = mUnrestrictedScreenWidth + mUnrestrictedScreenLeft;
pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenHeight + mUnrestrictedScreenTop;
cf.left = vf.left = mStableLeft;
cf.top = vf.top = mStableTop;
cf.right = vf.right = mStableRight;
vf.bottom = mStableBottom;
cf.bottom = mContentBottom;
} else {
// 其他类型窗口
// Default policy decor for the default display
dcf.left = mSystemLeft;
dcf.top = mSystemTop;
dcf.right = mSystemRight;
dcf.bottom = mSystemBottom;
final boolean inheritTranslucentDecor = (attrs.privateFlags
& WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) != 0;
final boolean isAppWindow =
attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW &&
attrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
final boolean topAtRest =
win == mTopFullscreenOpaqueWindowState && !win.isAnimatingLw();
if (isAppWindow && !inheritTranslucentDecor && !topAtRest) {
// 非全屏AppWindow
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) {
//不透明导可见状态栏, 并且也不绘制状态栏背景。 设置dcf不包含状态栏(用于防止过度绘制裁剪状态栏)
// Ensure policy decor includes status bar
dcf.top = mStableTop;
}
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不包含导航栏(用于防止过度绘制裁剪导航栏)
dcf.bottom = mStableBottom;
dcf.right = mStableRight;
}
}
if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR))
== (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {
if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+ "): IN_SCREEN, INSET_DECOR");
// FLAG_LAYOUT_IN_SCREEN 表示应用窗口希望填满整个屏幕,所以后面会尽量把窗口的pf、df、of 都放置在整个屏幕上,也就是包含过扫描区
// FLAG_LAYOUT_INSET_DECOR 表示应用希望将内容绘制在装饰区内(不要和装饰区重叠),所以cf、vf要避开装饰区
// This is the case for a normal activity window: we want it
// to cover all of the screen space, and it can take care of
// moving its contents to account for screen decorations that
// intrude into that space.
if (attached != null) {
// If this window is attached to another, our display
// frame is the same as the one we are attached to.
// 子窗口,根据父窗口设置布局
setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf);
} else { // 非子窗口
if (attrs.type == TYPE_STATUS_BAR_PANEL
|| attrs.type == TYPE_STATUS_BAR_SUB_PANEL) {
// statusbar panel窗口,窗口不能遮盖导航栏,但是能覆盖状态栏,所以如果有导航栏则使用dock区域,没有导航栏则使用Unrestricted区域,装饰窗口是不会渲染到过扫描区的
// Status bar panels are the only windows who can go on top of
// the status bar. They are protected by the STATUS_BAR_SERVICE
// permission, so they have the same privileges as the status
// bar itself.
//
// However, they should still dodge the navigation bar if it exists.
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;
if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
"Laying out status bar window: (%d,%d - %d,%d)",
pf.left, pf.top, pf.right, pf.bottom));
} else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
&& attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
&& attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
// Asking to layout into the overscan region, so give it that pure
// unrestricted area.
// FLAG_LAYOUT_IN_OVERSCAN 标志表示应用自己要处理过扫描区,of就是用于告诉应用过扫描区的边界,这里设置了FLAG_LAYOUT_IN_OVERSCAN,所以of设置为全屏大小,忽略过扫描区。 注意只有子窗口和应用窗口可以这样做,因为它们在装饰区下面或者被父窗口限制
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) {
// Asking for layout as if the nav bar is hidden, lets the
// application extend into the unrestricted overscan screen area. We
// only do this for application windows to ensure no window that
// can be above the nav bar can do this.
// 导航栏隐藏的情况窗口可以占用整个屏幕
pf.left = df.left = mOverscanScreenLeft;
pf.top = df.top = mOverscanScreenTop;
pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth;
pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight;
// We need to tell the app about where the frame inside the overscan
// is, so it can inset its content by that amount -- it didn't ask
// to actually extend itself into the overscan region.
of.left = mUnrestrictedScreenLeft;
of.top = mUnrestrictedScreenTop;
of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
} else {
// 1 导航栏可见,不是statusbar,没设置FLAG_LAYOUT_IN_OVERSCAN,那窗口应该不能包括导航区,所以pf、df 都被设置在RestrictedOverscan区域内, of还是准确描述过扫描区边界
pf.left = df.left = mRestrictedOverscanScreenLeft;
pf.top = df.top = mRestrictedOverscanScreenTop;
pf.right = df.right = mRestrictedOverscanScreenLeft
+ mRestrictedOverscanScreenWidth;
pf.bottom = df.bottom = mRestrictedOverscanScreenTop
+ mRestrictedOverscanScreenHeight;
// We need to tell the app about where the frame inside the overscan
// is, so it can inset its content by that amount -- it didn't ask
// to actually extend itself into the overscan region.
of.left = mUnrestrictedScreenLeft;
of.top = mUnrestrictedScreenTop;
of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
}
// 上面设置完了pf、df、of对于窗口大小的限制,后面要设置cf、vf来告知应用装饰区的情况
if ((fl & FLAG_FULLSCREEN) == 0) {
if (win.isVoiceInteraction()) {
// 状态栏可见的语音交互窗口(不显示输入法),设置内容区域为VoiceContent区域
cf.left = mVoiceContentLeft;
cf.top = mVoiceContentTop;
cf.right = mVoiceContentRight;
cf.bottom = mVoiceContentBottom;
} else {
// 不隐藏状态栏
if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
// 不需要根据输入法调整窗口,设置应用内容区为Docker区域,也就是不包含可见的状态栏和导航栏
cf.left = mDockLeft;
cf.top = mDockTop;
cf.right = mDockRight;
cf.bottom = mDockBottom;
} else {
// 需要根据输入法调整窗口大小Content区域已经是调整过得了,所以设置cf边界为Content区域
cf.left = mContentLeft;
cf.top = mContentTop;
cf.right = mContentRight;
cf.bottom = mContentBottom;
}
}
} else {
// Full screen windows are always given a layout that is as if the
// status bar and other transient decors are gone. This is to avoid
// bad states when moving from a window that is not hding the
// status bar to one that is.
// 全屏窗口,cf都限制为Restricted区域,也就是不包含导航栏,但是包含状态栏
cf.left = mRestrictedScreenLeft;
cf.top = mRestrictedScreenTop;
cf.right = mRestrictedScreenLeft + mRestrictedScreenWidth;
cf.bottom = mRestrictedScreenTop + mRestrictedScreenHeight;
}
applyStableConstraints(sysUiFl, fl, cf); // 设置cf区域,后面分析,这个函数主要处理View.SYSTEM_UI_FLAG_LAYOUT_STABLE标志,如果设置了View.SYSTEM_UI_FLAG_LAYOUT_STABLE标志,cf应该使用stable区域,防止状态栏和导航来出来内容跳动
if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
// 需要根据输入法调整窗口(上移内容或者滚动内容,都需要知道可见区,尤其滚动内容), 所以cf设置为Cur区域,Cur区域是减去输入法可见区域的屏幕区域
vf.left = mCurLeft;
vf.top = mCurTop;
vf.right = mCurRight;
vf.bottom = mCurBottom;
} else {
// 不需要根据输入法调整窗口或者滚动窗口里的内容,设置vf和cf一致
vf.set(cf);
}
}
} else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0 || (sysUiFl
& (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) {
// 如论设置了 FLAG_LAYOUT_IN_SCREEN| SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION,都表示应用希望填满整个窗口,但是这里不需要考虑装饰区,因为没有 FLAG_LAYOUT_INSET_DECOR标志,注意FLAG_LAYOUT_INSET_DECOR标志是和FLAG_LAYOUT_IN_SCREEN一起用才有用,所以后面cf,vf都应该不再考虑装饰区
if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
"): IN_SCREEN");
// A window that has requested to fill the entire screen just
// gets everything, period.
if (attrs.type == TYPE_STATUS_BAR_PANEL
|| attrs.type == TYPE_STATUS_BAR_SUB_PANEL
|| attrs.type == TYPE_VOLUME_OVERLAY) {
// statusBar panel 窗口 和 音量调节窗口都不应该和导航栏重合,也不使用过扫描区,所以根据导航栏设置pf、df、of 的区域为Dock 或者 Unrestricted
pf.left = df.left = of.left = cf.left = hasNavBar
? mDockLeft : mUnrestrictedScreenLeft;
pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
pf.right = df.right = of.right = cf.right = hasNavBar
? mRestrictedScreenLeft+mRestrictedScreenWidth
: mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
pf.bottom = df.bottom = of.bottom = cf.bottom = hasNavBar
? mRestrictedScreenTop+mRestrictedScreenHeight
: mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
"Laying out IN_SCREEN status bar window: (%d,%d - %d,%d)",
pf.left, pf.top, pf.right, pf.bottom));
} else if (attrs.type == TYPE_NAVIGATION_BAR
|| attrs.type == TYPE_NAVIGATION_BAR_PANEL) {
// 导航窗口或者导航panel, 可以包含导航区域,但是不适用过扫描区
// The navigation bar has Real Ultimate Power.
pf.left = df.left = of.left = mUnrestrictedScreenLeft;
pf.top = df.top = of.top = mUnrestrictedScreenTop;
pf.right = df.right = of.right = mUnrestrictedScreenLeft
+ mUnrestrictedScreenWidth;
pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop
+ mUnrestrictedScreenHeight;
if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
"Laying out navigation bar window: (%d,%d - %d,%d)",
pf.left, pf.top, pf.right, pf.bottom));
} else if ((attrs.type == TYPE_SECURE_SYSTEM_OVERLAY
|| attrs.type == TYPE_BOOT_PROGRESS)
&& ((fl & FLAG_FULLSCREEN) != 0)) {
// 隐藏状态栏安全窗口或者启动进度窗口, 可以使用整个屏幕
// Fullscreen secure system overlays get what they ask for.
pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
pf.right = df.right = of.right = cf.right = mOverscanScreenLeft
+ mOverscanScreenWidth;
pf.bottom = df.bottom = of.bottom = cf.bottom = mOverscanScreenTop
+ mOverscanScreenHeight;
} else if (attrs.type == TYPE_BOOT_PROGRESS) {
// 启动进度窗口默认使用整个屏幕
// Boot progress screen always covers entire display.
pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
pf.right = df.right = of.right = cf.right = mOverscanScreenLeft
+ mOverscanScreenWidth;
pf.bottom = df.bottom = of.bottom = cf.bottom = mOverscanScreenTop
+ mOverscanScreenHeight;
} else if (attrs.type == TYPE_WALLPAPER) {
// The wallpaper also has Real Ultimate Power, but we want to tell
// it about the overscan area.
// 壁纸窗口可以使用这个屏幕,但是还是通过cf和of告知过扫描区域范围
pf.left = df.left = mOverscanScreenLeft;
pf.top = df.top = mOverscanScreenTop;
pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth;
pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight;
of.left = cf.left = mUnrestrictedScreenLeft;
of.top = cf.top = mUnrestrictedScreenTop;
of.right = cf.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;
of.bottom = cf.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
} else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
&& attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
&& attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
// Asking to layout into the overscan region, so give it that pure
// unrestricted area.
// 设置了FLAG_LAYOUT_IN_OVERSCAN,应用窗口或者子窗口,由于其在装饰区下面,或者和父窗口属性一致,所以允许允许使用整个屏幕,包括cf(因为这里没有设置FLAG_LAYOUT_INSET_DECOR),所以cf可以包括过扫描区
pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
pf.right = df.right = of.right = cf.right
= mOverscanScreenLeft + mOverscanScreenWidth;
pf.bottom = df.bottom = of.bottom = cf.bottom
= mOverscanScreenTop + mOverscanScreenHeight;
} else if (canHideNavigationBar()
&& (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
&& (attrs.type == TYPE_STATUS_BAR
|| attrs.type == TYPE_TOAST
|| attrs.type == TYPE_VOICE_INTERACTION_STARTING
|| (attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW
&& attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW))) {
// Asking for layout as if the nav bar is hidden, lets the
// application extend into the unrestricted screen area. We
// only do this for application windows (or toasts) to ensure no window that
// can be above the nav bar can do this.
// XXX This assumes that an app asking for this will also
// ask for layout in only content. We can't currently figure out
// what the screen would be if only laying out to hide the nav bar.
// 这里没有设置FLAG_LAYOUT_IN_OVERSCAN标志,不允许窗口使用过扫描区
// 导航栏隐藏,应用窗口、状态栏,tost、语音交互这些系统窗口在屏幕下边,可以使pf、df、of、cf 延伸到UnrestrictedScreen区域。 也可以延伸到UnrestrictedScreen区域,并不会影响导航栏显示
pf.left = df.left = of.left = cf.left = mUnrestrictedScreenLeft;
pf.top = df.top = of.top = cf.top = mUnrestrictedScreenTop;
pf.right = df.right = of.right = cf.right = mUnrestrictedScreenLeft
+ mUnrestrictedScreenWidth;
pf.bottom = df.bottom = of.bottom = cf.bottom = mUnrestrictedScreenTop
+ mUnrestrictedScreenHeight;
} else {
// 导航栏可见的情况,只能使用RestrictedScreen区域作为pf、df、of、cf
// 这里虽然没有FLAG_LAYOUT_INSET_DECOR表示,不会向应用报告状态装饰区所占的空间,所以cf、pf、df、of都不允许占用装饰区
pf.left = df.left = of.left = cf.left = mRestrictedScreenLeft;
pf.top = df.top = of.top = cf.top = mRestrictedScreenTop;
pf.right = df.right = of.right = cf.right = mRestrictedScreenLeft
+ mRestrictedScreenWidth;
pf.bottom = df.bottom = of.bottom = cf.bottom = mRestrictedScreenTop
+ mRestrictedScreenHeight;
}
applyStableConstraints(sysUiFl, fl, cf); // 设置cf区域,后面分析,这个函数主要处理View.SYSTEM_UI_FLAG_LAYOUT_STABLE标志,如果设置了View.SYSTEM_UI_FLAG_LAYOUT_STABLE标志,cf应该使用stable区域,防止状态栏和导航来出来内容跳动
if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
vf.left = mCurLeft;
vf.top = mCurTop;
vf.right = mCurRight;
vf.bottom = mCurBottom;
} else {
vf.set(cf);
}
} else if (attached != null) {
if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
"): attached to " + attached);
// A child window should be placed inside of the same visible
// frame that its parent had.
// 子窗口根据父窗口layout
setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, of, cf, vf);
} else {
// 应用不希望窗口占用整个屏幕,窗口大小不应该包含装饰区
if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
"): normal window");
// Otherwise, a normal window must be placed inside the content
// of all screen decorations.
if (attrs.type == TYPE_STATUS_BAR_PANEL || attrs.type == TYPE_VOLUME_OVERLAY) {
// Status bar panels and the volume dialog are the only windows who can go on
// top of the status bar. They are protected by the STATUS_BAR_SERVICE
// permission, so they have the same privileges as the status
// bar itself.
// 使用RestrictedScreen, 不占用装饰区
pf.left = df.left = of.left = cf.left = mRestrictedScreenLeft;
pf.top = df.top = of.top = cf.top = mRestrictedScreenTop;
pf.right = df.right = of.right = cf.right = mRestrictedScreenLeft
+ mRestrictedScreenWidth;
pf.bottom = df.bottom = of.bottom = cf.bottom = mRestrictedScreenTop
+ mRestrictedScreenHeight;
} else if (attrs.type == TYPE_TOAST || attrs.type == TYPE_SYSTEM_ALERT) {
// TOAST 和 TYPE_SYSTEM_ALERT 使用Stable。避开装饰区
// These dialogs are stable to interim decor changes.
pf.left = df.left = of.left = cf.left = mStableLeft;
pf.top = df.top = of.top = cf.top = mStableTop;
pf.right = df.right = of.right = cf.right = mStableRight;
pf.bottom = df.bottom = of.bottom = cf.bottom = mStableBottom;
} else {
// 窗口pf使用 Content区域,避开可见的装饰区
pf.left = mContentLeft;
pf.top = mContentTop;
pf.right = mContentRight;
pf.bottom = mContentBottom;
if (win.isVoiceInteraction()) {
df.left = of.left = cf.left = mVoiceContentLeft;
df.top = of.top = cf.top = mVoiceContentTop;
df.right = of.right = cf.right = mVoiceContentRight;
df.bottom = of.bottom = cf.bottom = mVoiceContentBottom;
} else if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
df.left = of.left = cf.left = mDockLeft;
df.top = of.top = cf.top = mDockTop;
df.right = of.right = cf.right = mDockRight;
df.bottom = of.bottom = cf.bottom = mDockBottom;
} else {
df.left = of.left = cf.left = mContentLeft;
df.top = of.top = cf.top = mContentTop;
df.right = of.right = cf.right = mContentRight;
df.bottom = of.bottom = cf.bottom = mContentBottom;
}
if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
vf.left = mCurLeft;
vf.top = mCurTop;
vf.right = mCurRight;
vf.bottom = mCurBottom;
} else {
vf.set(cf);
}
}
}
}
// TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && attrs.type != TYPE_SYSTEM_ERROR) {
// FLAG_LAYOUT_NO_LIMITS 不限制窗口大小设置为10000
df.left = df.top = -10000;
df.right = df.bottom = 10000;
if (attrs.type != TYPE_WALLPAPER) {
of.left = of.top = cf.left = cf.top = vf.left = vf.top = -10000;
of.right = of.bottom = cf.right = cf.bottom = vf.right = vf.bottom = 10000;
}
}
// If the device has a chin (e.g. some watches), a dead area at the bottom of the screen we
// need to provide information to the clients that want to pretend that you can draw there.
// We only want to apply outsets to certain types of windows. For example, we never want to
// apply the outsets to floating dialogs, because they wouldn't make sense there.
final boolean useOutsets = shouldUseOutsets(attrs, fl);
if (isDefaultDisplay && useOutsets) {// 计算outsets,也就是下巴区域
osf = mTmpOutsetFrame;
osf.set(cf.left, cf.top, cf.right, cf.bottom);
int outset = ScreenShapeHelper.getWindowOutsetBottomPx(mContext.getResources());
if (outset > 0) {
int rotation = mDisplayRotation;
if (rotation == Surface.ROTATION_0) {
osf.bottom += outset;
} else if (rotation == Surface.ROTATION_90) {
osf.right += outset;
} else if (rotation == Surface.ROTATION_180) {
osf.top -= outset;
} else if (rotation == Surface.ROTATION_270) {
osf.left -= outset;
}
if (DEBUG_LAYOUT) Slog.v(TAG, "applying bottom outset of " + outset
+ " with rotation " + rotation + ", result: " + osf);
}
}
if (DEBUG_LAYOUT) Slog.v(TAG, "Compute frame " + attrs.getTitle()
+ ": sim=#" + Integer.toHexString(sim)
+ " attach=" + attached + " type=" + attrs.type
+ String.format(" flags=0x%08x", fl)
+ " pf=" + pf.toShortString() + " df=" + df.toShortString()
+ " of=" + of.toShortString()
+ " cf=" + cf.toShortString() + " vf=" + vf.toShortString()
+ " dcf=" + dcf.toShortString()
+ " sf=" + sf.toShortString()
+ " osf=" + (osf == null ? "null" : osf.toShortString()));
win.computeFrameLw(pf, df, of, cf, vf, dcf, sf, osf); // 计算出了pf、df、of、cf、vf、dcf、sf、osf, computeFrameLw函数计算窗口布局了
// Dock windows carve out the bottom of the screen, so normal windows
// can't appear underneath them.
if (attrs.type == TYPE_INPUT_METHOD && win.isVisibleOrBehindKeyguardLw()
&& !win.getGivenInsetsPendingLw()) {// 根据输入法调整Content,Cur
setLastInputMethodWindowLw(null, null);
offsetInputMethodWindowLw(win);
}
if (attrs.type == TYPE_VOICE_INTERACTION && win.isVisibleOrBehindKeyguardLw()
&& !win.getGivenInsetsPendingLw()) {
offsetVoiceInputWindowLw(win);
}
}
private void applyStableConstraints(int sysui, int fl, Rect r) {
if ((sysui & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
// SYSTEM_UI_FLAG_LAYOUT_STABLE 标志表示应用希望内容区是稳定的,如果设置了FLAG_FULLSCREEN表示要隐藏状态栏,使用StableFullscreen区域作为稳定的内容区域,否则使用Stable作为稳定的内容区域
// If app is requesting a stable layout, don't let the
// content insets go below the stable values.
if ((fl & FLAG_FULLSCREEN) != 0) {
if (r.left < mStableFullscreenLeft) r.left = mStableFullscreenLeft;
if (r.top < mStableFullscreenTop) r.top = mStableFullscreenTop;
if (r.right > mStableFullscreenRight) r.right = mStableFullscreenRight;
if (r.bottom > mStableFullscreenBottom) r.bottom = mStableFullscreenBottom;
} else {
if (r.left < mStableLeft) r.left = mStableLeft;
if (r.top < mStableTop) r.top = mStableTop;
if (r.right > mStableRight) r.right = mStableRight;
if (r.bottom > mStableBottom) r.bottom = mStableBottom;
}
}
}
private void offsetInputMethodWindowLw(WindowState win) {
// win是输入法窗口
int top = Math.max(win.getDisplayFrameLw().top, win.getContentFrameLw().top);
top += win.getGivenContentInsetsLw().top;
if (mContentBottom > top) {// Content底部在输入法窗口上边
mContentBottom = top;
}
if (mVoiceContentBottom > top) {
mVoiceContentBottom = top;
}
top = win.getVisibleFrameLw().top;
top += win.getGivenVisibleInsetsLw().top;
if (mCurBottom > top) { // Cur 在输入法可见区上边
mCurBottom = top;
}
if (DEBUG_LAYOUT) Slog.v(TAG, "Input method: mDockBottom="
+ mDockBottom + " mContentBottom="
+ mContentBottom + " mCurBottom=" + mCurBottom);
}
void setAttachedWindowFrames(WindowState win, int fl, int adjust, WindowState attached,
boolean insetDecors, Rect pf, Rect df, Rect of, Rect cf, Rect vf) {
if (win.getSurfaceLayer() > mDockLayer && attached.getSurfaceLayer() < mDockLayer) {
// Here's a special case: if this attached window is a panel that is
// above the dock window, and the window it is attached to is below
// the dock window, then the frames we computed for the window it is
// attached to can not be used because the dock is effectively part
// of the underlying window and the attached window is floating on top
// of the whole thing. So, we ignore the attached window and explicitly
// compute the frames that would be appropriate without the dock.
// 输入法在父窗口之上在子窗口之下, 子窗口不受输入法影响,使用Dock区域限制子窗口大小
df.left = of.left = cf.left = vf.left = mDockLeft;
df.top = of.top = cf.top = vf.top = mDockTop;
df.right = of.right = cf.right = vf.right = mDockRight;
df.bottom = of.bottom = cf.bottom = vf.bottom = mDockBottom;
} else {
// The effective display frame of the attached window depends on
// whether it is taking care of insetting its content. If not,
// we need to use the parent's content frame so that the entire
// window is positioned within that content. Otherwise we can use
// the overscan frame and let the attached window take care of
// positioning its content appropriately.
if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
// 子窗口不需要根据输入法调整大小, 根据标志FLAG_LAYOUT_ATTACHED_IN_DECOR限制子窗口内容区域是使用父窗口内容区域还是父窗口Overscan区域
// Set the content frame of the attached window to the parent's decor frame
// (same as content frame when IME isn't present) if specifically requested by
// setting {@link WindowManager.LayoutParams#FLAG_LAYOUT_ATTACHED_IN_DECOR} flag.
// Otherwise, use the overscan frame.
cf.set((fl & FLAG_LAYOUT_ATTACHED_IN_DECOR) != 0
? attached.getContentFrameLw() : attached.getOverscanFrameLw());
} else {
// If the window is resizing, then we want to base the content
// frame on our attached content frame to resize... however,
// things can be tricky if the attached window is NOT in resize
// mode, in which case its content frame will be larger.
// Ungh. So to deal with that, make sure the content frame
// we end up using is not covering the IM dock.
// 需要根据输入法调整子窗口大小,使用父窗口的Content 设置子窗口的cf
cf.set(attached.getContentFrameLw());
if (attached.isVoiceInteraction()) {
if (cf.left < mVoiceContentLeft) cf.left = mVoiceContentLeft;
if (cf.top < mVoiceContentTop) cf.top = mVoiceContentTop;
if (cf.right > mVoiceContentRight) cf.right = mVoiceContentRight;
if (cf.bottom > mVoiceContentBottom) cf.bottom = mVoiceContentBottom;
} else if (attached.getSurfaceLayer() < mDockLayer) {
// 子窗口在输入法下面。如果按照父窗口cf去设置,可能内容被输入法遮挡,所以这里再调整
if (cf.left < mContentLeft) cf.left = mContentLeft;
if (cf.top < mContentTop) cf.top = mContentTop;
if (cf.right > mContentRight) cf.right = mContentRight;
if (cf.bottom > mContentBottom) cf.bottom = mContentBottom;
}
}
df.set(insetDecors ? attached.getDisplayFrameLw() : cf);
of.set(insetDecors ? attached.getOverscanFrameLw() : cf);
vf.set(attached.getVisibleFrameLw());
}
// The LAYOUT_IN_SCREEN flag is used to determine whether the attached
// window should be positioned relative to its parent or the entire
// screen.
pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0
? attached.getFrameLw() : df);
}
关于layoutWindowLw 的解析都写在了注释里面,这里就不详细说了。layoutWindowLw主要根据装饰区计算出pf(父窗口对大小的限制)、df(屏幕对大小的限制)、of(Overscan的边界)、cf(内容应该放置的区域)、vf(内容可见的区域)、dcf(不透明装饰区的边界)、sf(稳定区域)、osf(下巴区域)。
最后我们看一下计算布局的实现
frameworks/base/services/core/java/com/android/server/wm/WindowState.java
@Override
public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf, Rect sf,
Rect osf) {
mHaveFrame = true;
final TaskStack stack = mAppToken != null ? getStack() : null;
final boolean nonFullscreenStack = stack != null && !stack.isFullscreen();
if (nonFullscreenStack) {
// 分屏窗口,必须被限制到栈的区域内
stack.getBounds(mContainingFrame);
final WindowState imeWin = mService.mInputMethodWindow;
if (imeWin != null && imeWin.isVisibleNow() && mService.mInputMethodTarget == this
&& mContainingFrame.bottom > cf.bottom) {
// IME is up and obscuring this window. Adjust the window position so it is visible.
mContainingFrame.top -= mContainingFrame.bottom - cf.bottom;
}
// Make sure the containing frame is within the content frame so we don't layout
// resized window under screen decorations.
if (!mContainingFrame.intersect(cf)) {
mContainingFrame.set(cf);
}
mDisplayFrame.set(mContainingFrame);
} else {
// 非分屏窗口mContainingFrame为父窗口大小,mDisplayFrame为屏幕窗口
mContainingFrame.set(pf);
mDisplayFrame.set(df);
}
final int pw = mContainingFrame.width();
final int ph = mContainingFrame.height();
// 如果设置了缩放,参考LayoutParam.with 和LayoutParam.height
// 缩放要设置宽高,如果没设置则按照MATCH_PARENT算
// 如果设置了宽高要 * mEnforceSizeCompat 进行缩放, mEnforceSizeCompat为false表示不需要缩放, 直接使用LayoutParam 给定的宽高
int w,h;
if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0) {
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 {
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;
}
}
// 更新mParentFrame
if (!mParentFrame.equals(pf)) {
//Slog.i(TAG, "Window " + this + " content frame from " + mParentFrame
// + " to " + pf);
mParentFrame.set(pf);
mContentChanged = true;
}
// 更新 mLastRequested*
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 boolean hasOutsets = osf != null;
if (hasOutsets) {
mOutsetFrame.set(osf);
}
final int fw = mFrame.width();
final int fh = mFrame.height();
// 计算xy顶点
float x, y;
if (mEnforceSizeCompat) {
x = mAttrs.x * mGlobalScale;
y = mAttrs.y * mGlobalScale;
} else {
x = mAttrs.x;
y = mAttrs.y;
}
if (nonFullscreenStack) {
// Make sure window fits in containing frame since it is in a non-fullscreen stack as
// required by {@link Gravity#apply} call.
// required by {@link Gravity#apply} call.
// 非全屏堆栈应该在pw 和 ph中
w = Math.min(w, pw);
h = Math.min(h, ph);
}
// 根据父窗口和 gravity 计算窗口大小位置, mFrame为输出参数
Gravity.apply(mAttrs.gravity, w, h, mContainingFrame,
(int) (x + mAttrs.horizontalMargin * pw),
(int) (y + mAttrs.verticalMargin * ph), mFrame);
// 根据屏幕限制和 gravity 计算窗口大小位置, mFrame为输出参数, 完成这一步mFrame就是surface大小了
// Now make sure the window fits in the overall display frame.
Gravity.applyDisplay(mAttrs.gravity, mDisplayFrame, mFrame);
// Calculate the outsets before the content frame gets shrinked to the window frame.
if (hasOutsets) { // 设置要知应用的下巴范围
mOutsets.set(Math.max(mContentFrame.left - mOutsetFrame.left, 0),
Math.max(mContentFrame.top - mOutsetFrame.top, 0),
Math.max(mOutsetFrame.right - mContentFrame.right, 0),
Math.max(mOutsetFrame.bottom - mContentFrame.bottom, 0));
} else {
mOutsets.set(0, 0, 0, 0);
}
// Make sure the content and visible frames are inside of the
// final window frame.
// 设置内容区范围
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));
// 设置可见区范围
mVisibleFrame.set(Math.max(mVisibleFrame.left, mFrame.left),
Math.max(mVisibleFrame.top, mFrame.top),
Math.min(mVisibleFrame.right, mFrame.right),
Math.min(mVisibleFrame.bottom, mFrame.bottom));
// 设置stable区范围
mStableFrame.set(Math.max(mStableFrame.left, mFrame.left),
Math.max(mStableFrame.top, mFrame.top),
Math.min(mStableFrame.right, mFrame.right),
Math.min(mStableFrame.bottom, mFrame.bottom));
// 设置告知应用的过扫描区到窗口的内边距
mOverscanInsets.set(Math.max(mOverscanFrame.left - mFrame.left, 0),
Math.max(mOverscanFrame.top - mFrame.top, 0),
Math.max(mFrame.right - mOverscanFrame.right, 0),
Math.max(mFrame.bottom - mOverscanFrame.bottom, 0));
// 设置告知应用的内容区到窗口的内边距
mContentInsets.set(mContentFrame.left - mFrame.left,
mContentFrame.top - mFrame.top,
mFrame.right - mContentFrame.right,
mFrame.bottom - mContentFrame.bottom);
// 设置告知应用的可见区到窗口的内边距
mVisibleInsets.set(mVisibleFrame.left - mFrame.left,
mVisibleFrame.top - mFrame.top,
mFrame.right - mVisibleFrame.right,
mFrame.bottom - mVisibleFrame.bottom);
// 设置告知应用的稳定区到窗口的内边距
mStableInsets.set(Math.max(mStableFrame.left - mFrame.left, 0),
Math.max(mStableFrame.top - mFrame.top, 0),
Math.max(mFrame.right - mStableFrame.right, 0),
Math.max(mFrame.bottom - mStableFrame.bottom, 0));
mCompatFrame.set(mFrame);
if (mEnforceSizeCompat) { // 兼容窗口情况
// If there is a size compatibility scale being applied to the
// window, we need to apply this to its insets so that they are
// reported to the app in its coordinate space.
mOverscanInsets.scale(mInvGlobalScale);
mContentInsets.scale(mInvGlobalScale);
mVisibleInsets.scale(mInvGlobalScale);
mStableInsets.scale(mInvGlobalScale);
mOutsets.scale(mInvGlobalScale);
// Also the scaled frame that we report to the app needs to be
// adjusted to be in its coordinate space.
mCompatFrame.scale(mInvGlobalScale);
}
// 更新壁纸偏移
if (mIsWallpaper && (fw != mFrame.width() || fh != mFrame.height())) {
final DisplayContent displayContent = getDisplayContent();
if (displayContent != null) {
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
mService.updateWallpaperOffsetLocked(this,
displayInfo.logicalWidth, displayInfo.logicalHeight, false);
}
}
if (DEBUG_LAYOUT || WindowManagerService.localLOGV) Slog.v(TAG,
"Resolving (mRequestedWidth="
+ mRequestedWidth + ", mRequestedheight="
+ mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph
+ "): frame=" + mFrame.toShortString()
+ " ci=" + mContentInsets.toShortString()
+ " vi=" + mVisibleInsets.toShortString()
+ " vi=" + mStableInsets.toShortString()
+ " of=" + mOutsets.toShortString());
}
到这里整个窗口的布局过程分析的也差不多了。 相关的执行逻辑我都写在注释里了, 欢迎交流。