资深架构师:深入聊聊获取屏幕高度这件事( 三 )

有人会问,这些key都是哪里来的?毕竟我在厂商文档也没有翻到 。
我能想到的办法是查看SettingsProvider,它是提供设置数据的Provider,分有Global、System、Secure三种类型,上面代码中可以看到不同品牌存放在的类型都不同 。我们可以通过adb命令查看所有数据,根据navigation等关键字去寻找 。比如查看Secure的数据:
adb shell settings list secure或者:
ContentResolver cr = context.getContentResolver();Uri uri = Uri.parse("content://settings/secure/");Cursor cursor = cr.query(uri, null, null, null, null);while (cursor.moveToNext()) {String name = cursor.getString(cursor.getColumnIndex("name"));String value = https://www.isolves.com/it/cxkf/bk/2020-12-31/cursor.getString(cursor.getColumnIndex("value"));Log.d("settings:", name + "=" + value);}cursor.close();这样如果有上面兼容不到的机型,可以使用这个方法适配 。也欢迎你的补充反馈 。
费了这么大的劲获取到了准确的高度,可能你会说,还不如直接获取ContentView的高度:
public static int getContentViewHeight(Activity activity) {View contentView = activity.getWindow().getDecorView().findViewById(android.R.id.content);return contentView.getHeight();}这个结果和上述计算的高度一致,唯一的限制是需要在onWindowFocusChanged之后调用,否则高度为0 。这个我们可以根据实际情况自行选用 。
已知问题

  • 网上有许多同类代码,发现会将vivo和oppo都使用navigation_gesture_on这一个key 。我在oppo Find x中发现此key并不存在,不知是否和系统版本有关 。如果是的话,又需要判断oppo的系统版本了 。
  • 上面提到的获取导航栏高度的方法在部分手机中无效,无效的原因是因为导航栏隐藏时,获取高度就为0 。所以判断是否显示导航栏是关键 。
  • 刘海的出现,很多人会吐槽丑,所以厂家想到了隐藏刘海的方式(掩耳盗铃),比如下面是Redmi K30的设置页面:

资深架构师:深入聊聊获取屏幕高度这件事

文章插图
设置刘海显示页
第二种没啥特别,就是状态栏强制为黑色 。这里我怀疑因为这个设置,导致在有刘海的手机上,ScreenHeight不包含状态栏高度 。
最糟糕的是第三种,隐藏后状态栏在刘海外 。例如Redmi K30在开启后,ScreenHeight 为2174,RealHeight为2304,而关闭时为2175 和 2400 。这下连万年不变的RealHeight也变化了,这太不real了,大家自行体会 。不过目前发现未影响适配方案,不知其他手机如何 。
对于是否隐藏刘海,其实也是有各家的判断的,比如小米:
// 0:显示刘海,1:隐藏刘海Settings.Global.getInt(context.getContentResolver(), "force_black", 0);
  • 有些App会使用修改density的屏幕适配方案,这会影响获取导航栏高度的方法 。比如130px的导航栏适配后获取到的是136px 。所以这里需要使用getSystem中的density转换回去:
public static int getNavigationBarHeight(Context context){int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");if (resourceId > 0) {int height = context.getResources().getDimensionPixelSize(resourceId);// 兼容屏幕适配导致density修改float density = context.getResources().getDisplayMetrics().density;if (DENSITY != density) {return dpToPx(px2dp(context, height));}return height;}return 0;}public static final float DENSITY = Resources.getSystem().getDisplayMetrics().density;public static int dpToPx(int dpValue) {return (int) (dpValue * DENSITY + 0.5f);}public static int px2dp(Context context, int px) {return (int) (px / context.getResources().getDisplayMetrics().density + 0.5);}getSystem源码如下:
/*** Return a global shared Resources object that provides access to only* system resources (no application resources), is not configured for the* current screen (can not use dimension units, does not change based on* orientation, etc), and is not affected by Runtime Resource Overlay.*/public static Resources getSystem() {synchronized (sSync) {Resources ret = mSystem;if (ret == null) {ret = new Resources();mSystem = ret;}return ret;}}它不受资源覆盖的影响,我们可以通过它将值转换回来 。
展望未来本篇看似聊的获取高度这件事,其实伴随导航栏的发展演进,核心是是如何判断导航栏是否显示 。
通过上面的介绍,总结一下就是在“全面屏时代”,如果你想获取屏幕高度,就不要使用ScreenHeight了 。否则会出现UI展示上的问题 。而且这种问题,线上也不会崩溃,难以发现 。以前在支付宝中就发现过 PopupWindow弹出高度不正确的问题,过了好久才修复了 。


推荐阅读