前言

2017年9月,拜腾的横空出世,打破了车载主机界一直以来的沉寂,各大媒体也是不吝词藻的对它的超长中控屏进行了大肆的报道。这个时候作为同为车机供应者的诸位友商心里却不那么的平静,恨不得在发布会现场带上一把尺子。要知道,咱们国人的借鉴能力,那在世界上都是有一号的,很快的各大厂商也纷纷开始推广起了自己的超长屏。

谁的锅?

所谓超长屏,即在中控主机上既能显示汽车仪表,同时又可以显示地图导航的一块屏幕,对于应用的显示是可以做全屏展示和半屏展示切换的,即App从3840720分辨率到1280720分辨率的切换。做这块长屏适配的时候,我们本以为只要做好长屏切换短屏的时候UI界面的适配就可以高枕无忧,没想到在实机调试的时候,我们却被一个很尴尬的问题困了许久,首先让我们来看一下在3840*720屏幕上我们的显示效果。

超长屏显示截图.png

从图片中可以看到,我们的应用并没有能够充满屏幕而只是占据了左边很窄的一个部分,这的确是一个非常离奇的现象,因为我们的布局使用的是填充结构,也就是match_parent,按逻辑来说应该会是拉伸至屏幕整个宽度。

layout布局文件.png

因为用手机和夜神模拟器模拟器(3840*720分辨率)我们的应用都是有进行调试过的,于是乎,我们开始怀疑是主机厂的FrameWork定制层出了问题,因为前文提到过主机厂是可以动态控制我们应用3840与1280的显示空间的。

论证自己的假设

为了证明自己的“清白”,我们特意在新的版本中增加了对rootView的Log打印信息,方法如下:

private void printRootViewWidthAndHeight(){

fmParent.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {

@Override

public boolean onPreDraw() {

fmParent.getViewTreeObserver().removeOnPreDrawListener(this);

int width = fmParent.getWidth();

int height = fmParent.getHeight();

Logger.t(TAG).d("ParentView_width_is: "+width+" height_is: "+height);

return true;

}

});

}

parentView打印信息.png

打印结果果然不出我们所料,parentView的宽度只有1339左右,parentView是我们Activity的根布局View,跟布局只有1339,里面的子布局只显示在这个区域空间也就能够解释了。从层级上来说,parentView的上层是DecorView,而DecorView只是将ActionBar与content包含在内的一个布局,它限制宽度的可能性不大。我们都知道每个Activity都会持有一个Window,而在安卓中,Window只有唯一的一个实现类PhoneWindow ,所以每个Activity都会持有一个PhoneWindow,下一个重点怀疑对象就是这个PhoneWindow。

private void printScreenWidthAndHeightByWindowManager(){

WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);

int width = windowManager.getDefaultDisplay().getWidth();

int height = windowManager.getDefaultDisplay().getHeight();

Logger.t(TAG).d("Screen_width_is: "+width+" height_is: "+height);

}

Screen打印.png

PhoneWindow打印结果也依旧只有1339,难道说我们在代码中手动设置了Window宽度?重新复查代码后并没有发现修改Window宽度相关代码。没办法,只有从源码入手了。我们深入WindowMangager的getWidth方法。

深入源码

/**

* @deprecated Use {@link #getSize(Point)} instead.

*/

@Deprecated

public int getWidth() {

synchronized (this) {

updateCachedAppSizeIfNeededLocked();

return mCachedAppWidthCompat;

}

}

从Display源码的getWidth中可以看到,mCachedAppWidthCompat就是屏幕的宽度,那么mCachedAppWidthCompat是在何处给予的赋值呢?我们来看一下updateCachedAppSizeIfNeededLocked方法。

private void updateCachedAppSizeIfNeededLocked() {

long now = SystemClock.uptimeMillis();

if (now > mLastCachedAppSizeUpdate + CACHED_APP_SIZE_DURATION_MILLIS) {

updateDisplayInfoLocked();

mDisplayInfo.getAppMetrics(mTempMetrics, getDisplayAdjustments());

mCachedAppWidthCompat = mTempMetrics.widthPixels;

mCachedAppHeightCompat = mTempMetrics.heightPixels;

mLastCachedAppSizeUpdate = now;

}

}

从updateCachedAppSizeIfNeededLocked函数中可以看到,当超过缓存时间的时候,updateCachedAppSizeIfNeededLocked会从mTempMetrics中取出屏幕宽度作为新的宽度缓存对象,让我买来看一下mTempMetrics是什么。

// Temporary display metrics structure used for compatibility mode.

private final DisplayMetrics mTempMetrics = new DisplayMetrics();

通过查看源码我们发现mTempMetrics 其实就是DisplayMetrics 。看来要搞懂屏幕宽度的获取机制,首先要找到DisplayMetrics 的widthPixels赋值原理。深入DisplayMetrics,我们发现在DisplayMetrics内部并没有widthPixels的初始化或计算公式,widthPixels的赋值来源于外部赋值。

