首页 > 代码库 > 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://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 = http://www.mamicode.com/(requested->density?requested->density:160);>
关键部分已用红字标明,在多个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 = http://www.mamicode.com/new TypedValue();>
Android资源图片读取机制