Android 基于dpi的资源加载
扫描二维码
随时随地手机看文章
Android设备碎片化问题一直被开发者广为诟病,而且,因为目前手机屏幕越来越来,分辨率越来越高,大有愈演愈烈的趋势。除了等待Google给出一个更加有效的解决方案以外,我们只能尽量适应当前的环境,尽量让自己的产品能够在更多的产品上正常运行。
在Google的建议下,开发者普遍使用dpi/dp单位,进行UI设计。本文将会介绍dalvik基于dpi加载资源的规则。
DPI,全称 dots per inch,意为每英寸的直线上像素点的数量。dpi越高,屏幕的画面越清晰,画质越细腻。
目前的Android设备支持以下几种DPI:
上面表格中dpi又称归一化DPI,可以算是一种“理想化”的DPI标准。
以Eclipse新建Android项目默认提供的机器人ICON为例:
在drwable-hdpi的文件夹中,ic_launcher.png的size为72*72,而drawable-xhdpi文件夹中的ic_launcher.png的size为96*96。
理论上来说,在Sumsong Note2(5.5寸,1280*700,XHDPI)上,会加载drawable-xhdpi中的ic_launcher.png,96/320=0.3,使用者会看到一个0.3*0.3英寸的机器人ICON,而在Sumsong的Glaxy S2上(4.3寸,480*800,HDPI)上,会加载drawable-hdpi中到ic_launcher.png,72/240=0.3,使用者仍旧会看到一个0.3*0.3英寸的机器人ICON。所以,从结果上来说,使用者在不同的设备上得到了相同到UI效果,而且,因为在Note2上使用了96*96的ICON, 可以获得更加精细的画面,这似乎是个很理想到结果:即维持了体验的一致性,又最大化的利用了屏幕的显示效果。
但是,理论和实际总是会有些微妙的差别。设备制造厂商为了迎合消费者的喜好,会生产各种屏幕尺寸的设备,而Android的归一化DPI只有6种(其中2种还是被市场淘汰的),最终呈现给开发者的结果就是,硬件设备的物理dpi(或者说ppi,pixel per inch)总是或大或小,和归一化dpi有一定差距,厂商会根据自己的需要设定设备的归一化DPI,而dalvik进而根据这个归一化DPI来加载资源,绘制界面。
Note2的物理分辨率其实是267ppi(其实,更接近HDPI,而非XHDPI),而非dalvik认为的320,96/267=0.36,所以,使用者实际看到的是一个0.36*0.36的ICON,而S2的物理分辨率是219,72/219=0.33,所以使用者实际看到的ICON为0.33*0.33寸。所以,界面的实际效果会和开发者的预想有一定偏差。
幸运的是,只要设备制造商设定的归一化DPI和设备的物理分辨率差距不会大的离谱(想象一下,DPI设置不佳导致大部分app都无法正常运行的设备,能够大卖么?),界面的最终效果还是能够达到开发者的要求的。
首先,我们需要明白dalvik匹配最佳资源的策略,从Google的官方资料,我们可以知道dalvik是这样工作的:
根据设备的属性,排除所有存在冲突属性的资源,注意两点: 不对Screen pixel density & screen size两个属性做检查如果在这一步就排除了所有资源,则抛出ResourceNotFoundException挑选当前优先级最高的属性 属性的优先级排序为:MCC and MNC、 Language and region、 Layout Direction、 smallestWidth、 Aailable width、 Aailable height、 Screen size、 Screen aspect、 Screen orientation、 UI mode、 Night Mode、 Screen pixel density(dpi)、 Touchscreen type、 Keyboard availability、 Primary test input method、 Navigation key availability、 Primary non-touch navigation method、 Platform Version如果存在包含了当前属性的资源,则执行4,否则跳过当前属性,执行2排除所有不包含该属性的资源 如果当前属性为screnn pixel density,则以如下方式进行匹配: 如果有最匹配的资源(e.g. 设备是HDPI,存在hdpi的资源),则删除其他的资源如果没有最佳匹配资源,优先匹配更高dpi的资源,缩小合适的比例以后使用(e.g. 设备是HDPI,未能找到hdpi的资源,但是有xhdpi的资源,则把XHDPI的资源缩小的3/4以后使用),并排除其他的资源(Google解释说,因为执行缩小操作比执行放大操作更加方便,所以高dpi资源优先与低dpi资源,不过,个人认为对于大部分图片来说,大图缩小造成的失真应该是小于小图放大造成的失真)如果没最佳匹配的资源,也不存在更高dpi的资源,则使用dpi更低的资源,并放大合适的比例以后使用(e.g. 设备为HDPI,未能找到hdpi以及更高的资源,单存在mdpi的资源,则把mdpi的资源放大到3/2以后使用),并删排除其他资源如果当前性为screen size,则以如下方式进行匹配: 如果有最佳匹配资源(e.g. 设备为large,存在large的资源),则排除其他资源如果没有最佳匹配资源,则使用更小size的资源(e.g. 设备为large,但是存在normal资源,则使用normal资源),排除其他资源如果没有最佳匹配资源,也没有更小size的资源,则抛出ResourceNotFoundException是否仅剩余唯一的资源,是则执行6,否则执行2以唯一的资源为最佳资源,匹配结束从Google的资料中,我们可以知道dalvik匹配最佳资源的逻辑,但是还有四个问题未解释清楚:
当最佳dpi资源不存在,但是高dpi资源大于一个时(e.g. 设备为hdpi,但是apk仅提供xhdpi & xxhdpi资源),如何选择资源?当最佳dpi资源&高dpi资源都不存在,但是低dpi资源大于1个时(e.g. 设备为hdpi,但是apk仅提供mdpi & ldpi资源),如何选择资源?当drawable资源不包含screen pixel density(e.g. 文件夹名为”drawable“,下文中称之为default)属性时,如何选择资源?当资源被nodpi修饰时,如何选择资源?既然没有现成的资料,就让我们自己测试:
测试设备:Nexus 7(TVDPI)
测试代码:
仅在Layout根目录添加了一个ImageView,未修改Eclipse自动生成的java代码。
测试图片:rabit.png, size:600*450pixel
问题1:
仅提供XHDPI & XXHDPI两种资源:
运行结果:
从执行结果,我们可以看到,dalvik使用了xhdpi的资源,并且进行了合适的缩小:600*1.33/2=399,450*1.33/2=299.
结论:当最佳dpi资源不存在,而高dpi资源大于1个时,选择更接近设备的资源(即较低dpi的资源),并根据缩放比缩小合适的比例后使用。
问题2:
仅提供ldpi和mdpi两种资源:
运行结果:
从运行结果,我们可以知道davlik加载了mdpi,并放大了合适的倍数:450*1.33/1 = 599(因为这里图片水平方向上已经被截断少许,所以未测量)。
结论:当最佳dpi资源&高dpi资源都不存在,而且dpi资源大于1个时,选择更接近设备的低dpi资源,并根据缩放比放大合适的比例后使用。
问题三:
第一步测试:
仅提供mdpi,ldpi,default三种资源:
运行结果:
从运行结果来看,mdpi资源的优先级要高于default。
第二步测试:
在第一步的基础上,删除mdpi下的rabit.png:
运行结果:
从运行结果看,default的优先级高于ldpi。
另外,我们可以看到default的缩放比和mdpi是一致的,由此我猜测,因为Android是以mdpi作为dpi基准,所以,default等同与mdpi,但是,资源优先级而言mdpi高于default。根据这个猜测,对于ldpi的设备来说,优先级顺序会是这样的:ldpi>mdpi>default>hdpi,不过找不到ldpi的设备,也无法验证。
结论:对于高于hdpi(包括hdpi)的设备来说,default资源的优先级高于ldpi但是低于mdpi,并且缩放比等于mdpi。
问题4:
步骤1,仅提供ldpi和nodpi的资源:
运行结果:
从运行结果来看,ldpi的优先级高于nodpi。
步骤2,在步骤1的基础上删除ldpi的资源:
运行结果:
从运行结果,我们看到,图片的确如google官方资料所说,未经过任何缩放。不过,google提供nodpi似乎是非常的不愿意啊,优先级最低,仅在无图可用的情况下,才会使用nodpi的资源。
原则上来说,dalvik优先使用符合设备dpi的资源,其次是dpi较低的高dpi资源,再次是dpi较高的高dpi资源,最后采用nodpi的资源,由此,根据设备自身的dpi的不同,不同dpi资源的优先级是有差异的(忽略mdpi&hdpi):
设备dpi 优先级顺序(由高到低) tvdpi tvdpi>hdpi>xhdpi>xxhdpi>mdpi>default>ldpi>nodpi hdpi hdpi>xhdpi>xxhdpi>tvdpi>mdpi>default>ldpi>nodpi xhdpi xhdpi>xxhdpi>hdpi>tvdpi>mdpi>default>ldpi>nodpi xxhdpi xxhdpi>xhdpi>hdpi>tvdpi>mdpi>default>ldpi>nodpi 另外,除了nodpi以外,使用其他dpi资源前,还需要根据缩放比进行缩小/放大操作。
经过同事的测试,上面的结论有一点小问题:
hdpi的设备对于dpi的优先级顺序实际上是这样的: hdpi>tvdpi>xhdpi>xxhdpi>>mdpi>default>ldpi>nodpi(可能Google觉得tvdpi(213)还是很接近hdpi(240)的,可堪一用)
参考资料:
http://developer.android.com/guide/topics/resources/providing-resources.html
http://developer.android.com/guide/practices/screens_support.html
http://ivan-ru.iteye.com/blog/1711414