DisplayMetrics获取宽高不对
一、引言
该车机项目为宽屏显示,使用Android 9.0系统开发,分辨率为1920x720,配置navigation bar为120px,显示在左侧。
二、问题
通过如下方法拿到的屏幕高度值是576,正常应该是720。
DisplayMetrics dm = getResources().getDisplayMetrics();
int screenHeight = dm.heightPixels;
三、分析
1、dumpsys display
dumpsys display信息如下:
Logical Displays: size=2
Display 0:
mDisplayId=0
mLayerStack=0
mHasContent=true
mRequestedMode=0
mRequestedColorMode=0
mDisplayOffset=(0, 0)
mPrimaryDisplayDevice=内置屏幕
mBaseDisplayInfo=DisplayInfo{"内置屏幕", uniqueId "local:0", app 1920 x 720, real 1920 x 720, largest app 1920 x 720, smallest app 1920 x 720, mode 1, defaultMode 1, modes [{id=1, width=1920, height=720, fps=60.000004}], colorMode 0, supportedColorModes [0], hdrCapabilities android.view.Display$HdrCapabilities@40f16308, rotation 0, density 240 (160.0 x 160.0) dpi, layerStack 0, appVsyncOff 8300000, presDeadline 9366666, type BUILT_IN, state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, removeMode 0}
mOverrideDisplayInfo=DisplayInfo{"内置屏幕", uniqueId "local:0", app 1920 x 576, real 1920 x 720, largest app 1920 x 1808, smallest app 720 x 536, mode 1, defaultMode 1, modes [{id=1, width=1920, height=720, fps=60.000004}], colorMode 0, supportedColorModes [0], hdrCapabilities android.view.Display$HdrCapabilities@40f16308, rotation 0, density 240 (160.0 x 160.0) dpi, layerStack 0, appVsyncOff 8300000, presDeadline 9366666, type BUILT_IN, state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, removeMode 0}
Display 1:
mDisplayId=1
mLayerStack=1
mHasContent=false
mRequestedMode=0
mRequestedColorMode=0
mDisplayOffset=(0, 0)
mPrimaryDisplayDevice=HDMI 屏幕
mBaseDisplayInfo=DisplayInfo{"HDMI 屏幕", uniqueId "local:1", app 1920 x 720, real 1920 x 720, largest app 1920 x 720, smallest app 1920 x 720, mode 2, defaultMode 2, modes [{id=2, width=1920, height=720, fps=60.000004}], colorMode 0, supportedColorModes [0], hdrCapabilities android.view.Display$HdrCapabilities@40f16308, rotation 0, density 213 (213.0 x 213.0) dpi, layerStack 1, appVsyncOff 8300000, presDeadline 9366666, type HDMI, state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_PRESENTATION, removeMode 0}
mOverrideDisplayInfo=DisplayInfo{"HDMI 屏幕", uniqueId "local:1", app 1920 x 720, real 1920 x 720, largest app 1920 x 1920, smallest app 720 x 720, mode 2, defaultMode 2, modes [{id=2, width=1920, height=720, fps=60.000004}], colorMode 0, supportedColorModes [0], hdrCapabilities android.view.Display$HdrCapabilities@40f16308, rotation 0, density 213 (213.0 x 213.0) dpi, layerStack 1, appVsyncOff 8300000, presDeadline 9366666, type HDMI, state ON, FLAG_SECURE, FLAG_SUPPORTS_PROTECTED_BUFFERS, FLAG_PRESENTATION, removeMode 0}
看到mOverrideDisplayInfo中打印为1920 x 576;
2、DisplayContent
跟踪代码发现DisplayContent中会更新mOverrideDisplayInfo
frameworks\base\services\core\java\com\android\server\wm\DisplayContent.java
/**
* Update {@link #mDisplayInfo} and other internal variables when display is rotated or config
* changed.
* Do not call if {@link WindowManagerService#mDisplayReady} == false.
*/
private DisplayInfo updateDisplayAndOrientation(int uiMode) {
...
final int appWidth = mService.mPolicy.getNonDecorDisplayWidth(dw, dh, mRotation, uiMode,
mDisplayId, displayCutout);
final int appHeight = mService.mPolicy.getNonDecorDisplayHeight(dw, dh, mRotation, uiMode,
mDisplayId, displayCutout);
...
// We usually set the override info in DisplayManager so that we get consistent display
// metrics values when displays are changing and don't send out new values until WM is aware
// of them. However, we don't do this for displays that serve as containers for ActivityView
// because we don't want letter-/pillar-boxing during resize.
final DisplayInfo overrideDisplayInfo = mShouldOverrideDisplayConfiguration
? mDisplayInfo : null;
mService.mDisplayManagerInternal.setDisplayInfoOverrideFromWindowManager(mDisplayId,
overrideDisplayInfo);
继续跟进,发现调用的mPolicy获取的appWidth和appHeight,分别在PhoneWindowManager中的getNonDecorDisplayWidth与getNonDecorDisplayHeight实现。
3、PhoneWindowManager
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
private int getNavigationBarWidth(int rotation, int uiMode) {
if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
return mNavigationBarWidthForRotationInCarMode[rotation];
} else {
return mNavigationBarWidthForRotationDefault[rotation];
}
}
@Override
public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode,
int displayId, DisplayCutout displayCutout) {
int width = fullWidth;
// TODO(multi-display): Support navigation bar on secondary displays.
if (displayId == DEFAULT_DISPLAY && mHasNavigationBar) {
// For a basic navigation bar, when we are in landscape mode we place
// the navigation bar to the side.
if (mNavigationBarCanMove && fullWidth > fullHeight) {
width -= getNavigationBarWidth(rotation, uiMode);
}
}
if (displayCutout != null) {
width -= displayCutout.getSafeInsetLeft() + displayCutout.getSafeInsetRight();
}
return width;
}
private int getNavigationBarHeight(int rotation, int uiMode) {
if (ALTERNATE_CAR_MODE_NAV_SIZE && (uiMode & UI_MODE_TYPE_MASK) == UI_MODE_TYPE_CAR) {
return mNavigationBarHeightForRotationInCarMode[rotation];
} else {
return mNavigationBarHeightForRotationDefault[rotation];
}
}
@Override
public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation, int uiMode,
int displayId, DisplayCutout displayCutout) {
int height = fullHeight;
// TODO(multi-display): Support navigation bar on secondary displays.
if (displayId == DEFAULT_DISPLAY && mHasNavigationBar) {
// For a basic navigation bar, when we are in portrait mode we place
// the navigation bar to the bottom.
if (!mNavigationBarCanMove || fullWidth < fullHeight) {
height -= getNavigationBarHeight(rotation, uiMode);
}
}
if (displayCutout != null) {
height -= displayCutout.getSafeInsetTop() + displayCutout.getSafeInsetBottom();
}
return height;
}
分析这两个方法,发现:
- mNavigationBarCanMove为true且fullWidth > fullHeight的情况,认为navigation bar显示在左侧或右侧,getNonDecorDisplayWidth获取时需减掉navigation bar的宽度;
- 而fullWidth < fullHeight时,认为navigation bar始终显示在底部,则getNonDecorDisplayHeight需减掉navigation bar的高度。
所以,我们保证上述条件满足时即可正确计算应用显示的宽高。
四、实现
1、新增属性ro.vendor.navi_bar_pos配置navigation bar显示位置
device/[project]/system.prop
+#navigation bar position, 1-left 2-right
+ro.vendor.navi_bar_pos=1
2、配置显示navigation bar及宽高
a、device/[project]/overlay/frameworks/base/core/res/res/values/config.xml
<bool name="config_showNavigationBar">true</bool>
b、device/[project]/system.prop
-qemu.hw.mainkeys=1
c、device/[project]/overlay/frameworks/base/core/res/res/values/dimens.xml
<!-- Height of the bottom navigation / system bar. -->
<dimen name="navigation_bar_height">120px</dimen>
<!-- Width of the navigation bar when it is placed vertically on the screen -->
<dimen name="navigation_bar_width">120px</dimen>
3、PhoneWindowManager适配
--- a/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -2514,6 +2514,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
//Log.d(TAG, "wlf shortSizeDp = "+shortSizeDp);
+ // add start
+ if(SystemProperties.getInt("ro.vendor.navi_bar_pos", 0) > 0) {
+ mNavigationBarCanMove = true;
+ }
+ // add end
mHasNavigationBar = res.getBoolean(com.android.internal.R.bool.config_showNavigationBar);
@@ -5168,10 +5173,16 @@ public class PhoneWindowManager implements WindowManagerPolicy {
@NavigationBarPosition
private int navigationBarPosition(int displayWidth, int displayHeight, int displayRotation) {
+ // add start
+ int position= SystemProperties.getInt("ro.bw.navi_bar_pos", 0);
+ if(displayWidth > displayHeight && position > 0) {
+ return position;
+ }
+ // add end
if (mNavigationBarCanMove && displayWidth > displayHeight) {
if (displayRotation == Surface.ROTATION_270) {
该修改主要是保证mNavigationBarCanMove为true,还有就是navigationBarPosition根据显示宽高调整位置。