• 【转】被误用的屏幕分辨率限定符


    原文:https://www.jianshu.com/p/b0253e457031

    前言

    最近要给项目换几个图片资源外加调整一下UI尺寸细节,发现项目里面使用了有很多类似这种限定屏幕分辨率的配置限定符:


     
    屏幕分辨率配置限定符

    乍一看以为是针对特定的屏幕弄的一些特殊资源,似乎用法是当且仅当屏幕像素点数量和限定符一致时就能匹配到,所以里面会放一些针对该分辨率的特殊资源。然而经过一番测试,实际情况并不是我想象的那样,甚至让我产生了更多疑问。

    1. AxB的写法,到底A和B,哪个代表宽度,哪个代表高度?
    2. A和B的单位是dp还是px?
    3. 为什么某些情况下匹配不到我指定的屏幕分辨率目录下的资源?

    目前已公开的情报

    文档

    遇到这种问题,第一个当然是要查关于配置限定符的官方文档,然而文档里并没有提到AxB这种写法的配置限定符,官方文档里列出了19个配置限定符(截至API 27),按照顺序,它们分别是:MCC和MNC,语言和区域,布局方向,最小dp宽度,可用dp宽度,可用dp高度,屏幕尺寸(值为normal, xlarge等),屏幕纵横比,是否圆形屏幕,UI模式,是否夜间模式,屏幕像素密度(值为hdpi, xhdpi等),触摸屏类型,键盘可用性,主要文本输入法(感觉叫硬件键盘类型更准确),导航键可用性,主要非触摸导航方法,平台版本。

    完全没有提到屏幕分辨率限定符这种写法,是否代表这种写法完全不存在呢?

    Android Studio

    在AS里新建Android资源目录的时候,IDE会提示我们补充配置限定符,奇怪的是里面一共有20种配置限定符可选,多了一个叫Dimension的配置限定符:

     
    Dimension配置限定符

    随便填两个值,发现写法和我在项目里看到的是一样的,都是AxB的形式:

     
    Dimension配置限定符举例

    这说明使用屏幕分辨率作为限定符是一个合法的写法,不然IDE也不会大费周章的把它做进去了。但这里有两点让我很疑惑:

    1. IDE并没有告诉我是长在前还是宽在前,values-1920x1080values-1080x1920是一样的吗?
    2. 为什么Dimension限定符的描述是Screen dimension in dp?如果单位是dp,那项目里面所有的相关配置不都写错了吗?

    网络文章

    网上有很多提到这种写法的文章,似乎从来没有对AxB这种形式的限定符有提出任何疑问,只知道肯定是有效的。

    从这些文章的意思看来,这种屏幕分辨率限定符,可以限制Android只在遇到屏幕分辨率完全匹配的时候才取得相应的特殊资源,比如2560x1536的手机肯定不会使用1920x1080限定的资源。

    源码里找答案

    没办法,只能去翻源码找答案了,这是效率最低的一种方式,毕竟很多逻辑描述起来简单,实现起来代码不知道要写多复杂。在看源码之前,还是先了解一下Android是如何查找最佳匹配资源的。

    查找最佳匹配资源

    Android查找最佳匹配资源的过程如下


     
    Android查找最佳匹配资源流程

    Android反复执行这个图中的2~4步骤直到只剩下唯一一个资源目录

    这个图描述的逻辑很清晰,不过还是要指出其中的关键点:

    1. 步骤1事先排除了不匹配的配置限定符,比如对于可用高度来说,如果系统的可用高度是400dp,而某个限定符组合限定可用高度为500dp,那么显然这个限定符肯定得不到满足,直接就被排除在外了。完全不会参与资源查找,除非系统的配置出现了变化,使得该限定被满足了。
    2. 步骤2中识别限定符,是按顺序来的。不是说限定符越多,优先级越高,比如一台语言为英语的手机上,drawable-en中的资源比drawable-port-notouch-12key中的资源优先级高。因为当识别到en的时候,drawable-port-notouch-12key会被步骤4排除掉。

    知道了Android的查找资源策略,我需要从源码中找到以下问题的答案:

    1. 分辨率限定符AxB和BxA的写法有什么区别
    2. 分辨率限定符中尺寸的单位到底是dp还是px
    3. 排除与系统配置不匹配的分辨率限定符的策略是什么(即步骤1如何排除不合法的分辨率限定资源)
    4. 分辨率限定符处于限定符的处理顺序中的什么位置(即步骤2识别限定符时,何时识别分辨率限定符)
    5. 分辨率限定符的匹配策略是怎样的,是否精确匹配(即步骤3如何匹配最佳分辨率限定符资源)

    AxB与BxA的区别,以及dp还是px

    要回答这个问题,必须知道Android如何解析分辨率限定符,以及系统自身的配置如何存储。

    Android如何解析分辨率限定符

    frameworks/base/tools/aapt/AaptConfig.cpp

    bool parseScreenSize(const char* name, ResTable_config* out) {
        if (strcmp(name, kWildcardName) == 0) {
            if (out) {
                out->screenWidth = out->SCREENWIDTH_ANY;
                out->screenHeight = out->SCREENHEIGHT_ANY;
            }
            return true;
        }
    
        const char* x = name;
        while (*x >= '0' && *x <= '9') x++;
        if (x == name || *x != 'x') return false;
        String8 xName(name, x-name);
        x++;
    
        const char* y = x;
        while (*y >= '0' && *y <= '9') y++;
        if (y == name || *y != 0) return false;
        String8 yName(x, y-x);
    
        uint16_t w = (uint16_t)atoi(xName.string());
        uint16_t h = (uint16_t)atoi(yName.string());
        if (w < h) {
            return false;
        }
    
        if (out) {
            out->screenWidth = w;
            out->screenHeight = h;
        }
    
        return true;
    }
    

    可以看到,Android在解析分辨率限定符时,默认第一个数字大于等于第二个数字,否则解析就失败了。Android将解析到的第一个数字保存为宽,将第二个数字保存为高。也就是说,1920x10801080x1920,只有1920x1080是合法的。

    系统自身的配置

    /frameworks/base/core/java/android/content/res/ResourcesImpl.java

    public void updateConfiguration(Configuration config, DisplayMetrics metrics,
                                    CompatibilityInfo compat) {
                ...
    
                final int width, height;
                if (mMetrics.widthPixels >= mMetrics.heightPixels) {
                    width = mMetrics.widthPixels;
                    height = mMetrics.heightPixels;
                } else {
                    //noinspection SuspiciousNameCombination
                    width = mMetrics.heightPixels;
                    //noinspection SuspiciousNameCombination
                    height = mMetrics.widthPixels;
                }
    
                ...
    
                mAssets.setConfiguration(mConfiguration.mcc, mConfiguration.mnc,
                        adjustLanguageTag(mConfiguration.getLocales().get(0).toLanguageTag()),
                        mConfiguration.orientation,
                        mConfiguration.touchscreen,
                        mConfiguration.densityDpi, mConfiguration.keyboard,
                        keyboardHidden, mConfiguration.navigation, width, height,
                        mConfiguration.smallestScreenWidthDp,
                        mConfiguration.screenWidthDp, mConfiguration.screenHeightDp,
                        mConfiguration.screenLayout, mConfiguration.uiMode,
                        mConfiguration.colorMode, Build.VERSION.RESOURCES_SDK_INT);
    
                ...
    }
    

    这里发现系统配置里面,屏幕分辨率宽高使用heightPixels和widthPixels,而且还故意将值大的设为width,因此系统配置里,分辨率配置,也是宽大于高,同时这里也说明分辨率限定符的单位是px而不是dp,因为系统配置里这俩值直接从heightPixels和widthPixels里取得并保存。

    结论

    分辨率限定符,单位是px。

    必须将长边写在前,短边写在后,反之则非法。

    排除策略与是否精确匹配

    /frameworks/base/libs/androidfw/ResourceTypes.cpp

    bool ResTable_config::match(const ResTable_config& settings) const {
        ...
        if (screenSize != 0) {
            if (screenWidth != 0 && screenWidth > settings.screenWidth) {
                return false;
            }
            if (screenHeight != 0 && screenHeight > settings.screenHeight) {
                return false;
            }
        }
        ...
        return true;
    }
    

    该方法返回false表示需要在第一个步骤中予以排除,settings中存储的是系统配置,可以看出,当分辨率限定符中的任一维度大于系统配置的对应维度时,会被排除。这同时说明分辨率限定符不是精确匹配的。

    比如我们提供了2020x1080、1080x740以及960x540限定的资源,一台1920x1080的手机,会排除掉2020x1080的资源,匹配1080x740或960x540中的一个资源。

    结论

    分辨率限定符的排除策略是任一维度大于系统对应维度则排除。

    分辨率限定符的匹配不是精确地,尺寸小于等于系统配置时即可能匹配到。

    识别顺序

    /frameworks/base/libs/androidfw/ResourceTypes.cpp中的ResTable_config::isBetterThan方法中的代码顺序即识别顺序。这里的识别顺序和文档里介绍的限定符识别顺序是一致的,屏幕分辨率限定符在其中处于倒数第二个,排在平台版本之前。

    结论

    分辨率限定符的识别顺序处于倒数第二,排在平台版本之前。

    匹配策略

    /frameworks/base/libs/androidfw/ResourceTypes.cpp

    bool ResTable_config::isBetterThan(const ResTable_config& o,
            const ResTable_config* requested) const {
        if (requested) {
            ...
    
            if (screenSize || o.screenSize) {
                // "Better" is based on the sum of the difference between both
                // width and height from the requested dimensions.  We are
                // assuming the invalid configs (with smaller sizes) have
                // already been filtered.  Note that if a particular dimension
                // is unspecified, we will end up with a large value (the
                // difference between 0 and the requested dimension), which is
                // good since we will prefer a config that has specified a
                // size value.
                int myDelta = 0, otherDelta = 0;
                if (requested->screenWidth) {
                    myDelta += requested->screenWidth - screenWidth;
                    otherDelta += requested->screenWidth - o.screenWidth;
                }
                if (requested->screenHeight) {
                    myDelta += requested->screenHeight - screenHeight;
                    otherDelta += requested->screenHeight - o.screenHeight;
                }
                if (myDelta != otherDelta) {
                    return myDelta < otherDelta;
                }
            }
    
            ...
    
            return false;
        }
        return isMoreSpecificThan(o);
    }
    

    这里的匹配策略是两个维度的差值之和越小,则优先级越高,也就是说越接近屏幕的尺寸的限定资源,优先级越高,当然这里进行对比的时候,已经将大于屏幕尺寸的资源排除掉了。

    结论

    屏幕分辨率限定符的匹配策略是选取最接近屏幕分辨率的资源。

    讨论

    经过上面的分析,可以得知屏幕分辨率限定符,非精确匹配,且要求两个维度均小于等于屏幕的相应维度的时候才能被匹配,匹配策略选取最接近屏幕分辨率的资源,识别顺序排倒数第二,单位是px,长边值写在前面。

    从这个限定符的用法来看,它的名字应该叫最小可用分辨率限定符,才能准确表达它的含义。

    这里要注意一点,一台en-1920x1080的手机,如果我们提供了一个图片的如下两个版本:
    drawable-en
    drawable-1920x1080
    最终匹配到的是drawable-en版本,因为分辨率限定符的识别顺序实在太靠后,导致直接在识别en时被排除了。

    测试最好用原生ROM。我的小米4上,即便是分辨率限定符反过来写,如1080x1920,MIUI也能正常识别,一度让我以为我代码看错了,然后找了台Nexus 5X运行,则和源码一致,认定其不合法,不予匹配。

    从结论来看,这个屏幕分辨率限定符并不是很多人想象中那样工作的,不是只适配特定分辨率,而是会影响到所有完全大于该分辨率的屏幕,用了得不偿失,建议大家不要用这个限定符。



    作者:Shawon
    链接:https://www.jianshu.com/p/b0253e457031
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  • 相关阅读:
    C#面向对象五(继承、抽象类和抽象方法、多态、虚方法、is、as、new覆盖关键字)
    C#面向对象四(文件与目录操作、序列化与反序列化、XML)
    C#面向对象三(常用控件)
    C#面向对象二(集合泛型及排序)
    C#面向对象一(方法、封装、类、两种数据类型)
    如何最快找到重点 —— 小小码虫瞎想の效率与绩效篇(一)
    Android Telephony —— 手机信号实时变化源码分析过程记录
    Android 某些配置记录
    Macbook Pro 使用小记
    fhq treap 范浩强平衡树
  • 原文地址:https://www.cnblogs.com/tc310/p/12370369.html
Copyright © 2020-2023  润新知