Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android資源圖片讀取機制

Android資源圖片讀取機制

編輯:關於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),區別是預縮放是在讀取時縮放,自動縮放時在繪制的時候縮放,從速度來說預縮放要快一些。另外還有一個很重要的區別,就是如果,應用在請求屏幕參數時,系統會欺騙應用,告訴它你現在跑在一個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; 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的值就是實際讀取的圖片路徑

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved