背景
對於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;
}
代碼很簡單,解釋通過註釋寫在了上邊的代碼中。