編輯:關於Android編程
在新建一個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://develZ喎?/kf/ware/vc/" target="_blank" class="keylink">vcGVyLmFuZHJvaWQuY29tL2d1aWRlL3RvcGljcy9yZXNvdXJjZXMvcHJvdmlkaW5nLXJlc291cmNlcy5odG1sPC9wPgo8cD7B7bi90rvVxbnZt721xMbBxLu089Ch0+vD3LbItcS21NOmse2jujwvcD4KPHA+PGltZyBzcmM9"/uploadfile/Collfiles/20140929/20140929091627115.png" alt="\">
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文件裡可以設置這麼一個屬性:
不設置的話默認為true。
按文檔的說法(http://developer.android.com/guide/practices/screens_support.html),這個值如果為true,縮放機制為預縮放(pre-scaling),如果為false,縮放機制為自動縮放(auto-scaling),區別是預縮放是在讀取時縮放,自動縮放時在繪制的時候縮放,從速度來說預縮放要快一些。另外還有一個很重要的區別,就是如果
按我的個人理解,這個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; iconfigs[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存儲系統如何優化?答案是我也不知道…那為什麼會想到要寫這篇文章哪?主要是因為有天晚上和以前一個同事討論到Android手機存儲系統的優化問題,
在Android的源代碼中,經常會看到形如:sp<xxx>、wp<xxx>這樣的類型定義,這其實是Android中的智能 指針。智能
因為項目中需要用到所以實現的一個橫向的照片浏覽器,使用橫向SrollView實現。實現效果如下:實現思路:在開始做之前呢,本著有輪子就用輪子的精神,也還是去百度找了很久,
如果你使用的是小米手機或者MIUI系統,你知道MIUI系統的撥號界面可以直接查詢你的手機話費余額嗎?這麼高大上的功能沒用過吧?還在撥打客服熱線、發送短信查詢