安卓全面屏适配攻略(适配超长车载主机)

前言

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这个属性,具体设置方法如下:

<meta-data android:name="android.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获取屏幕宽高,一味的盲目相信,很可能造成非常惨重的代价。

示例代码

GitHub完整示例代码

参考文献

(1)https://android-developers.googleblog.com/2017/03/update-your-app-to-take-advantage-of.html
(2)https://blog.csdn.net/qq_33523706/article/details/79241585
(3)https://blog.csdn.net/reboot123/article/details/23710827
(4)https://blog.csdn.net/weelyy/article/details/79284332
(5)https://www.jianshu.com/p/b7594ae3900f

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,313评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,369评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,916评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,333评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,425评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,481评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,491评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,268评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,719评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,004评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,179评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,832评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,510评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,153评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,402评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,045评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,071评论 2 352

推荐阅读更多精彩内容