public void setTo(DisplayMetrics o) {

if (this == o) {

return;

}

//从DisplayMetrics获取widthPixels

widthPixels = o.widthPixels;

heightPixels = o.heightPixels;

density = o.density;

densityDpi = o.densityDpi;

scaledDensity = o.scaledDensity;

xdpi = o.xdpi;

ydpi = o.ydpi;

noncompatWidthPixels = o.noncompatWidthPixels;

noncompatHeightPixels = o.noncompatHeightPixels;

noncompatDensity = o.noncompatDensity;

noncompatDensityDpi = o.noncompatDensityDpi;

noncompatScaledDensity = o.noncompatScaledDensity;

noncompatXdpi = o.noncompatXdpi;

noncompatYdpi = o.noncompatYdpi;

}

通过对setTo函数的溯源,最终找到了一个名为updateSystemConfiguration的方法,从该方法中可以看到还是对DisplayMetrics的外部赋值,调查似乎进入了盒子效应,盒子的里面是盒子,里面的盒子里还是盒子。

/**

* Update the system resources configuration if they have previously

* been initialized.

*

* @hide

*/

public static void updateSystemConfiguration(Configuration config, DisplayMetrics metrics,

CompatibilityInfo compat) {

if (mSystem != null) {

mSystem.updateConfiguration(config, metrics, compat);

}

}

柳暗花明

在调查DisplayMetrics的过程中,安卓手机刘海屏适配的问题吸引了我的注意,自IPhoneX推出了刘海屏之后,安卓各位友商的刘海屏可谓是纷至沓来,颇有些累死安卓开发不偿命的架势。未做适配的应用运行在刘海屏上的效果和我们应用在车机上的运行效果是很相似的,都是设置了match_parent之后不能够填充满屏幕。本着谷歌不会对不完美显示置之不理的思想,我去查阅了谷歌官方对刘海屏的适配,终于发现了max_aspect这个属性,具体设置方法如下:

android:value="ratio_float"/>

max_aspect也可以通过代码设置,如下:

public void setMaxAspect() {

ApplicationInfo applicationInfo = null;

try {

applicationInfo = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA);

} catch (PackageManager.NameNotFoundException e) {

e.printStackTrace();

}

if(applicationInfo == null){

throw new IllegalArgumentException(" get application info = null, has no meta data! ");

}

applicationInfo.metaData.putString("android.max_aspect", "5.3");

}

通过查阅资料,在不设置max_aspect属性的情况下,安卓系统会取默认值,即1.86,那么这个值到底是什么呢?这个值其实是屏幕分辨率宽高的比值,在全面屏量产以前,安卓手机的分辨率多为16:9,换算成浮点型的值大概为1.777,小于最大的宽高比1.86,所以能够正常显示。而我们的车载主机的分辨率为3840*720,宽长比达到了令人发指的5.33,远大于系统默认值1.86。因为没有设置max_aspect,系统在显示应用的时候会采取默认的宽长比来显示,让我们来回看一下出问题的应用显示效果,因为高度很小,只有720,所以宽度在限定了宽高比之后只能显示1339的大小。

限制宽高比.png

了解了max_aspect的原理之后,显示不全的问题也就迎刃而解了,我们重设max_aspect的值为5.3(3840/720) ,再看一下效果。

修改max_aspect效果.png

可以看到显示不全的问题已完美解决。但是对于max_aspect我还有一个疑问,既然在全面屏以前最大是16:9只有1.77左右,为什么谷歌要设置默认值为1.86呢?难道谷歌早已看穿了一切?我觉得不会这么巧合。结合DisplayMetrics又对max_aspect进行了一番研究,我们发现在获取宽高的时候,虚拟导航菜单栏也是不会计算到宽高之中的,也就是说如果3840*720的屏幕上如果底部有一个100左右的导航栏,那么应用实际高度只有600左右,如果max_aspect赋值为5.3的话,根据600的宽高比换算出的宽度依旧不能充满屏幕,需要比5.3还要大一点的值才能够适配有虚拟按键的情况。

总结

很多曾经深信不疑的系统方法不一定真的无懈可击,就像我们最开始从WindowManager获取屏幕宽高,一味的盲目相信,很可能造成非常惨重的代价。

示例代码

参考文献

