前言
在Android系统中WindowConfiguration这个类用于管理与窗口相关的设置,该类存储了当前窗口的显示区域、屏幕的旋转方向、窗口模式等参数,应用程序通过该类提供的信息可以更好的适配不同的屏幕布局和窗口环境,以提高用户体验。
一、类定义
frameworks/base/core/java/android/app/WindowConfiguration.java
public class WindowConfiguration implements Parcelable, Comparable<WindowConfiguration> {
//包含装饰窗口在内的窗口显示区域
private Rect mBounds = new Rect();
//不包含装饰窗口在内的窗口显示区域
private Rect mAppBounds;
//可显示的最大区域
private final Rect mMaxBounds = new Rect();
//当前屏幕设备的旋转角度
private int mRotation = ROTATION_UNDEFINED;
//当前窗口模式
private @WindowingMode int mWindowingMode;
//屏幕窗口模式
private @WindowingMode int mDisplayWindowingMode;
/** @hide */
@IntDef(prefix = { "WINDOWING_MODE_" }, value = {
WINDOWING_MODE_UNDEFINED,//未定义
WINDOWING_MODE_FULLSCREEN,//全屏
WINDOWING_MODE_MULTI_WINDOW,//多窗口
WINDOWING_MODE_PINNED,
WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
WINDOWING_MODE_FREEFORM,
})
public @interface WindowingMode {}
//Activity的类型
private @ActivityType int mActivityType;
/** @hide */
@IntDef(prefix = { "ACTIVITY_TYPE_" }, value = {
ACTIVITY_TYPE_UNDEFINED,
ACTIVITY_TYPE_STANDARD,
ACTIVITY_TYPE_HOME,
ACTIVITY_TYPE_RECENTS,
ACTIVITY_TYPE_ASSISTANT,
ACTIVITY_TYPE_DREAM,
})
public @interface ActivityType {}
//窗口是否总是位于最上层
private @AlwaysOnTop int mAlwaysOnTop;
/** @hide */
@IntDef(prefix = { "ALWAYS_ON_TOP_" }, value = {
ALWAYS_ON_TOP_UNDEFINED,
ALWAYS_ON_TOP_ON,
ALWAYS_ON_TOP_OFF,
})
private @interface AlwaysOnTop {}
}
该类主要有以下几个关键属性:
- mBounds: 屏幕尺寸
- mAppBounds:不包含装饰窗口在内的窗口显示区域**(根据源码发现mAppBounds只排除了导航栏这个装饰窗口所在的区域,状态栏和输入法等装饰窗口所在的区域是被包含在内的)**
- mMaxBounds:窗口可显示的最大区域
- mRotation:当前屏幕设备的旋转角度
- mWindowingMode:当前窗口的窗口模式,例如未定义、全屏、分屏、多窗口等
- ActivityType:页面类型,例如未定义、标准、首页、最近任务等
- mAlwaysOnTop:窗口是否总是位于最上层
二、WindowConfiguration的属性设置
2.1 Configuration类
WindowConfiguration在Android系统中基本都是作为Configuration类的内部属性出现的。
frameworks/base/core/java/android/content/res/Configuration.java
public final class Configuration implements Parcelable, Comparable<Configuration> {
public final WindowConfiguration windowConfiguration = new WindowConfiguration();
}
2.2 计算当前屏幕尺寸和当前窗口可显示的最大区域
这里我们主要是结合WMS模块的相关代码来分析WindowConfiguration的各个属性的来源;系统主要是通过DisplayContent的computeScreenConfiguration方法来计算当前屏幕对应的窗口配置信息的。
frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer>
implements WindowManagerPolicy.DisplayContentInfo {
DisplayInfo computeScreenConfiguration(Configuration outConfig, int rotation) {
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);//屏幕旋转角度
final int dw = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;//屏幕宽度
final int dh = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;//屏幕高度
//注释1,对屏幕的实际宽高进行存储
outConfig.windowConfiguration.setMaxBounds(0, 0, dw, dh);
outConfig.windowConfiguration.setBounds(outConfig.windowConfiguration.getMaxBounds());
final int uiMode = getConfiguration().uiMode;//UI模式
final DisplayCutout displayCutout = calculateDisplayCutoutForRotation(rotation).getDisplayCutout();//计算屏幕的显示切口
//注释2,调用computeScreenAppConfiguration方法
computeScreenAppConfiguration(outConfig, dw, dh, rotation, uiMode, displayCutout);
...代码省略...
}
}
在注释1处根据屏幕旋转角度和基本显示尺寸,确定屏幕的实际宽高,并将其存储到WindowConfiguration的mMaxBounds属性和mBounds属性中。
在注释2处将屏幕实际宽度、高度、旋转角度、UI模式、屏幕显示切口作为参数,调用computeScreenAppConfiguration方法计算当前窗口可显示的安全区域。
2.3 计算当前窗口可显示的安全区域
class DisplayContent extends WindowContainer<DisplayContent.DisplayChildWindowContainer>
implements WindowManagerPolicy.DisplayContentInfo {
private final DisplayPolicy mDisplayPolicy;
private void computeScreenAppConfiguration(Configuration outConfig, int dw, int dh,
int rotation, int uiMode, DisplayCutout displayCutout) {
//注释1,获取不包含系统装饰窗口的可显示屏幕区域
final Point appSize = mDisplayPolicy.getNonDecorDisplaySize(dw, dh, rotation, uiMode, displayCutout);
//注释2,获取不包含系统装饰窗口的可显示屏幕边界
mDisplayPolicy.getNonDecorInsetsLw(rotation, dw, dh, displayCutout, mTmpRect);
final int leftInset = mTmpRect.left;
final int topInset = mTmpRect.top;
//注释3,存储应用的可显示的安全区域
outConfig.windowConfiguration.setAppBounds(leftInset /* left */, topInset /* top */,
leftInset + appSize.x /* right */, topInset + appSize.y /* bottom */);
//存储屏幕旋转角度
outConfig.windowConfiguration.setRotation(rotation);
//存储屏幕是横屏还是竖屏
outConfig.orientation = (dw <= dh) ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
//屏幕像素密度
final float density = mDisplayMetrics.density;
final Point configSize = mDisplayPolicy.getConfigDisplaySize(dw, dh, rotation, uiMode,
displayCutout);
outConfig.screenWidthDp = (int) (configSize.x / density);
outConfig.screenHeightDp = (int) (configSize.y / density);
outConfig.compatScreenWidthDp = (int) (outConfig.screenWidthDp / mCompatibleScreenScale);
outConfig.compatScreenHeightDp = (int) (outConfig.screenHeightDp / mCompatibleScreenScale);
final boolean rotated = (rotation == ROTATION_90 || rotation == ROTATION_270);
outConfig.compatSmallestScreenWidthDp = computeCompatSmallestWidth(rotated, uiMode, dw,
dh);
}
}
frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java
public class DisplayPolicy {
Point getNonDecorDisplaySize(int fullWidth, int fullHeight, int rotation, int uiMode,
DisplayCutout displayCutout) {
int width = fullWidth;
int height = fullHeight;
int navBarReducedHeight = 0;
int navBarReducedWidth = 0;
//获取导航栏的位置
final int navBarPosition = navigationBarPosition(fullWidth, fullHeight, rotation);
if (hasNavigationBar()) {
if (navBarPosition == NAV_BAR_BOTTOM) {
navBarReducedHeight = getNavigationBarHeight(rotation, uiMode);
} else if (navBarPosition == NAV_BAR_LEFT || navBarPosition == NAV_BAR_RIGHT) {
navBarReducedWidth = getNavigationBarWidth(rotation, uiMode, navBarPosition);
}
}
if (mExtraNavBarAlt != null) {
final LayoutParams altBarParams = mExtraNavBarAlt.getLayoutingAttrs(rotation);
final int altBarPosition = getAltBarPosition(altBarParams);
if (altBarPosition == ALT_BAR_BOTTOM || altBarPosition == ALT_BAR_TOP) {
if (altBarPosition == navBarPosition) {
navBarReducedHeight = Math.max(navBarReducedHeight,
getAltBarHeight(ITYPE_EXTRA_NAVIGATION_BAR));
} else {
navBarReducedHeight += getAltBarHeight(ITYPE_EXTRA_NAVIGATION_BAR);
}
} else if (altBarPosition == ALT_BAR_LEFT || altBarPosition == ALT_BAR_RIGHT) {
if (altBarPosition == navBarPosition) {
navBarReducedWidth = Math.max(navBarReducedWidth,
getAltBarWidth(ITYPE_EXTRA_NAVIGATION_BAR));
} else {
navBarReducedWidth += getAltBarWidth(ITYPE_EXTRA_NAVIGATION_BAR);
}
}
}
//当前窗口的安全显示区域为屏幕宽高减去导航栏所在的区域
height -= navBarReducedHeight;
width -= navBarReducedWidth;
//如果屏幕显示切口对象不为空,还要减去该区域
if (displayCutout != null) {
height -= displayCutout.getSafeInsetTop() + displayCutout.getSafeInsetBottom();
width -= displayCutout.getSafeInsetLeft() + displayCutout.getSafeInsetRight();
}
return new Point(width, height);
}
public void getNonDecorInsetsLw(int displayRotation, int displayWidth, int displayHeight,
DisplayCutout displayCutout, Rect outInsets) {
outInsets.setEmpty();
//系统存在导航栏
if (hasNavigationBar()) {
final int uiMode = mService.mPolicy.getUiMode();
//获取导航栏的位置,最终返回的边界区域要去掉导航栏所在的区域
int position = navigationBarPosition(displayWidth, displayHeight, displayRotation);
if (position == NAV_BAR_BOTTOM) {
outInsets.bottom = getNavigationBarHeight(displayRotation, uiMode);
} else if (position == NAV_BAR_RIGHT) {
outInsets.right = getNavigationBarWidth(displayRotation, uiMode, position);
} else if (position == NAV_BAR_LEFT) {
outInsets.left = getNavigationBarWidth(displayRotation, uiMode, position);
}
}
if (mExtraNavBarAlt != null) {
final LayoutParams extraNavLayoutParams =
mExtraNavBarAlt.getLayoutingAttrs(displayRotation);
final int position = getAltBarPosition(extraNavLayoutParams);
if (position == ALT_BAR_BOTTOM) {
outInsets.bottom = Math.max(outInsets.bottom,
getAltBarHeight(ITYPE_EXTRA_NAVIGATION_BAR));
} else if (position == ALT_BAR_RIGHT) {
outInsets.right = Math.max(outInsets.right,
getAltBarWidth(ITYPE_EXTRA_NAVIGATION_BAR));
} else if (position == ALT_BAR_LEFT) {
outInsets.left = Math.max(outInsets.left,
getAltBarWidth(ITYPE_EXTRA_NAVIGATION_BAR));
} else if (position == ALT_BAR_TOP) {
outInsets.top = Math.max(outInsets.top,
getAltBarHeight(ITYPE_EXTRA_NAVIGATION_BAR));
}
}
//如果屏幕显示切口对象不为空,还要减去该区域
if (displayCutout != null) {
outInsets.left += displayCutout.getSafeInsetLeft();
outInsets.top += displayCutout.getSafeInsetTop();
outInsets.right += displayCutout.getSafeInsetRight();
outInsets.bottom += displayCutout.getSafeInsetBottom();
}
}
}
在注释1处调用DisplayPolicy的getNonDecorDisplaySize方法,获取不包含系统装饰窗口的可显示屏幕区域,将结果存放在类型为Point的appSize对象中,结合getNonDecorDisplaySize方法源码可以发现该方法只是去除了导航栏窗口所在的区域,另外如果有屏幕显示切口区域,还会去除屏幕显示切口区域。
在注释2处调用DisplayPolicy的getNonDecorInsetsLw方法,获取不包含系统装饰窗口的可显示屏幕边界,将结果存放在类型为Rect的mTmpRect对象中,结合getNonDecorInsetsLw方法源码可以发现该方法只是去除了导航栏窗口所在的区域,另外如果有屏幕显示切口区域,还会去除屏幕显示切口区域。
在注释3处会结合appSize和mTmpRect,将当前窗口可显示的安全区域存储到Configuration的WindowConfiguration中。
三、WindowConfiguration的作用
WindowConfiguration的toString方法包含了此类的所有关键信息。
public class WindowConfiguration implements Parcelable, Comparable<WindowConfiguration> {
@Override
public String toString() {
return "{ mBounds=" + mBounds
+ " mAppBounds=" + mAppBounds
+ " mMaxBounds=" + mMaxBounds
+ " mWindowingMode=" + windowingModeToString(mWindowingMode)
+ " mDisplayWindowingMode=" + windowingModeToString(mDisplayWindowingMode)
+ " mActivityType=" + activityTypeToString(mActivityType)
+ " mAlwaysOnTop=" + alwaysOnTopToString(mAlwaysOnTop)
+ " mRotation=" + (mRotation == ROTATION_UNDEFINED
? "undefined" : rotationToString(mRotation))
+ "}";
}
public static String windowingModeToString(@WindowingMode int windowingMode) {
switch (windowingMode) {
case WINDOWING_MODE_UNDEFINED: return "undefined";
case WINDOWING_MODE_FULLSCREEN: return "fullscreen";
case WINDOWING_MODE_MULTI_WINDOW: return "multi-window";
case WINDOWING_MODE_PINNED: return "pinned";
case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return "split-screen-primary";
case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return "split-screen-secondary";
case WINDOWING_MODE_FREEFORM: return "freeform";
}
return String.valueOf(windowingMode);
}
public static String activityTypeToString(@ActivityType int applicationType) {
switch (applicationType) {
case ACTIVITY_TYPE_UNDEFINED: return "undefined";
case ACTIVITY_TYPE_STANDARD: return "standard";
case ACTIVITY_TYPE_HOME: return "home";
case ACTIVITY_TYPE_RECENTS: return "recents";
case ACTIVITY_TYPE_ASSISTANT: return "assistant";
case ACTIVITY_TYPE_DREAM: return "dream";
}
return String.valueOf(applicationType);
}
public static String alwaysOnTopToString(@AlwaysOnTop int alwaysOnTop) {
switch (alwaysOnTop) {
case ALWAYS_ON_TOP_UNDEFINED: return "undefined";
case ALWAYS_ON_TOP_ON: return "on";
case ALWAYS_ON_TOP_OFF: return "off";
}
return String.valueOf(alwaysOnTop);
}
}
//frameworks/base/core/java/android/view/Surface.java
public class Surface implements Parcelable {
public static String rotationToString(int rotation) {
switch (rotation) {
case Surface.ROTATION_0: {
return "ROTATION_0";
}
case Surface.ROTATION_90: {
return "ROTATION_90";
}
case Surface.ROTATION_180: {
return "ROTATION_180";
}
case Surface.ROTATION_270: {
return "ROTATION_270";
}
default: {
return Integer.toString(rotation);
}
}
}
}
当我们调用如下方法
Log.i(TAG, "getWindowInfo: config = " + getResources().getConfiguration());
或者通过dumpsys window windows导出当前所有窗口的堆栈信息,都可以得到和Configuration类相关的以下信息:
config={1.0 ?mcc?mnc [zh_CN] ldltr sw360dp w764dp h324dp 480dpi nrml long land finger -keyb/h/h -nav/h winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(107, 0 - 2400, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mDisplayRotation=ROTATION_90 mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_90} s.2 fontWeightAdjustment=0mThemeChanged= 0, mThemeChangedFlags= 0, mFlipFont= 0, mAccessibleChanged= -1, mUxIconConfig= 3468921665126662176, mMaterialColor= 0, mUserId= 0, mFontUserId= 0, mFontVariationSettings= 226, mFoldingAngle = -1.0, mIconPackName= , mDarkModeBackgroundMaxL= 0.0, mDarkModeDialogBgMaxL= 27.0, mDarkModeForegroundMinL= 100.0, mOplusConfigType= 1, mOplusChangedConfigs= 0, OpSans= 0, mBurmeseFontFlag= 2, mFlag= 0, mPuttDisplayFlag= -1}
这里我们重点关注和WindowConfiguration相关的信息:
winConfig={ mBounds=Rect(0, 0 - 2400, 1080) mAppBounds=Rect(107, 0 - 2400, 1080) mMaxBounds=Rect(0, 0 - 2400, 1080) mWindowingMode=fullscreen mDisplayWindowingMode=fullscreen mActivityType=standard mAlwaysOnTop=undefined mRotation=ROTATION_90}
- mBounds=Rect(0, 0 - 2400, 1080):屏幕尺寸
- mAppBounds=Rect(107, 0 - 2400, 1080):窗口可显示的安全区域,此属性会影响应用具体加载那个layout下面的布局文件,系统会优先选择尺寸最接近2293x1080的布局文件。
- mMaxBounds=Rect(0, 0 - 2400, 1080):窗口可显示的最大区域
- mWindowingMode=fullscreen:窗口为全屏模式
- mDisplayWindowingMode=fullscreen:屏幕设备窗口为全屏模式
- mActivityType=standard:页面类型未定义
- mAlwaysOnTop=undefined:窗口悬浮模式未定义
- mRotation=ROTATION_90:屏幕设备的旋转角度为90度
借助这些属性,开发者能够更好地适配不同的设备配置和屏幕状态,确保应用在不同环境下的一致性和优化。
四、修改WindowConfiguration的配置信息,刷新窗口UI视图
4.1 通过adb 修改屏幕旋转角度
我们可以通过以下指令获取当前屏幕的旋转角度
adb shell settings get system user_rotation #0:自然方向(竖屏)1:右旋转 90 度(横屏)2:倒转 180 度(反向竖屏)3:左旋转 270 (横屏)
还可以通过如下配置修改当前屏幕的旋转角度
adb shell settings put system user_rotation <value>
4.2 实现原理
当我们修改system数据库中的user_rotation字段的时候,会触发以下代码逻辑。
frameworks/base/services/core/java/com/android/server/wm/DisplayRotation.java
//frameworks/base/core/java/android/provider/Settings.java
public final class Settings {
public static final String USER_ROTATION = "user_rotation";
}
public class DisplayRotation {
private final WindowManagerService mService;
private class SettingsObserver extends ContentObserver {
SettingsObserver(Handler handler) {
super(handler);
}
void observe() {
final ContentResolver resolver = mContext.getContentResolver();
...代码省略...
//注释1,监听system数据库user_rotation字段的变化
resolver.registerContentObserver(Settings.System.getUriFor(
Settings.System.USER_ROTATION), false, this,
UserHandle.USER_ALL);
updateSettings();
}
@Override
public void onChange(boolean selfChange) {
//注释2,判断设置是否发生了变化,如果发生了变化调用WMS的updateRotation方法
if (updateSettings()) {
mService.updateRotation(true /* alwaysSendConfiguration */,
false /* forceRelayout */);
}
}
}
private boolean updateSettings() {
final ContentResolver resolver = mContext.getContentResolver();
boolean shouldUpdateRotation = false;
synchronized (mLock) {
...代码省略...
//获取当前屏的旋转角度
final int userRotation = Settings.System.getIntForUser(resolver,
Settings.System.USER_ROTATION, Surface.ROTATION_0,
UserHandle.USER_CURRENT);
if (mUserRotation != userRotation) {
mUserRotation = userRotation;
shouldUpdateRotation = true;
}
...代码省略...
return shouldUpdateRotation;
}
}
在注释1处DisplayRotation类会监听system数据库的user_rotation字段的变化,当该字段发生变化的时候,会在注释2处触发WindowManagerServices的updateRotation方法。
frameworksbase/services/core/java/com/android/server/wm/WindowManagerService.java
public class WindowManagerService extends IWindowManager.Stub
implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
@Override
public void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout) {
updateRotationUnchecked(alwaysSendConfiguration, forceRelayout);
}
private void updateRotationUnchecked(boolean alwaysSendConfiguration, boolean forceRelayout) {
...代码省略...
try {
synchronized (mGlobalLock) {
boolean layoutNeeded = false;
final int displayCount = mRoot.mChildren.size();
for (int i = 0; i < displayCount; ++i) {
final DisplayContent displayContent = mRoot.mChildren.get(i);
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "updateRotation: display");
final boolean rotationChanged = displayContent.updateRotationUnchecked();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (rotationChanged) {
mAtmService.getTaskChangeNotificationController()
.notifyOnActivityRotation(displayContent.mDisplayId);
}
if (!rotationChanged || forceRelayout) {
displayContent.setLayoutNeeded();
layoutNeeded = true;
}
if (rotationChanged || alwaysSendConfiguration) {
displayContent.sendNewConfiguration();
}
}
if (layoutNeeded) {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
"updateRotation: performSurfacePlacement");
mWindowPlacerLocked.performSurfacePlacement();
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
} finally {
Binder.restoreCallingIdentity(origId);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
}
}