Android资源图片读取机制

时间:2022-02-19 15:35:14

在新建一个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

Android资源图片读取机制

drawable目录除了这些密度类的后缀,还有比如-en表示英语环境,-port表示用于竖屏等,这里不做讨论。能够參考http://developer.android.com/guide/topics/resources/providing-resources.html

另附一张官方的屏幕大小与密度的相应表:

Android资源图片读取机制

 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)

Android资源图片读取机制

问题就来了,假设你不听建议。就整了一种密度的图片呢?那么当遇到不同密度的手机时,系统就会好(无)心(情)的对你的图进行缩放了,按文档的说法,这是为了你的应用更好看。

缩放公式:缩放后大小= 图片实际大小 × (手机密度/图片密度)

当中图片密度由图片所在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

測试结果():

Android资源图片读取机制

怎么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资源图片读取机制