android车载支持格式,安卓全面屏适配攻略(适配超长车载主机)相关推荐

  1. 关于Android全面屏与虚拟按键适配问题

    随着手机不断更新换代,从物理按键到虚拟按键到全面屏都需要去适配. 1. 最简单的虚拟按键适配: if (Build.VERSION.SDK_INT >= Build.VERSION_CODES. ...

  2. 手淘启动页全面屏和虚拟键适配

    背景 华为对新发布的机器进行适配测试,发现手淘存在全面屏适配问题,随后还附了个3页的文档,文档比较粗泛的描述了一下不适配将会存在的问题,适配可以采取的措施,以及Google开发者文档.简单来说,因为全 ...

  3. 安卓全面屏手机获取虚拟导航栏高度

    安卓全面屏手机获取虚拟导航栏高度 问题:之前做过各种导航栏的适配,这次在适配RN的虚拟导航栏时会出现闪一下的问题,问题虽然不大但是看上去不是十分美观. 解决方法:在安卓端获取虚拟导航栏高度,在RN添加 ...

  4. (以三星S8为例)安卓全面屏手势设置教程

    安卓手机 全面屏手势设置教程(以三星S8为例) 前些日子,从少数派看到了一篇文章,介绍了一款安卓全面屏手势操作的app,流畅的不像第三方软件,如果你的安卓手机是全面屏,并且系统没有完善的全面屏手势,那 ...

  5. 安卓系统的指纹解锁_安卓全面屏都用的屏幕下指纹识别,科普超声波指纹识别的原理...

    指纹识别可以说是苹果一手带热的,虽然苹果将指纹识别普及推广,但是随着iPhoneX的发布指纹识别和home一样消失了.不过目前大部分的安卓手机,都是采用了屏幕下指纹识别.在全面屏上,安卓走得比苹果iP ...

  6. Android 屏幕适配攻略(六)设置通知样图标与启动图标适配

    Android 屏幕适配攻略(六)设置通知样图标与启动图标适配 1 Android中资源文件中的图片加载分析 Android中对屏幕的像素适配处理分类 屏幕密度 对应的标签 对应的像素 120dip ...

  7. Android 屏幕适配攻略(四)获取手机屏幕的相关信息 与动态设置控件的大小

    Android 屏幕适配攻略(四)获取手机屏幕的相关信息 与动态设置控件的大小 1 动态获取手机屏幕的 屏幕密度与对应像素比例 例如在 320 * 480 尺寸为 3.2 英寸的手机 ,对应的像素密度 ...

  8. Android 屏幕适配攻略(三)单位dp与sp

    Android 屏幕适配攻略(三)单位dp与sp 1 一般手机默认使用情况下 在安卓中,一般情况下,也就是正常使用情况下 屏幕密度 对应的标签 对应的像素 sp 120dip ldpi 1dp= 0. ...

  9. Android 屏幕适配攻略(二)单位dp与px来表示控件的尺寸

    Android 屏幕适配攻略(二)单位dp与px来表示控件的尺寸 在安卓中,将屏幕密度分为了五类 屏幕密度 对应的标签 对应的像素 120dip ldpi 1dp= 0.75px 160dip mdp ...

最新文章

  1. shiro 同时实现url和按钮的拦截_一个“保存”按钮同时存在“增删改”三种操作,该如何去实现?...
  2. Eclipse 每行 79 字符限制的提示线
  3. gpu处理信号_GPU中的并行运算,加速你的Matlab程序
  4. nodejs常用组件
  5. 使用SQLite3存储和读取数据(转)
  6. python数据分析推荐课程_coursera上有哪些值得学习的Python,数据分析的课程
  7. 设计模式 (十四) Cglib动态代理模式
  8. 【WPF】Slider 任意位置拖动
  9. AI人工智能开发的5种最佳人工智能编程语言
  10. modscan32做主站 一直显示MODBUS MESSAGE TIME-OUT
  11. 启动tomcat服务器,struts2报此错:org.apache.catalina.core.StandardContext.filterStart Exception starting filt
  12. 在visio中绘制流程图如何绘制箭头?
  13. 微信小助手:专为mac微信3.1.0发行!支持发朋友圈!支持僵尸粉清理
  14. 微信屏蔽跳去App Store链接的解决方法
  15. 手把手教你ssm整合 超级详细
  16. 5种经典程序化日内交易策略
  17. 人工智能之路学习计划
  18. sas html5,什么是sas?
  19. 计算机组装室标语,TheShy的座位这么真实?电脑上的标语吸睛,网友看后笑出声...
  20. matlab的sinxx,用MATLAB程序编程:分析方程f(x)=sinx-x/2=0正根的分布情况,并用二分法求正根近似值,使误差不超过0.01....

热门文章

  1. l-aravel项目部署
  2. 罗格朗呼叫系统管理服务器图片,罗格朗智能家居系统解决方案(图)
  3. Ubuntu16.04添加HP Laserjet Pro M128fn打印机和驱动
  4. 经验分享(一)SCAPS-1D太阳能电池模拟软件使用教程-入门
  5. Python 爬虫与HTTP协议简介
  6. MySQL Workbench教程
  7. 购机玩机完全手册(主板篇)
  8. linux echo 命令,Linux echo 命令
  9. 宝宝的成长脚印3/26
  10. 算法总结与归纳ForLiXinBo