Android T为什么不再使用getSize()和getRealSize()

1,169 阅读5分钟

在做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是如何启动的]