背景
对于Android来说,为了方便开发,一方面方便资源的管理,提高代码的可读性,另外一方面方便适配,Android实现了资源和代码分离。要想做到资源代码分离需要解决以下几个问题:
- 简单编程
- 方便适配(根据不同设备环境选择不同资源加载)
- 加快资源查找速度
- 减少空间占用
针对简单编程方面,Android对资源进行了统一编址,编程中使用R.${type}.id 来查找相应资源,所以要在资源的编译过程中对资源进行编址,来生成R.java文件,生成资源id,这样大大简化编程中对资源的使用。
方便适配方面主要做的工作就是要在不同的设备环境下选择不同的资源来加载。比如一个叫icon的drawable资源,可以使用不同大小的图片放在drawable,drawable-hdpi, drawable-xxhdpi文件夹下面,我们编程的时候是需要通过R.drawable.icon来加载资源,系统会根据屏幕的密度来自动选择合适的drawable文件夹下的icon资源来进行加载,这其实也大大的简化了应用开发者的编程工作,更简化了多设备适配的工作。
加快资源查找速度 和 减少空间占用发面,自然就是要在索引文件的结构上面做文章。
所以对于Android资源索引来说最主要的工作就是对重复的资源进行合并压缩, 对资源进行统一编址,以及设计紧凑高效的文件格式。
对于Android资源的统一编址方面,把Android将资源id分为三部分,package_id, type_id 和 index, 类型包括如下几种assets,res,animator,anim,color,drawable,layout,menu,raw,values,xml。不同的package使用不同的package_id,不同的类型使用不同的type_id, 对相同类型下的不同资源进行编码,生成不同的index。
对于资源的压缩其实主要就是对重复字符串的合并索引。字符串的合并包含三部分,第一部分是对文件路径的索引,如drawable-xxxhdpi/icon.png资源。 但二部分是对value类型的资源名称进行索引。第三部分则是对文件资源的编译索引。怎么理解value类型的资源和文件类型的资源呢。 上面说的11种资源中values和color类型的资源就是value资源,它不是文件类型的资源,而其他九种资源则都是文件类型的资源。 对于文件类型的资源,如果是xml资源,就需要对xml资源进行编译索引来减少大小。values类型的资源子需要把xml中的信息进行提取索引就可以了。
对于怎样设计一个紧凑的索引文件格式,我们来看下资源索引后生成的resources.arsc文件格式。 这里要说明 resources.arsc中包含的信息,对于value类型的资源,resources.arsc主要描述了它的名字,值以及不同配置信息。对于文件类型的资源,resources.arsc则描述了它的文件路径以及配置信息。
resources.arsc格式说明
首先我们要记住下面三点点:
对于一个资源id,对应多个同包同类型同名称的不同配置的资源。
对于文件类型的资源resources.arsc存储的信息包含它的名称、配置信息、类型信息和路径。
对于非文件类型的资源resources.arsc保存了它的名称、类型信息、配置信息和值。
下面来看下resources.arsc的格式:
我们从宏观到微观来看下文件的格式:
-
头部
size描述了整个文件的大小。
packageCount描述了该资源文件索引了几个包的信息。 -
valueStrings是一个字符串池类型的数据结构,为了尽可能的压缩字符串资源,一个resources.arsc会将所有包的值信息进行合并索引。 关于字符串池请参考aapt字符串池一文。 另外什么是values呢,对于一个资源,它有两部分,分别是名称和值, 这里valueStrings部分存储的就是所有包中的值信息。
-
package: 这部分包含了多个包信息。
下面我们展开来看包的信息,包含如下几部分:
- 头部:
id:package_id(资源id分为三部分,第一部分就是package_id)。
name:包名。
typeStrings指向后面的资源类型字符串池。
keyStrings指向后面的资源名称字符串池。
- typeStrings:资源类型字符串池,包含了该包下所有类型名称。
- keyString:资源名称字符串池,包含了该包下所有的资源名称。
- typeinfos: 多个typeinfo,每个typeinfo代表该包下的一个类型的资源信息。
下面展开来看类型信息:
- id:类型id。
- entryCount: 该类型下包含的不同名资源个数。
- typeSpecFlags: entryCount个typeSpecFlags, typeSpecFlags为32位整数,描述资源支持的配置类型,方便系统切换设置后重新加载资源。typeSpecFlags每一位表示是否支持一种配置信息。
- Config info: 包含多个Config info, 每个Config信息描述一种配置下包含的资源。
下面展开来说Config信息:
- id: 所属的typeid,这个值和前边typeifo的id是一样的。
- entryCount:包含的资源个数,这个值其实和前边typeinfo下的entryCount是一样的。
- entriesStart:指向资源项信息开始位置,如图箭头所示。
- config:该数据库的配置信息如drawable-en-xxhdpi。
- entry_offset: entryCount个entry项,每一项为一个32位的整数,指向后面对应entry的位置(如图箭头),entry信息就是该资源在该配置下的值,为什么是entryCount个呢,如果一个资源在该配置下没有值,该项对应的位置存储ResTable_type::NO_ENTRY,表示没有对应的资源,使用ResTable_type::NO_ENTRY占位是为了使用下标找到对应的entry_offset。
- entry: 资源的具体值信息。
下面展开来说entry信息:
entry分为两种类型。
第一种是正常的key-value类型的资源,使用Restable_entry来描述。
- size:头的大小。
- flags:
- key.index:该资源名称在package的keystrings中的索引。
- value.size:value的大小。
- data_type:数据的类型。
- res0: 该值为保留项用于扩展,目前该值为0。
- data:数据值(数据类型为string的时候指向resources.arsc中的valueStrings,其他整型值则直接存储在此)。
第二种类型是Restable_map_entry类型。 主要是针对attr属性。举几个例子来说明。
<attr name="attr_enum" >
<enum name="enum1" value="1"/>
<enum name="enum2" value="2"/>
</attr>
<attr name="attr_integer" format="integer" min="10" max="100"/>
对于attr_enum这个资源编译出来后是一个名称为attr_enum的资源项,类型为attr,它包含三个bag项,分别是:
- 名称为^type, 值为TYPE_ENUM的bag1。
- 名称为enum1,值为“1”的bag2
- 名称为emu2,值为“2”的bag2
对于attr_integer资源也是包含三个bag,分别是
- 名称为^type, 值为TYPE_INTEGER的bag1。
- 名称^min,值为10的bag2
- 名称为^max, 值为100的bag3
所以为了描述资源名称和bag信息,我们使用 Restable_map_entry来描述attr资源:
- size:头的大小。
- flags:
- key.index: bagkey_id。
- parent.index: 父资源的id。
- count:包含的bag项数目。
- item: 具体的bag信息。
下面展开bag信息(使用Restable_map来描述,map顾名思义键值对):
- name.ident :该名称对应的id,系统会给每个bag生成一个id。
- value.size : 值的大小。
- dataType:值的类型。
- value.res0: 恒为0。
- value.data: 值(数据类型为string的时候指向resources.arsc中的valueStrings,其他整型值则直接存储在此)。
另外补充一点,关于统一编码,每个包有一个packageid,每个包下的typeid是从1开始,依次递增,entryid和typeid一样,也是从1开始依次递增的。
资源查找过程
下面我们来总结一下资源查找过程,以查找一个drawable资源为例子。
public static final int drawable1=0x7f060054;
我们编程时使用R.drawable.drawable1引用该资源,整个查找过程如下:首先将id分为3部分。 packageid使用8位为0x7f,typeid使用8位为0x06, entryid使用16位为0x0054。查找过程首先根据packageid从resources.arsc文件中找到对应的package, 从package根据typeid 0x06,找到第六个typeinfo,再遍历该type下不同的配置项,找到与当前设备环境最适配的配置项。从这个配置项中找到第84个(0x0054)entry_offset, 根据这个entry_offset找到具体存放entry值的地方,也就是Restable_entry和Res_value数据结构,从这个数据结构可以知道资源的名称为drawable1,资源的类型为string,资源的值索引,根据值的索引就能在resources.arsc的valueStrings字符串索引池中找到相应的值,也就是drawable1的文件路径, 整个查找过程就完成了。 这里没有使用Restable_typeSpec信息(也就是一个资源支持什么样的配置), 主要是因为Restable_typeSpec信息主要用于切换系统配置的时候重新加载资源。
下面我们从代码角度看下resources.arsc的加载过程和资源查找过程:
status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData, size_t idmapDataSize,
const int32_t cookie, bool copyData)
{
if (!data) {
return NO_ERROR;
}
if (dataSize < sizeof(ResTable_header)) {
ALOGE("Invalid data. Size(%d) is smaller than a ResTable_header(%d).",
(int) dataSize, (int) sizeof(ResTable_header));
return UNKNOWN_ERROR;
}
Header* header = new Header(this);
header->index = mHeaders.size();
header->cookie = cookie; //cookie其实是对多个resources.arsc进行的编码,为一个整数值。
if (idmapData != NULL) {
header->resourceIDMap = (uint32_t*) malloc(idmapDataSize);
if (header->resourceIDMap == NULL) {
delete header;
return (mError = NO_MEMORY);
}
memcpy(header->resourceIDMap, idmapData, idmapDataSize);
header->resourceIDMapSize = idmapDataSize;
}
mHeaders.add(header);
const bool notDeviceEndian = htods(0xf0) != 0xf0;
if (kDebugLoadTableNoisy) {
ALOGV("Adding resources to ResTable: data=%p, size=%zu, cookie=%d, copy=%d "
"idmap=%p\n", data, dataSize, cookie, copyData, idmapData);
}
if (copyData || notDeviceEndian) {
header->ownedData = malloc(dataSize);
if (header->ownedData == NULL) {
return (mError=NO_MEMORY);
}
memcpy(header->ownedData, data, dataSize);
data = header->ownedData;
}
header->header = (const ResTable_header*)data;
header->size = dtohl(header->header->header.size);
if (kDebugLoadTableSuperNoisy) {
ALOGI("Got size %zu, again size 0x%x, raw size 0x%x\n", header->size,
dtohl(header->header->header.size), header->header->header.size);
}
if (kDebugLoadTableNoisy) {
ALOGV("Loading ResTable @%p:\n", header->header);
}
if (dtohs(header->header->header.headerSize) > header->size
|| header->size > dataSize) {
ALOGW("Bad resource table: header size 0x%x or total size 0x%x is larger than data size 0x%x\n",
(int)dtohs(header->header->header.headerSize),
(int)header->size, (int)dataSize);
return (mError=BAD_TYPE);
}
if (((dtohs(header->header->header.headerSize)|header->size)&0x3) != 0) {
ALOGW("Bad resource table: header size 0x%x or total size 0x%x is not on an integer boundary\n",
(int)dtohs(header->header->header.headerSize),
(int)header->size);
return (mError=BAD_TYPE);
}
header->dataEnd = ((const uint8_t*)header->header) + header->size;
// Iterate through all chunks.
size_t curPackage = 0;
const ResChunk_header* chunk =
(const ResChunk_header*)(((const uint8_t*)header->header)
+ dtohs(header->header->header.headerSize));
while (((const uint8_t*)chunk) <= (header->dataEnd-sizeof(ResChunk_header)) &&
((const uint8_t*)chunk) <= (header->dataEnd-dtohl(chunk->size))) {
status_t err = validate_chunk(chunk, sizeof(ResChunk_header), header->dataEnd, "ResTable");
if (err != NO_ERROR) {
return (mError=err);
}
if (kDebugTableNoisy) {
ALOGV("Chunk: type=0x%x, headerSize=0x%x, size=0x%x, pos=%p\n",
dtohs(chunk->type), dtohs(chunk->headerSize), dtohl(chunk->size),
(void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header)));
}
const size_t csize = dtohl(chunk->size);
const uint16_t ctype = dtohs(chunk->type);
if (ctype == RES_STRING_POOL_TYPE) { //加载valueStrings
if (header->values.getError() != NO_ERROR) {
// Only use the first string chunk; ignore any others that
// may appear.
status_t err = header->values.setTo(chunk, csize);
if (err != NO_ERROR) {
return (mError=err);
}
} else {
ALOGW("Multiple string chunks found in resource table.");
}
} else if (ctype == RES_TABLE_PACKAGE_TYPE) { //加载package
if (curPackage >= dtohl(header->header->packageCount)) {
ALOGW("More package chunks were found than the %d declared in the header.",
dtohl(header->header->packageCount));
return (mError=BAD_TYPE);
}
if (parsePackage((ResTable_package*)chunk, header) != NO_ERROR) {
return mError;
}
curPackage++;
} else {
ALOGW("Unknown chunk type 0x%x in table at %p.\n",
ctype,
(void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header)));
}
chunk = (const ResChunk_header*)
(((const uint8_t*)chunk) + csize);
}
if (curPackage < dtohl(header->header->packageCount)) {
ALOGW("Fewer package chunks (%d) were found than the %d declared in the header.",
(int)curPackage, dtohl(header->header->packageCount));
return (mError=BAD_TYPE);
}
mError = header->values.getError();
if (mError != NO_ERROR) {
ALOGW("No string values found in resource table!");
}
if (kDebugTableNoisy) {
ALOGV("Returning from add with mError=%d\n", mError);
}
return mError;
}
addInternal函数为添加一个新的resources.arsc的处理函数,函数首先对文件进行了一些检查,然后解析header,解析valueStrings,解析多个package。 解析package的函数为parsePackage((ResTable_package*)chunk, header)。
parsePackage代码如下:
status_t ResTable::parsePackage(const ResTable_package* const pkg,
const Header* const header)
{
const uint8_t* base = (const uint8_t*)pkg;
status_t err = validate_chunk(&pkg->header, sizeof(*pkg) - sizeof(pkg->typeIdOffset),
header->dataEnd, "ResTable_package");
if (err != NO_ERROR) {
return (mError=err);
}
const uint32_t pkgSize = dtohl(pkg->header.size);
if (dtohl(pkg->typeStrings) >= pkgSize) {
ALOGW("ResTable_package type strings at 0x%x are past chunk size 0x%x.",
dtohl(pkg->typeStrings), pkgSize);
return (mError=BAD_TYPE);
}
if ((dtohl(pkg->typeStrings)&0x3) != 0) {
ALOGW("ResTable_package type strings at 0x%x is not on an integer boundary.",
dtohl(pkg->typeStrings));
return (mError=BAD_TYPE);
}
if (dtohl(pkg->keyStrings) >= pkgSize) {
ALOGW("ResTable_package key strings at 0x%x are past chunk size 0x%x.",
dtohl(pkg->keyStrings), pkgSize);
return (mError=BAD_TYPE);
}
if ((dtohl(pkg->keyStrings)&0x3) != 0) {
ALOGW("ResTable_package key strings at 0x%x is not on an integer boundary.",
dtohl(pkg->keyStrings));
return (mError=BAD_TYPE);
}
uint32_t id = dtohl(pkg->id);
KeyedVector<uint8_t, IdmapEntries> idmapEntries;
if (header->resourceIDMap != NULL) {
uint8_t targetPackageId = 0;
status_t err = parseIdmap(header->resourceIDMap, header->resourceIDMapSize, &targetPackageId, &idmapEntries);
if (err != NO_ERROR) {
ALOGW("Overlay is broken");
return (mError=err);
}
id = targetPackageId;
}
if (id >= 256) {
LOG_ALWAYS_FATAL("Package id out of range");
return NO_ERROR;
} else if (id == 0) {
// This is a library so assign an ID
id = mNextPackageId++;
}
PackageGroup* group = NULL;
Package* package = new Package(this, header, pkg);
if (package == NULL) {
return (mError=NO_MEMORY);
}
//解析typeStrings
err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings),
header->dataEnd-(base+dtohl(pkg->typeStrings)));
if (err != NO_ERROR) {
delete group;
delete package;
return (mError=err);
}
//解析valueStrings
err = package->keyStrings.setTo(base+dtohl(pkg->keyStrings),
header->dataEnd-(base+dtohl(pkg->keyStrings)));
if (err != NO_ERROR) {
delete group;
delete package;
return (mError=err);
}
size_t idx = mPackageMap[id];
if (idx == 0) {
idx = mPackageGroups.size() + 1;
char16_t tmpName[sizeof(pkg->name)/sizeof(pkg->name[0])];
strcpy16_dtoh(tmpName, pkg->name, sizeof(pkg->name)/sizeof(pkg->name[0]));
group = new PackageGroup(this, String16(tmpName), id);
if (group == NULL) {
delete package;
return (mError=NO_MEMORY);
}
err = mPackageGroups.add(group);
if (err < NO_ERROR) {
return (mError=err);
}
mPackageMap[id] = static_cast<uint8_t>(idx);
// Find all packages that reference this package
size_t N = mPackageGroups.size();
for (size_t i = 0; i < N; i++) {
mPackageGroups[i]->dynamicRefTable.addMapping(
group->name, static_cast<uint8_t>(group->id));
}
} else {
group = mPackageGroups.itemAt(idx - 1);
if (group == NULL) {
return (mError=UNKNOWN_ERROR);
}
}
err = group->packages.add(package);
if (err < NO_ERROR) {
return (mError=err);
}
// Iterate through all chunks.
const ResChunk_header* chunk =
(const ResChunk_header*)(((const uint8_t*)pkg)
+ dtohs(pkg->header.headerSize));
const uint8_t* endPos = ((const uint8_t*)pkg) + dtohs(pkg->header.size);
while (((const uint8_t*)chunk) <= (endPos-sizeof(ResChunk_header)) &&
((const uint8_t*)chunk) <= (endPos-dtohl(chunk->size))) {
if (kDebugTableNoisy) {
ALOGV("PackageChunk: type=0x%x, headerSize=0x%x, size=0x%x, pos=%p\n",
dtohs(chunk->type), dtohs(chunk->headerSize), dtohl(chunk->size),
(void*)(((const uint8_t*)chunk) - ((const uint8_t*)header->header)));
}
const size_t csize = dtohl(chunk->size);
const uint16_t ctype = dtohs(chunk->type);
if (ctype == RES_TABLE_TYPE_SPEC_TYPE) { //解析type信息
const ResTable_typeSpec* typeSpec = (const ResTable_typeSpec*)(chunk);
err = validate_chunk(&typeSpec->header, sizeof(*typeSpec),
endPos, "ResTable_typeSpec");
if (err != NO_ERROR) {
return (mError=err);
}
const size_t typeSpecSize = dtohl(typeSpec->header.size);
const size_t newEntryCount = dtohl(typeSpec->entryCount);
if (kDebugLoadTableNoisy) {
ALOGI("TypeSpec off %p: type=0x%x, headerSize=0x%x, size=%p\n",
(void*)(base-(const uint8_t*)chunk),
dtohs(typeSpec->header.type),
dtohs(typeSpec->header.headerSize),
(void*)typeSpecSize);
}
// look for block overrun or int overflow when multiplying by 4
if ((dtohl(typeSpec->entryCount) > (INT32_MAX/sizeof(uint32_t))
|| dtohs(typeSpec->header.headerSize)+(sizeof(uint32_t)*newEntryCount)
> typeSpecSize)) {
ALOGW("ResTable_typeSpec entry index to %p extends beyond chunk end %p.",
(void*)(dtohs(typeSpec->header.headerSize) + (sizeof(uint32_t)*newEntryCount)),
(void*)typeSpecSize);
return (mError=BAD_TYPE);
}
if (typeSpec->id == 0) {
ALOGW("ResTable_type has an id of 0.");
return (mError=BAD_TYPE);
}
if (newEntryCount > 0) {
uint8_t typeIndex = typeSpec->id - 1;
ssize_t idmapIndex = idmapEntries.indexOfKey(typeSpec->id);
if (idmapIndex >= 0) {
typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
}
TypeList& typeList = group->types.editItemAt(typeIndex);
if (!typeList.isEmpty()) {
const Type* existingType = typeList[0];
if (existingType->entryCount != newEntryCount && idmapIndex < 0) {
ALOGW("ResTable_typeSpec entry count inconsistent: given %d, previously %d",
(int) newEntryCount, (int) existingType->entryCount);
// We should normally abort here, but some legacy apps declare
// resources in the 'android' package (old bug in AAPT).
}
}
Type* t = new Type(header, package, newEntryCount);
t->typeSpec = typeSpec;
t->typeSpecFlags = (const uint32_t*)(
((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
if (idmapIndex >= 0) {
t->idmapEntries = idmapEntries[idmapIndex];
}
typeList.add(t);
group->largestTypeId = max(group->largestTypeId, typeSpec->id);
} else {
ALOGV("Skipping empty ResTable_typeSpec for type %d", typeSpec->id);
}
} else if (ctype == RES_TABLE_TYPE_TYPE) {//解析ResTable_type
const ResTable_type* type = (const ResTable_type*)(chunk);
err = validate_chunk(&type->header, sizeof(*type)-sizeof(ResTable_config)+4,
endPos, "ResTable_type");
if (err != NO_ERROR) {
return (mError=err);
}
const uint32_t typeSize = dtohl(type->header.size);
const size_t newEntryCount = dtohl(type->entryCount);
if (kDebugLoadTableNoisy) {
printf("Type off %p: type=0x%x, headerSize=0x%x, size=%u\n",
(void*)(base-(const uint8_t*)chunk),
dtohs(type->header.type),
dtohs(type->header.headerSize),
typeSize);
}
if (dtohs(type->header.headerSize)+(sizeof(uint32_t)*newEntryCount) > typeSize) {
ALOGW("ResTable_type entry index to %p extends beyond chunk end 0x%x.",
(void*)(dtohs(type->header.headerSize) + (sizeof(uint32_t)*newEntryCount)),
typeSize);
return (mError=BAD_TYPE);
}
if (newEntryCount != 0
&& dtohl(type->entriesStart) > (typeSize-sizeof(ResTable_entry))) {
ALOGW("ResTable_type entriesStart at 0x%x extends beyond chunk end 0x%x.",
dtohl(type->entriesStart), typeSize);
return (mError=BAD_TYPE);
}
if (type->id == 0) {
ALOGW("ResTable_type has an id of 0.");
return (mError=BAD_TYPE);
}
if (newEntryCount > 0) {
uint8_t typeIndex = type->id - 1;
ssize_t idmapIndex = idmapEntries.indexOfKey(type->id);
if (idmapIndex >= 0) {
typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
}
TypeList& typeList = group->types.editItemAt(typeIndex);
if (typeList.isEmpty()) {
ALOGE("No TypeSpec for type %d", type->id);
return (mError=BAD_TYPE);
}
Type* t = typeList.editItemAt(typeList.size() - 1);
if (newEntryCount != t->entryCount) {
ALOGE("ResTable_type entry count inconsistent: given %d, previously %d",
(int)newEntryCount, (int)t->entryCount);
return (mError=BAD_TYPE);
}
if (t->package != package) {
ALOGE("No TypeSpec for type %d", type->id);
return (mError=BAD_TYPE);
}
t->configs.add(type);
if (kDebugTableGetEntry) {
ResTable_config thisConfig;
thisConfig.copyFromDtoH(type->config);
ALOGI("Adding config to type %d: %s\n", type->id,
thisConfig.toString().string());
}
} else {
ALOGV("Skipping empty ResTable_type for type %d", type->id);
}
} else if (ctype == RES_TABLE_LIBRARY_TYPE) {
if (group->dynamicRefTable.entries().size() == 0) {
status_t err = group->dynamicRefTable.load((const ResTable_lib_header*) chunk);
if (err != NO_ERROR) {
return (mError=err);
}
// Fill in the reference table with the entries we already know about.
size_t N = mPackageGroups.size();
for (size_t i = 0; i < N; i++) {
group->dynamicRefTable.addMapping(mPackageGroups[i]->name, mPackageGroups[i]->id);
}
} else {
ALOGW("Found multiple library tables, ignoring...");
}
} else {
status_t err = validate_chunk(chunk, sizeof(ResChunk_header),
endPos, "ResTable_package:unknown");
if (err != NO_ERROR) {
return (mError=err);
}
}
chunk = (const ResChunk_header*)
(((const uint8_t*)chunk) + csize);
}
return NO_ERROR;
}
parsePackage只解析了ResTable_typeSpec信息和ResTable_type信息。并没有解析具体的entry信息。这样做的原因是为了加快加载速度,因为很多资源我们不一定会用到,对于资源的具体entry信息采用懒加载的模式,用的时候再去加载。
最后看一下资源查找的代码:
ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag, uint16_t density,
uint32_t* outSpecFlags, ResTable_config* outConfig) const
{
if (mError != NO_ERROR) {
return mError;
}
//从原始id拆分出packageid, typeid,和entryid
const ssize_t p = getResourcePackageIndex(resID);
const int t = Res_GETTYPE(resID);
const int e = Res_GETENTRY(resID);
if (p < 0) {
if (Res_GETPACKAGE(resID)+1 == 0) {
ALOGW("No package identifier when getting value for resource number 0x%08x", resID);
} else {
ALOGW("No known package when getting value for resource number 0x%08x", resID);
}
return BAD_INDEX;
}
if (t < 0) {
ALOGW("No type identifier when getting value for resource number 0x%08x", resID);
return BAD_INDEX;
}
const PackageGroup* const grp = mPackageGroups[p];
if (grp == NULL) {
ALOGW("Bad identifier when getting value for resource number 0x%08x", resID);
return BAD_INDEX;
}
// Allow overriding density
ResTable_config desiredConfig = mParams;
if (density > 0) {
desiredConfig.density = density;
}
Entry entry;
status_t err = getEntry(grp, t, e, &desiredConfig, &entry); //查找entry信息
if (err != NO_ERROR) {
// Only log the failure when we're not running on the host as
// part of a tool. The caller will do its own logging.
#ifndef STATIC_ANDROIDFW_FOR_TOOLS
ALOGW("Failure getting entry for 0x%08x (t=%d e=%d) (error %d)\n",
resID, t, e, err);
#endif
return err;
}
if ((dtohs(entry.entry->flags) & ResTable_entry::FLAG_COMPLEX) != 0) {
if (!mayBeBag) {
ALOGW("Requesting resource 0x%08x failed because it is complex\n", resID);
}
return BAD_VALUE;
}
//解析entry的值
const Res_value* value = reinterpret_cast<const Res_value*>(
reinterpret_cast<const uint8_t*>(entry.entry) + entry.entry->size);
outValue->size = dtohs(value->size);
outValue->res0 = value->res0;
outValue->dataType = value->dataType;
outValue->data = dtohl(value->data);
// The reference may be pointing to a resource in a shared library. These
// references have build-time generated package IDs. These ids may not match
// the actual package IDs of the corresponding packages in this ResTable.
// We need to fix the package ID based on a mapping.
if (grp->dynamicRefTable.lookupResourceValue(outValue) != NO_ERROR) {
ALOGW("Failed to resolve referenced package: 0x%08x", outValue->data);
return BAD_VALUE;
}
if (kDebugTableNoisy) {
size_t len;
printf("Found value: pkg=%zu, type=%d, str=%s, int=%d\n",
entry.package->header->index,
outValue->dataType,
outValue->dataType == Res_value::TYPE_STRING ?
String8(entry.package->header->values.stringAt(outValue->data, &len)).string() :
"",
outValue->data);
}
if (outSpecFlags != NULL) {
*outSpecFlags = entry.specFlags;
}
if (outConfig != NULL) {
*outConfig = entry.config;
}
//结果由outValue传出,这里返回值所在的字符串池信息。
return entry.package->header->index;
}
这个函数主要调用getEntry获取资源对应的值, 最终通过传出参数outValue返回值信息,并通过返回值返回该包对应的cookie信息(通过这个cookie能拿到resources.arsc的valueStrings字符串池)。所以最重点函数还是getEntry(grp, t, e, &desiredConfig, &entry)
status_t ResTable::getEntry(
const PackageGroup* packageGroup, int typeIndex, int entryIndex,
const ResTable_config* config,
Entry* outEntry) const
{
const TypeList& typeList = packageGroup->types[typeIndex]; //根据typeIndex找到对应的type,这里使用packageGroup是在一些分包的情况下出现多个package在同一个packageGroup中,所以对于相同typeid可能对应多个Type信息。
if (typeList.isEmpty()) {
ALOGV("Skipping entry type index 0x%02x because type is NULL!\n", typeIndex);
return BAD_TYPE;
}
const ResTable_type* bestType = NULL;
uint32_t bestOffset = ResTable_type::NO_ENTRY;
const Package* bestPackage = NULL;
uint32_t specFlags = 0;
uint8_t actualTypeIndex = typeIndex;
ResTable_config bestConfig;
memset(&bestConfig, 0, sizeof(bestConfig));
// Iterate over the Types of each package.
const size_t typeCount = typeList.size();
for (size_t i = 0; i < typeCount; i++) { //编译该组下对应该typeid的所有type信息
const Type* const typeSpec = typeList[i];
int realEntryIndex = entryIndex;
int realTypeIndex = typeIndex;
bool currentTypeIsOverlay = false;
// Runtime overlay packages provide a mapping of app resource
// ID to package resource ID.
if (typeSpec->idmapEntries.hasEntries()) {
uint16_t overlayEntryIndex;
if (typeSpec->idmapEntries.lookup(entryIndex, &overlayEntryIndex) != NO_ERROR) {
// No such mapping exists
continue;
}
realEntryIndex = overlayEntryIndex;
realTypeIndex = typeSpec->idmapEntries.overlayTypeId() - 1;
currentTypeIsOverlay = true;
}
if (static_cast<size_t>(realEntryIndex) >= typeSpec->entryCount) {
ALOGW("For resource 0x%08x, entry index(%d) is beyond type entryCount(%d)",
Res_MAKEID(packageGroup->id - 1, typeIndex, entryIndex),
entryIndex, static_cast<int>(typeSpec->entryCount));
// We should normally abort here, but some legacy apps declare
// resources in the 'android' package (old bug in AAPT).
continue;
}
// Aggregate all the flags for each package that defines this entry.
if (typeSpec->typeSpecFlags != NULL) { //找到对应的typeSpecFlags
specFlags |= dtohl(typeSpec->typeSpecFlags[realEntryIndex]);
} else {
specFlags = -1;
}
const size_t numConfigs = typeSpec->configs.size();
for (size_t c = 0; c < numConfigs; c++) { //遍历该type下的所有配置信息ResTable_type,找到与当前环境最适配的资源
const ResTable_type* const thisType = typeSpec->configs[c];
if (thisType == NULL) {
continue;
}
ResTable_config thisConfig;
thisConfig.copyFromDtoH(thisType->config);
// Check to make sure this one is valid for the current parameters.
if (config != NULL && !thisConfig.match(*config)) { //配置不匹配则直接从下一个ResTable_type中匹配
continue;
}
// Check if there is the desired entry in this type.
const uint32_t* const eindex = reinterpret_cast<const uint32_t*>(
reinterpret_cast<const uint8_t*>(thisType) + dtohs(thisType->header.headerSize));
uint32_t thisOffset = dtohl(eindex[realEntryIndex]);
if (thisOffset == ResTable_type::NO_ENTRY) { //获取entry offset,如果为ResTable_type::NO_ENTRY说明在该配置下不存在对应资源,直接匹配下一项
// There is no entry for this index and configuration.
continue;
}
if (bestType != NULL) {
// Check if this one is less specific than the last found. If so,
// we will skip it. We check starting with things we most care
// about to those we least care about.
if (!thisConfig.isBetterThan(bestConfig, config)) { //找到最匹配的配置资源
if (!currentTypeIsOverlay || thisConfig.compare(bestConfig) != 0) {
continue;
}
}
}
bestType = thisType;
bestOffset = thisOffset;
bestConfig = thisConfig;
bestPackage = typeSpec->package;
actualTypeIndex = realTypeIndex;
// If no config was specified, any type will do, so skip
if (config == NULL) {
break;
}
}
}
if (bestType == NULL) {
return BAD_INDEX;
}
bestOffset += dtohl(bestType->entriesStart);
if (bestOffset > (dtohl(bestType->header.size)-sizeof(ResTable_entry))) {
ALOGW("ResTable_entry at 0x%x is beyond type chunk data 0x%x",
bestOffset, dtohl(bestType->header.size));
return BAD_TYPE;
}
if ((bestOffset & 0x3) != 0) {
ALOGW("ResTable_entry at 0x%x is not on an integer boundary", bestOffset);
return BAD_TYPE;
}
const ResTable_entry* const entry = reinterpret_cast<const ResTable_entry*>(
reinterpret_cast<const uint8_t*>(bestType) + bestOffset);
if (dtohs(entry->size) < sizeof(*entry)) {
ALOGW("ResTable_entry size 0x%x is too small", dtohs(entry->size));
return BAD_TYPE;
}
if (outEntry != NULL) { //返回最终找到的entry
outEntry->entry = entry;
outEntry->config = bestConfig;
outEntry->type = bestType;
outEntry->specFlags = specFlags;
outEntry->package = bestPackage;
outEntry->typeStr = StringPoolRef(&bestPackage->typeStrings, actualTypeIndex - bestPackage->typeIdOffset);
outEntry->keyStr = StringPoolRef(&bestPackage->keyStrings, dtohl(entry->key.index));
}
return NO_ERROR;
}
代码很简单,解释通过注释写在了上边的代码中。