要想读懂窗口布局管理,必须了解status bar 和nav bar ,要了解这两个bar 更要了解相关的flags,下面就会这些flags做一些说明
View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR //高亮状态栏
View.STATUS_BAR_TRANSLUCENT //半透明状态栏
View.STATUS_BAR_TRANSPARENT //透明状态栏,一般指定WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS系统会设置成全透明
View.SYSTEM_UI_FLAG_FULLSCREEN // 全屏显示,隐藏状态栏和状态栏
View.STATUS_BAR_UNHIDE // 显示状态栏,用于传递给systemui处理
View.NAVIGATION_BAR_TRANSPARENT //半透明导航栏
View.NAVIGATION_BAR_TRANSLUCENT //透明导航栏,一般指定WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS系统会设置成全透明
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // 隐藏导航栏
View.NAVIGATION_BAR_UNHIDE // 显示状态栏,传递给systemui处理
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY //身临其境的感觉,和SYSTEM_UI_FLAG_LIGHT_STATUS_BAR一起使用会隐藏状态栏和导航栏,从上面/下面滑出状态栏和导航栏,过几秒自动消失
View.SYSTEM_UI_FLAG_IMMERSIVE //身临其境的感觉,自动隐藏状态栏和导航栏,出上部/下部滑动状态栏出现,不自动隐藏
View.STATUS_BAR_TRANSIENT //进入瞬态,状态栏出来和隐藏的过程
View.NAVIGATION_BAR_TRANSIENT //进入瞬态,导航栏出来和隐藏的过程
WindowManager.LayoutParams.FLAG_FULLSCREEN //全屏显示,隐藏状态栏
WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS //导航栏状态栏透明,客户端渲染状态栏导航栏背景
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND //强制导航栏状态栏透明,客户端渲染状态栏导航栏背景
WindowManager.LayoutParams.PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR //当此窗口到达顶部的时候保持前一个窗口的透明状态
WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS //指定半透明status bar
WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION //指定半透明nav bar
WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND 强制渲染背景色
下面这几个状态用于status bar 和nav bar 切换时候的瞬态过程
private static final int TRANSIENT_BAR_NONE = 0; //无任何状态,当隐藏完成时候设置
private static final int TRANSIENT_BAR_SHOW_REQUESTED = 1; // 请求显示
private static final int TRANSIENT_BAR_SHOWING = 2; //正在显示的过程
private static final int TRANSIENT_BAR_HIDING = 3; //正在隐藏的过程,隐藏完成window变成不可见,设置TRANSIENT_BAR_NONE
还要记住,分屏模式不允许隐藏bar
另外管理的还要注意几个状态,包括状态栏展开,锁屏和锁屏被阻断,以及正常的全屏activity在前台的几种情况。
状态栏管理在wms这端的核心函数是PhoneWindowManager中的updateSystemUiVisibilityLw()函数
private int updateSystemUiVisibilityLw() {
// If there is no window focused, there will be nobody to handle the events
// anyway, so just hang on in whatever state we're in until things settle down.
//1 获取焦点的window也就是能接收input事件的window
WindowState winCandidate = mFocusedWindow != null ? mFocusedWindow
: mTopFullscreenOpaqueWindowState;
if (winCandidate == null) {
return 0;
}
//2 如果当前焦点的window是ImmersiveModeConfirmation弹出的window不做处理,直接返回
if (winCandidate.getAttrs().token == mImmersiveModeConfirmation.getWindowToken()) {
// The immersive mode confirmation should never affect the system bar visibility,
// otherwise it will unhide the navigation bar and hide itself.
winCandidate = isStatusBarKeyguard() ? mStatusBar : mTopFullscreenOpaqueWindowState;
if (winCandidate == null) {
return 0;
}
}
final WindowState win = winCandidate;
//3 焦点window是keygurd,但是keygurd被阻断,不处理直接返回
if ((win.getAttrs().privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 && mHideLockScreen == true) {
// We are updating at a point where the keyguard has gotten
// focus, but we were last in a state where the top window is
// hiding it. This is probably because the keyguard as been
// shown while the top window was displayed, so we want to ignore
// it here because this is just a very transient change and it
// will quickly lose focus once it correctly gets hidden.
return 0;
}
//4 获取win的标志,处理rest flags
int tmpVisibility = PolicyControl.getSystemUiVisibility(win, null)
& ~mResettingSystemUiFlags
& ~mForceClearedSystemUiFlags;
if (mForcingShowNavBar && win.getSurfaceLayer() < mForcingShowNavBarLayer) {
tmpVisibility &= ~PolicyControl.adjustClearableFlags(win, View.SYSTEM_UI_CLEARABLE_FLAGS);
}
//5 根据当前状态处理SYSTEM_UI_FLAG_LIGHT_STATUS_BAR标志(也就是status bar高亮状态)
final int fullscreenVisibility = updateLightStatusBarLw(0 /* vis */,
mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState);
//6 导航栏高亮状态处理
final int dockedVisibility = updateLightStatusBarLw(0 /* vis */,
mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
//7 获取home stack和dock stack的大小
mWindowManagerFuncs.getStackBounds(HOME_STACK_ID, mNonDockedStackBounds);
mWindowManagerFuncs.getStackBounds(DOCKED_STACK_ID, mDockedStackBounds);
//8 更新status bar 的属性
final int visibility = updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility);
final int diff = visibility ^ mLastSystemUiFlags;
final int fullscreenDiff = fullscreenVisibility ^ mLastFullscreenStackSysUiFlags;
final int dockedDiff = dockedVisibility ^ mLastDockedStackSysUiFlags;
//9 更新各属性
final boolean needsMenu = win.getNeedsMenuLw(mTopFullscreenOpaqueWindowState);
if (diff == 0 && fullscreenDiff == 0 && dockedDiff == 0 && mLastFocusNeedsMenu == needsMenu
&& mFocusedApp == win.getAppToken()
&& mLastNonDockedStackBounds.equals(mNonDockedStackBounds)
&& mLastDockedStackBounds.equals(mDockedStackBounds)) {
return 0;
}
mLastSystemUiFlags = visibility;
mLastFullscreenStackSysUiFlags = fullscreenVisibility;
mLastDockedStackSysUiFlags = dockedVisibility;
mLastFocusNeedsMenu = needsMenu;
mFocusedApp = win.getAppToken();
final Rect fullscreenStackBounds = new Rect(mNonDockedStackBounds);
final Rect dockedStackBounds = new Rect(mDockedStackBounds);
//10请求StatusBarManager处理(最终请求status bar 处理)
mHandler.post(new Runnable() {
@Override
public void run() {
StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
if (statusbar != null) {
statusbar.setSystemUiVisibility(visibility, fullscreenVisibility,
dockedVisibility, 0xffffffff, fullscreenStackBounds,
dockedStackBounds, win.toString());
statusbar.topAppWindowChanged(needsMenu);
}
}
});
return diff;
}
我们这里对以上10个步骤进行解释
1获取候选的window,也就是接收事件的window,这里可能有集中情况
(1)status bar 被展开或者keygurd的情况statusbar window是焦点的window
(2) 在分屏状态下,接收事件的那个window
(3) 在没有分屏状态下全屏的activity就是焦点
对于这里选择的候选window,默认以焦点为主,没有焦点时则使用mTopFullscreenOpaqueWindowState作为候选window,候选window用于使用它的flags处理系统bar的状态。 如果没有找到候选window 就直接返回。这里解释下mTopFullscreenOpaqueWindowState代表在full workspace stack上的activity
2 这里处理的情况是当前的焦点window 是如下这个window
当处于身临其境的模式,也就是设置了View.SYSTEM_UI_FLAG_IMMERSIVE |View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY|View.SYSTEM_UI_FLAG_HIDE_NAVIGATION标志的时候,防止用户恐慌,提示如何划出状态栏导航栏,这种状态不需要处理状态栏属性,直接等到用户退出帮助页面后再去操作
3 当有一个window设置了WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED标志的时候这个界面会在锁屏上面显示,这时候mHideLockScreen为真,注意这个标志在8.0里面改了这变量名,其实是一样的。这种情况马上焦点window就不是keygurd了,这只是焦点窗口切换的中间状态,这种情况直接不处理,等状态变了后再去处理
4 PolicyControl.getSystemUiVisibility(win, null)函数我们暂时怎为是直接获取到win的systemUiVisibility变量所描述的关于该window设置的systemui的一些flags。
另外对于mResettingSystemUiFlags的解释,当设置了View.SYSTEM_UI_FLAG_HIDE_NAVIGATION|View.SYSTEM_UI_FLAG_FULLSCREEN这连个标志而没有设置IMMERSIVE相关标志的时候,会隐藏掉状态栏和导航栏,这时候要求点击屏幕任意地方,状态栏和导航栏都出出来,这个功能的实现是创建一个全屏的事件消费者去接收屏幕点击事件,当用户点击后这个事件消费者会处理事件,事件消费者注册的地方是在PhoneWindowManager的beginLayoutLw()函数中,事件的处理是在PhoneWindowManager的内部类HideNavInputEventReceiver的onInputEvent()方法中,读者不妨自己去读一下,主要是通过给mResettingSystemUiFlags设置View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN 这三个标志,使status bar 出现来实现的。这个过程我们最后用一个情景栈去分析.
5 第五步是最终要的一步,前面的步骤都,用于更新systemui的一些标志,由于函数比较长,我们稍后分析.
6 在一些场景下要对高亮标志进行修正,我们这里只拿statusbar 作为例子说明
private int updateLightStatusBarLw(int vis, WindowState opaque, WindowState opaqueOrDimming) {
WindowState statusColorWin = isStatusBarKeyguard() && !mHideLockScreen
? mStatusBar
: opaqueOrDimming;
if (statusColorWin != null) {
if (statusColorWin == opaque) {
// If the top fullscreen-or-dimming window is also the top fullscreen, respect
// its light flag.
vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
vis |= PolicyControl.getSystemUiVisibility(statusColorWin, null)
& View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
} else if (statusColorWin != null && statusColorWin.isDimming()) {
// Otherwise if it's dimming, clear the light flag.
vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
}
}
return vis;
}
代码很简单,首先选择statusColorWin,规则是如果是keygurd且不被其他window覆盖则选择statusbar 作为statusColorWin,否则就是使用参数opaqueOrDimming(放在full stack中的), 当statusColorWin为opaque的window的时候(也就是full stack中不透明的window),这时候状态栏的高亮状态由opaque window决定. 如果不是full stack中的opaque window 则由window自身的Dimming状态决定
7 获取Home stack 和docker stack的大小保存在mNonDockedStackBounds,mDockedStackBounds变量中
8,9 更新我们计算好的systemui相关的属性
10请求StatusBarManager处理(最终请求Systemui 处理)
这里第五条和第十条我们分别展开说明
首先第五步骤更新systemuiFlags的过程,这个过程主要参考焦点window,mTopFullscreenOpaqueWindowState和mTopDockedOpaqueWindowState去计算bar的属性
private int updateSystemBarsLw(WindowState win, int oldVis, int vis) {
final boolean dockedStackVisible = mWindowManagerInternal.isStackVisible(DOCKED_STACK_ID);
final boolean freeformStackVisible =
mWindowManagerInternal.isStackVisible(FREEFORM_WORKSPACE_STACK_ID);
final boolean resizing = mWindowManagerInternal.isDockedDividerResizing();
// We need to force system bars when the docked stack is visible, when the freeform stack
// is visible but also when we are resizing for the transitions when docked stack
// visibility changes.
//1 判断是否强制显示system bar
mForceShowSystemBars = dockedStackVisible || freeformStackVisible || resizing;
final boolean forceOpaqueStatusBar = mForceShowSystemBars && !mForceStatusBarFromKeyguard;
//2 计算fullscreenTransWin用于计算bar 的透明情况
// apply translucent bar vis flags
WindowState fullscreenTransWin = isStatusBarKeyguard() && !mHideLockScreen
? mStatusBar
: mTopFullscreenOpaqueWindowState;
//3 计算状态栏透明flags
vis = mStatusBarController.applyTranslucentFlagLw(fullscreenTransWin, vis, oldVis);
//4 计算导航栏透明情况
vis = mNavigationBarController.applyTranslucentFlagLw(fullscreenTransWin, vis, oldVis);
//5 计算docker导航栏透明度
final int dockedVis = mStatusBarController.applyTranslucentFlagLw(
mTopDockedOpaqueWindowState, 0, 0);
//6 根据mTopFullscreenOpaqueWindowState计算fullscreen stack的FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS标志
final boolean fullscreenDrawsStatusBarBackground =
(drawsSystemBarBackground(mTopFullscreenOpaqueWindowState)
&& (vis & View.STATUS_BAR_TRANSLUCENT) == 0)
|| forcesDrawStatusBarBackground(mTopFullscreenOpaqueWindowState);
//7 根据dockedDrawsStatusBarBackground计算docker stack的FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS标志
final boolean dockedDrawsStatusBarBackground =
(drawsSystemBarBackground(mTopDockedOpaqueWindowState)
&& (dockedVis & View.STATUS_BAR_TRANSLUCENT) == 0)
|| forcesDrawStatusBarBackground(mTopDockedOpaqueWindowState);
// prevent status bar interaction from clearing certain flags
int type = win.getAttrs().type;
boolean statusBarHasFocus = type == TYPE_STATUS_BAR;
//8 根据status 是否展开清除一些属性
if (statusBarHasFocus && !isStatusBarKeyguard()) {
int flags = View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
if (mHideLockScreen) {
flags |= View.STATUS_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSLUCENT;
}
vis = (vis & ~flags) | (oldVis & flags);
}
//9 根据FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS设置一些属性
if (fullscreenDrawsStatusBarBackground && dockedDrawsStatusBarBackground) {
vis |= View.STATUS_BAR_TRANSPARENT;
vis &= ~View.STATUS_BAR_TRANSLUCENT;
} else if ((!areTranslucentBarsAllowed() && fullscreenTransWin != mStatusBar)
|| forceOpaqueStatu