標籤:raw from code eval his and 模擬器 基礎知識 技術
在建立一個Android項目時。在res目錄下會自己主動產生幾個drawable目錄,drawable-ldpi,drawable-mdpi,drawable-hdpi,一直以來都對此不太清楚。圖片應該放到哪個目錄以下。有什麼不同的影響?曾經一直都是乾脆再建立一個不帶尾碼的drawable目錄,圖片都丟進去,如今決定徹底搞清楚這個事兒。
1、基礎知識
density(密度):簡單的說就是一個比例係數,用來將Dip(裝置獨立像素)轉換成實際像素px。詳細公式是:
px = dip*density+0.5f;
densityDpi:The screen density expressed asdots-per-inch.簡單的說就是densityDpi = density*160
drawable目錄除了這些密度類的尾碼,還有比如-en表示英語環境,-port表示用於豎屏等,這裡不做討論。能夠參考http://developer.android.com/guide/topics/resources/providing-resources.html
另附一張官方的螢幕大小與密度的相應表:
2、為什麼要縮放
為了適應這麼多亂七八糟的裝置,Android官方就建議大家針對不同密度的裝置製作不同的圖片:
36x36 (0.75x) for low-density
48x48 (1.0xbaseline) for medium-density
72x72 (1.5x) for high-density
96x96 (2.0x) for extra-high-density
180x180 (3.0x) for extra-extra-high-density
192x192 (4.0x) for extra-extra-extra-high-density(launcher icon only; see note above)
問題就來了,假設你不聽建議。就整了一種密度的圖片呢?那麼當遇到不同密度的手機時,系統就會好(無)心(情)的對你的圖進行縮放了,按文檔的說法,這是為了你的應用更好看。
縮放公式:縮放後大小= 圖片實際大小 × (手機密度/圖片密度)
當中圖片密度由圖片所在drawable目錄的尾碼決定
比方一張100X100的圖放在mdpi目錄裡。在hdpi的手機上,縮放後大小= 100 * (1.5/1) = 150
就成了一張150*150的圖片。
3、android:anyDensity
(網上有些部落格對這個屬性的解釋是錯的,這裡特意提一下)
在AndroidManifest.XML檔案中能夠設定這麼一個屬性:<supports-screens android:anyDensity="true"/>
不設定的話默覺得true。
按文檔的說法(http://developer.android.com/guide/practices/screens_support.html),這個值假設為true,縮放機製為預縮放(pre-scaling),假設為false,縮放機製為自己主動縮放(auto-scaling),差別是預縮放是在讀取時縮放,自己主動縮放時在繪製的時候縮放,從速度來說預縮放要快一些。另外另一個非常重要的差別。就是假設<supports-screensandroid:anyDensity="false"/>,應用在請求螢幕參數時。系統會欺騙應用,告訴它你如今跑在一個density為1的手機上,而無論手機實際density是多少,比方實際手機是hdpi。尺寸480*800,系統會告訴應用螢幕尺寸是320(400/1.5)*533(800/1.5),然後當應用將圖片繪製到(10,10)到(100,100)的地區時。系統會將其轉換到(15,15)到(150,150),這時假設你去直接操作這些縮放後的圖。就會出些不可預期的問題。總之就是建議不要把這個屬性設為false。
按我的個人理解,這個false就是告訴系統這個應用不支援多解析度,於是系統就覺得你僅僅支援預設解析度(mdpi),系統就會給你虛擬一個mdpi的裝置,讓你顯示在上面,系統再從這上面展開或者縮小到實際裝置上。這樣既速度慢又效果不好。所以就不推薦。
4、各檔案夾讀取優先順序
如果項目內有例如以下drawable檔案夾:
drawable
drawable-nodpi
drawable-ldpi
drawable-mdpi
drawable-hdpi
drawable-xhdpi.
(假設不想系統對圖片進行縮放,能夠把圖片放到drawable-nodpi檔案夾下,從該檔案夾讀的圖片系統不會進行不論什麼縮放。)
(由下文可知。不帶尾碼的drawable檔案夾下的圖片依照drawable-mdpi處理.)
假設這些檔案夾下都可能有一張同名圖片,那系統該讀哪一張呢?
毋庸置疑,假設手機密度同樣的對應的密度檔案夾下有該圖片,那就是它了,假設沒有呢?
跟蹤原始碼看看系統是怎樣選擇圖片的(基於android4.4.2):
ImageView.java:
setImageResource()
resolveUri()
Resources.java:
getDrawable()
getValue()
AssetManager.java:
getResourceValue()
native loadResourceValue()
frameworks/base/core/jni/android_util_AssetManager.cpp:
android_content_AssetManager_loadResourceValue()
frameworks/base/libs/androidfw/AssetManager.cpp:
AssetManager::getResources()
AssetManager::getResTable()
frameworks/base/libs/androidfw/ResourceTypes.cpp:
ResTable::getResource()
ResTable::getEntry()
ssize_t ResTable::getEntry( const Package* package, int typeIndex, int entryIndex, const ResTable_config* config, const ResTable_type** outType, const ResTable_entry** outEntry, const Type** outTypeClass) const{ ********省略******* const size_t NT = allTypes->configs.size(); for (size_t i=0; i<NT; i++) { const ResTable_type* const thisType = allTypes->configs[i]; if (thisType == NULL) continue; ResTable_config thisConfig; thisConfig.copyFromDtoH(thisType->config); ********省略******* if (type != NULL) { // Check if this one is less specific than the last found. If so, // we will skip it. We checkstarting with things we most care // about to those we least care about. if(!thisConfig.isBetterThan(bestConfig, config)) { //就是這裡 TABLE_GETENTRY(ALOGI("Thisconfig is worse than last!\n")); continue; } } type = thisType; offset = thisOffset; bestConfig = thisConfig; TABLE_GETENTRY(ALOGI("Best entry so far -- using it!\n")); if (!config) break; } ********省略******* return offset + dtohs(entry->size);}
ResTable_config::isBetterThan()
bool ResTable_config::isBetterThan(const ResTable_config& o, const ResTable_config* requested) const { if (requested) { ************** if (screenType || o.screenType) { if (density != o.density) { // density is tough. Any density is potentially useful // because the system will scale it. Scaling down // is generally better than scaling up. // Default density counts as 160dpi (the system default) // TODO - remove 160 constants int h = (density?density:160); int l = (o.density?o.density:160); bool bImBigger = true; if (l > h) { int t = h; h = l; l = t; bImBigger = false; } int reqValue = (requested->density?requested->density:160); if (reqValue >= h) { // requested value higher than both l and h, give h return bImBigger; } if (l >= reqValue) { // requested value lower than both l and h, give l return !bImBigger; } // saying that scaling down is 2x better than up if (((2 * l) - reqValue) * h > reqValue * reqValue) { return !bImBigger; } else { return bImBigger; } } *********** } } return isMoreSpecificThan(o);}
關鍵區段已用紅字標明,在多個drawable下都有同名圖片時。一個資源ID相應不止一個圖片,在getEntry裡面就有一個迴圈,用isBetterThan()函數在迴圈裡把最合適的圖片選出來。
能夠看見,假設該圖片沒有指明density。density就默覺得160。這也是drawable目錄下的圖片被默覺得mdpi的原因。
在isBetterThan函數裡,density是當前資源的密度,o.density是之前的迴圈中已有的最合適的資源的密度,reqValue則是請求密度。
三個if,
第一個if:假設density和o.density都小於reqValue,那麼大的那個比較合適
第二個if: 假設density和o.density都大於reqValue,那麼小的那個比較合適
第三個if: 假設reqValue大小在density和o.density之間,先推斷
if(((2 * l) - reqValue) * h > reqValue * reqValue)
這個推斷大意就是請求密度和較小的密度相差非常小而與較大的一個密度相差非常大。那麼就覺得較小的密度更合適。
測試環境: 模擬器+Android4.4.2,當中xh和xxh是用真機+Android4.4.2測的。當中ldpi除Android4.4.2外也用Android2.3.1,hdpi除Android4.4.2外也用了Android2.1,結果並無不同。
測試檔案夾:drawable-ldpi,drawable-mdpi,drawable-hdpi,drawable-xhdpi,drawable-nodpi,drawable
測試結果():
怎麼drawable-nodpi有時候在前面有時候在後面?附兩處原始碼你就明確了
frameworks/base/include/androidfw/ResourceTypes.h:
enum { DENSITY_DEFAULT = ACONFIGURATION_DENSITY_DEFAULT, DENSITY_LOW =ACONFIGURATION_DENSITY_LOW, DENSITY_MEDIUM =ACONFIGURATION_DENSITY_MEDIUM, DENSITY_TV = ACONFIGURATION_DENSITY_TV, DENSITY_HIGH =ACONFIGURATION_DENSITY_HIGH, DENSITY_XHIGH = ACONFIGURATION_DENSITY_XHIGH, DENSITY_XXHIGH =ACONFIGURATION_DENSITY_XXHIGH, DENSITY_XXXHIGH =ACONFIGURATION_DENSITY_XXXHIGH, DENSITY_NONE =ACONFIGURATION_DENSITY_NONE };
frameworks/native/include/android/configuration.h:
ACONFIGURATION_DENSITY_DEFAULT = 0, ACONFIGURATION_DENSITY_LOW = 120, ACONFIGURATION_DENSITY_MEDIUM = 160, ACONFIGURATION_DENSITY_TV = 213, ACONFIGURATION_DENSITY_HIGH = 240, ACONFIGURATION_DENSITY_XHIGH = 320, ACONFIGURATION_DENSITY_XXHIGH = 480, ACONFIGURATION_DENSITY_XXXHIGH = 640, ACONFIGURATION_DENSITY_NONE = 0xffff,
可見drawable-nodpi檔案夾下的圖片密度值為0xffff。即65535。帶入推斷一算,結果正如測試所得。
無圖無真相,所以貼一張hdpi環境的測試圖:
想要知道會讀取到哪張圖,能夠這樣:
TypedValue typedValue = new TypedValue();getResources().getValue(R.drawable.test,typedValue,true);//然後typedValue.string的值就是實際讀取的圖片路徑
Android資源圖片讀取機制