Android设备碎片化问题一直被开发者广为诟病,而且,因为目前手机屏幕越来越来,分辨率越来越高,大有愈演愈烈的趋势。除了等待Google给出一个更加有效的解决方案以外,我们只能尽量适应当前的环境,尽量让自己的产品能够在更多的产品上正常运行。
在Google的建议下,开发者普遍使用dpi/dp单位,进行UI设计。本文将会介绍dalvik基于dpi加载资源的规则。
DPI,全称 dots per inch,意为每英寸的直线上像素点的数量。dpi越高,屏幕的画面越清晰,画质越细腻。
目前的Android设备支持以下几种DPI:
类别 | dpi | 缩放比率 | 代表机型 |
---|---|---|---|
LDPI | 120 | 75% | 已经被市场淘汰 |
MDPI | 160 | 100% | 基本被市场淘汰 |
TVDPI | 213 | 133% | Google Nexus 7 |
HDPI | 240 | 150% | Sumsong Glaxy S2, Google Nexus S, MOTO Droid |
XHDPI | 320 | 200% | Sumsong Glaxy S3, Sumsong Note2, Google Nexus 4 |
XXHDPI | 480 | 300% | 目前市场上各品牌的旗舰机:Sumsong Glaxy S4、 Asus Padfone Infinite、HTC One、 小米3 |
上面表格中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是这样工作的:
从Google的资料中,我们可以知道dalvik匹配最佳资源的逻辑,但是还有四个问题未解释清楚:
既然没有现成的资料,就让我们自己测试:
测试设备: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 |
经过同事的测试,上面的结论有一点小问题:
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