Android 基于dpi的資源加載
? ? Android設(shè)備碎片化問題一直被開發(fā)者廣為詬病,而且,因?yàn)槟壳笆謾C(jī)屏幕越來越來,分辨率越來越高,大有愈演愈烈的趨勢。除了等待Google給出一個(gè)更加有效的解決方案以外,我們只能盡量適應(yīng)當(dāng)前的環(huán)境,盡量讓自己的產(chǎn)品能夠在更多的產(chǎn)品上正常運(yùn)行。
? ? 在Google的建議下,開發(fā)者普遍使用dpi/dp單位,進(jìn)行UI設(shè)計(jì)。本文將會介紹dalvik基于dpi加載資源的規(guī)則。
? ? DPI,全稱 dots per inch,意為每英寸的直線上像素點(diǎn)的數(shù)量。dpi越高,屏幕的畫面越清晰,畫質(zhì)越細(xì)膩。
? ? 目前的Android設(shè)備支持以下幾種DPI:
? ? 上面表格中dpi又稱歸一化DPI,可以算是一種“理想化”的DPI標(biāo)準(zhǔn)。
? ? 以Eclipse新建Android項(xiàng)目默認(rèn)提供的機(jī)器人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,使用者會看到一個(gè)0.3*0.3英寸的機(jī)器人ICON,而在Sumsong的Glaxy S2上(4.3寸,480*800,HDPI)上,會加載drawable-hdpi中到ic_launcher.png,72/240=0.3,使用者仍舊會看到一個(gè)0.3*0.3英寸的機(jī)器人ICON。所以,從結(jié)果上來說,使用者在不同的設(shè)備上得到了相同到UI效果,而且,因?yàn)樵贜ote2上使用了96*96的ICON, 可以獲得更加精細(xì)的畫面,這似乎是個(gè)很理想到結(jié)果:即維持了體驗(yàn)的一致性,又最大化的利用了屏幕的顯示效果。
? ? 但是,理論和實(shí)際總是會有些微妙的差別。設(shè)備制造廠商為了迎合消費(fèi)者的喜好,會生產(chǎn)各種屏幕尺寸的設(shè)備,而Android的歸一化DPI只有6種(其中2種還是被市場淘汰的),最終呈現(xiàn)給開發(fā)者的結(jié)果就是,硬件設(shè)備的物理dpi(或者說ppi,pixel per inch)總是或大或小,和歸一化dpi有一定差距,廠商會根據(jù)自己的需要設(shè)定設(shè)備的歸一化DPI,而dalvik進(jìn)而根據(jù)這個(gè)歸一化DPI來加載資源,繪制界面。
? ? Note2的物理分辨率其實(shí)是267ppi(其實(shí),更接近HDPI,而非XHDPI),而非dalvik認(rèn)為的320,96/267=0.36,所以,使用者實(shí)際看到的是一個(gè)0.36*0.36的ICON,而S2的物理分辨率是219,72/219=0.33,所以使用者實(shí)際看到的ICON為0.33*0.33寸。所以,界面的實(shí)際效果會和開發(fā)者的預(yù)想有一定偏差。
? ? 幸運(yùn)的是,只要設(shè)備制造商設(shè)定的歸一化DPI和設(shè)備的物理分辨率差距不會大的離譜(想象一下,DPI設(shè)置不佳導(dǎo)致大部分app都無法正常運(yùn)行的設(shè)備,能夠大賣么?),界面的最終效果還是能夠達(dá)到開發(fā)者的要求的。
? ? 首先,我們需要明白dalvik匹配最佳資源的策略,從Google的官方資料,我們可以知道dalvik是這樣工作的:
根據(jù)設(shè)備的屬性,排除所有存在沖突屬性的資源,注意兩點(diǎn): 不對Screen pixel density &?screen size兩個(gè)屬性做檢查如果在這一步就排除了所有資源,則拋出ResourceNotFoundException挑選當(dāng)前優(yōu)先級最高的屬性 屬性的優(yōu)先級排序?yàn)椋篗CC 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如果存在包含了當(dāng)前屬性的資源,則執(zhí)行4,否則跳過當(dāng)前屬性,執(zhí)行2排除所有不包含該屬性的資源 如果當(dāng)前屬性為screnn pixel density,則以如下方式進(jìn)行匹配: 如果有最匹配的資源(e.g. 設(shè)備是HDPI,存在hdpi的資源),則刪除其他的資源如果沒有最佳匹配資源,優(yōu)先匹配更高dpi的資源,縮小合適的比例以后使用(e.g. 設(shè)備是HDPI,未能找到hdpi的資源,但是有xhdpi的資源,則把XHDPI的資源縮小的3/4以后使用),并排除其他的資源(Google解釋說,因?yàn)閳?zhí)行縮小操作比執(zhí)行放大操作更加方便,所以高dpi資源優(yōu)先與低dpi資源,不過,個(gè)人認(rèn)為對于大部分圖片來說,大圖縮小造成的失真應(yīng)該是小于小圖放大造成的失真)如果沒最佳匹配的資源,也不存在更高dpi的資源,則使用dpi更低的資源,并放大合適的比例以后使用(e.g. 設(shè)備為HDPI,未能找到hdpi以及更高的資源,單存在mdpi的資源,則把mdpi的資源放大到3/2以后使用),并刪排除其他資源如果當(dāng)前性為screen size,則以如下方式進(jìn)行匹配: 如果有最佳匹配資源(e.g. 設(shè)備為large,存在large的資源),則排除其他資源如果沒有最佳匹配資源,則使用更小size的資源(e.g. 設(shè)備為large,但是存在normal資源,則使用normal資源),排除其他資源如果沒有最佳匹配資源,也沒有更小size的資源,則拋出ResourceNotFoundException是否僅剩余唯一的資源,是則執(zhí)行6,否則執(zhí)行2以唯一的資源為最佳資源,匹配結(jié)束? ? 從Google的資料中,我們可以知道dalvik匹配最佳資源的邏輯,但是還有四個(gè)問題未解釋清楚:
當(dāng)最佳dpi資源不存在,但是高dpi資源大于一個(gè)時(shí)(e.g. 設(shè)備為hdpi,但是apk僅提供xhdpi & xxhdpi資源),如何選擇資源?當(dāng)最佳dpi資源&高dpi資源都不存在,但是低dpi資源大于1個(gè)時(shí)(e.g. 設(shè)備為hdpi,但是apk僅提供mdpi & ldpi資源),如何選擇資源?當(dāng)drawable資源不包含screen pixel density(e.g. 文件夾名為”drawable“,下文中稱之為default)屬性時(shí),如何選擇資源?當(dāng)資源被nodpi修飾時(shí),如何選擇資源?? ? 既然沒有現(xiàn)成的資料,就讓我們自己測試:
測試設(shè)備:Nexus 7(TVDPI)
測試代碼:
? ? 僅在Layout根目錄添加了一個(gè)ImageView,未修改Eclipse自動(dòng)生成的java代碼。
測試圖片:rabit.png, size:600*450pixel
問題1:
僅提供XHDPI & XXHDPI兩種資源:
運(yùn)行結(jié)果:
從執(zhí)行結(jié)果,我們可以看到,dalvik使用了xhdpi的資源,并且進(jìn)行了合適的縮小:600*1.33/2=399,450*1.33/2=299.
結(jié)論:當(dāng)最佳dpi資源不存在,而高dpi資源大于1個(gè)時(shí),選擇更接近設(shè)備的資源(即較低dpi的資源),并根據(jù)縮放比縮小合適的比例后使用。
問題2:
僅提供ldpi和mdpi兩種資源:
運(yùn)行結(jié)果:
從運(yùn)行結(jié)果,我們可以知道davlik加載了mdpi,并放大了合適的倍數(shù):450*1.33/1 = 599(因?yàn)檫@里圖片水平方向上已經(jīng)被截?cái)嗌僭S,所以未測量)。
結(jié)論:當(dāng)最佳dpi資源&高dpi資源都不存在,而且dpi資源大于1個(gè)時(shí),選擇更接近設(shè)備的低dpi資源,并根據(jù)縮放比放大合適的比例后使用。
問題三:
第一步測試:
僅提供mdpi,ldpi,default三種資源:
運(yùn)行結(jié)果:
從運(yùn)行結(jié)果來看,mdpi資源的優(yōu)先級要高于default。
第二步測試:
在第一步的基礎(chǔ)上,刪除mdpi下的rabit.png:
運(yùn)行結(jié)果:
從運(yùn)行結(jié)果看,default的優(yōu)先級高于ldpi。
另外,我們可以看到default的縮放比和mdpi是一致的,由此我猜測,因?yàn)锳ndroid是以mdpi作為dpi基準(zhǔn),所以,default等同與mdpi,但是,資源優(yōu)先級而言mdpi高于default。根據(jù)這個(gè)猜測,對于ldpi的設(shè)備來說,優(yōu)先級順序會是這樣的:ldpi>mdpi>default>hdpi,不過找不到ldpi的設(shè)備,也無法驗(yàn)證。
結(jié)論:對于高于hdpi(包括hdpi)的設(shè)備來說,default資源的優(yōu)先級高于ldpi但是低于mdpi,并且縮放比等于mdpi。
問題4:
步驟1,僅提供ldpi和nodpi的資源:
運(yùn)行結(jié)果:
從運(yùn)行結(jié)果來看,ldpi的優(yōu)先級高于nodpi。
步驟2,在步驟1的基礎(chǔ)上刪除ldpi的資源:
運(yùn)行結(jié)果:
從運(yùn)行結(jié)果,我們看到,圖片的確如google官方資料所說,未經(jīng)過任何縮放。不過,google提供nodpi似乎是非常的不愿意啊,優(yōu)先級最低,僅在無圖可用的情況下,才會使用nodpi的資源。
原則上來說,dalvik優(yōu)先使用符合設(shè)備dpi的資源,其次是dpi較低的高dpi資源,再次是dpi較高的高dpi資源,最后采用nodpi的資源,由此,根據(jù)設(shè)備自身的dpi的不同,不同dpi資源的優(yōu)先級是有差異的(忽略mdpi&hdpi):
設(shè)備dpi 優(yōu)先級順序(由高到低) 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資源前,還需要根據(jù)縮放比進(jìn)行縮小/放大操作。
? ? 經(jīng)過同事的測試,上面的結(jié)論有一點(diǎn)小問題:
? ? hdpi的設(shè)備對于dpi的優(yōu)先級順序?qū)嶋H上是這樣的:?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