在做Android 12L时开始会遇到一些UI显示相关的bug,比如应用的页面被下面的导航栏遮住了,而且有些应用在导航栏附近还有button或其他需要交互的组件,导致应用无法正常使用。在最开始解决这类问题时,应用层给到我们的信息是,显示异常由于调用getSize和getRealSize返回值相等,应用端会认为当前没有导航栏,所以会申请全屏的宽高来使用。直到Android T版本,这两个api还是仍然存在并且可使用的。
/frameworks/base/core/java/android/view/Display.java
800 @Deprecated
801 public void getSize(Point outSize) {
802 synchronized (mLock) {
803 updateDisplayInfoLocked();
804 mDisplayInfo.getAppMetrics(mTempMetrics, getDisplayAdjustments());
805 outSize.x = mTempMetrics.widthPixels;
806 outSize.y = mTempMetrics.heightPixels;
807 }
808 }
1475 @Deprecated
1476 public void getRealSize(Point outSize) {
1477 synchronized (mLock) {
1478 updateDisplayInfoLocked();
1479 if (shouldReportMaxBounds()) {
1480 final Rect bounds = mResources.getConfiguration()
1481 .windowConfiguration.getMaxBounds();
1482 outSize.x = bounds.width();
1483 outSize.y = bounds.height();
1484 if (DEBUG) {
1485 Log.d(TAG, "getRealSize determined from max bounds: " + outSize);
1486 }
1487 // Skip adjusting by fixed rotation, since if it is necessary, the configuration
1488 // should already reflect the expected rotation.
1489 return;
1490 }
1491 outSize.x = mDisplayInfo.logicalWidth;
1492 outSize.y = mDisplayInfo.logicalHeight;
1493 final @Surface.Rotation int rotation = getLocalRotation();
1494 if (rotation != mDisplayInfo.rotation) {
1495 adjustSize(outSize, mDisplayInfo.rotation, rotation);
1496 }
1497 }
1498 }
这里可以看到区别,getRealSize拿到的宽高是logical Width和logical Height,这里返回的是经过计算之后,app实际可用的尺寸,而getSize返回的是屏幕的物理尺寸。
getSize函数是在Android Api 30之后弃置的,getRealSize在Api 31时弃置
getSize
Added in API level 13
Deprecated in API level 30 fun getSize(outSize: Point!): Unit
Deprecated: Use WindowMetrics instead. Obtain a WindowMetrics instance by calling WindowManager#getCurrentWindowMetrics(), then call WindowMetrics#getBounds() to get the dimensions of the application window.
具体可以参考官方开发者指南:developer.android.google.cn/reference/k…
那为什么getSize和getRealSize会有这种差异?要弄清楚这点我们要搞清楚Display中 physical 和 logical 的区别和联系,以宽度举例,physical width指的是屏幕的物理宽高,是不可调整的,它的数据是直接从surfaceflinger中拿的,代表完整的物理屏幕硬件参数,描述的是屏幕的物理特性;而logical width是一种可覆盖的配置,比如基于物理显示对显示区域作裁剪修正,或对显示属性、方向进行修正,logical尺寸会受到APP本身和WMS、View的综合影响。 DMS是衔接WMS和SurfaceFlinger的重要桥梁,后面有时间会再整理一些DMS相关的总结。
言归正传,继续调查logtcal display和physical display的差异,以DisplayPolicy为例
/frameworks/base/services/core/java/com/android/server/wm/DisplayPolicy.java
2143 /**
2144 * Return the display width available after excluding any screen
2145 * decorations that could never be removed in Honeycomb. That is, system bar or
2146 * button bar.
2147 */
2148 public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation, int uiMode,
2149 DisplayCutout displayCutout) {
2150 int width = fullWidth;
2151 if (hasNavigationBar()) {
2152 final int navBarPosition = navigationBarPosition(fullWidth, fullHeight, rotation);
2153 if (navBarPosition == NAV_BAR_LEFT || navBarPosition == NAV_BAR_RIGHT) {
2154 width -= getNavigationBarWidth(rotation, uiMode);
2155 }
2156 }
2157 if (displayCutout != null) {
2158 width -= displayCutout.getSafeInsetLeft() + displayCutout.getSafeInsetRight();
2159 }
2160 return width;
2161 }
以此处为例,logical display相对于physical display,会减掉Decor部分的差异,这里传进去的入口参数fullwidth就指physical width,是屏幕的实际最大宽度,Decor指的是添加的一些小组件,比如手机开发的刘海屏就属于Decor,此外还调用了getNavigationBarWidth,所以应用层在此时可用的logical width就是fullwidth去掉DisplayCutout的宽度和NavigationBar的宽度。
Andrioid T不再使用getSize 和getRealSize的原因也和这里有关,因为从Android 12.1之后,对平板和大屏设备google使用了新的TaskBar替换掉NavigationBar,相比于传统的3-Buttons导航,TaskBar的功能更加丰富便捷,简单来说当前只要大于 sw600 就会使用TaskBar,此时系统中的NavigationBar的状态是null,所以这里的计算会出现问题。传统的getSize和getRealSize都是通过一些api来减掉不可用区域,剩下的就是可用区域,但对于导航栏来讲,NavigationBar和TaskBar分属两个不同的系统,TaskBar常驻在Launcher3进程,而导航栏是常驻systemUI进程的,如果对每次计算logical display时都区分NavigationBar和TaskBar,还有按键导航及手势导航的状态,势必会带来冗余。所以gooogle 是建议不再使用这两个api,并且给出了替代方案。
google给出的建议是通过当前window对象或service,来获取一个不依赖于physical display的实际可用宽高
Gets the size of the display in pixels.
The return value does not necessarily represent the actual size (native resolution) of the display. The returned size might be adjusted to exclude certain system decor elements that are always visible, or the size might be scaled to provide compatibility with older applications that were originally designed for smaller displays.
The returned size can also be different depending on the WindowManager bound to the display:
If size is requested from an activity (either using a WindowManager accessed by getWindowManager() or getSystemService(Context.WINDOW_SERVICE)), the size of the current app window is returned. As a result, in multi-window mode, the returned size can be smaller than the size of the device screen.
If size is requested from a non-activity context (for example, the application context, where the WindowManager is accessed by getApplicationContext().getSystemService(Context.WINDOW_SERVICE)), the returned size can vary depending on API level:
API level 29 and below — The size of the entire display (based on current rotation) minus system decoration areas is returned.
API level 30 and above — The size of the top running activity in the current process is returned. If the current process has no running activities, the size of the device default display, including system decoration areas, is returned.
For layout purposes, apps should make a request from an activity context to obtain the size of the display area available for app content.
通过调用WindowManager#getCurrentWindowMetrics()来返回一个WindowMetrics对象,再调用该对象的WindowMetrics.getBounds()返回该对象的可用尺寸,以此来完全替代getSize起到的作用。
这个问题是必须从App端适配的,随着Android版本更新,App必须做出相应适配,来提供更好的用户体验。同样的道理,我们在某些测试场景下,测试机和对比机的Android 版本最好相同,不同的Android 版本有行为差异,很可能就是App本身的原因。
可以看到通过这样的方式,对应用层来说完全可以替代旧版本getSize的作用。那如果应用层一定要知道TaskBar的高度呢?关于TaskBar的介绍可以看这篇[TaskBar是如何启动的]