前言
去年整理过android系统资源加载流程,时间久了发现有些地方仍旧不清楚,这里重新整理了一次。这次整理的流程主要分析了资源查找流程、AssetManager添加apk时都做了什么等。
备注:参考源码为android6.0,之前是参考android4.4源码学习;对比发现在资源处理这块,6.0版本的源码跟4.4已经很大不同。
基础知识
资源ID
Package ID + Type ID + Entry ID
关键词
AssetManager
AssetManager一般用来加载资源文件,AssetManager有个ResTable类型的成员变量,该ResTable变量保存了AssetManager加载的所有资源文件中的资源。
ResTable
ResTable即资源表,与某个AssetManager关联。内部存在一个PackageGroup列表,该列表用来保存所有加载的资源文件资源。
PackageGroup
- 一个PackageGroup存放了Package ID相同的Package信息
- 一个PackageGroup可能对应了多个Package ID相同的Package
Package
- 一个Package对应一个APK包
- 一个Package包含多种Type类型的资源
Type
- 资源类型,如:layout、drawable、string等各对应一个Type。
- 一个Type可能对应多个Config
Config
- Config表示同名资源
- 一个Config可能包含多个Entry,即一个资源的不同配置。
Entry
具体配置下的资源
重叠包(overlay)
假设我们正在编译的是Package-1,这时候我们可以设置另外一个Package-2,用来告诉aapt,如果Package-2定义有和Package-1一样的资源,那么就用定义在Package-2的资源来替换掉定义在Package-1的资源。
重叠包设置可以参考:编译时替换资源 - Android重叠包与资源合并一见
举个栗子
1.png上图中存在三种类型资源:layout、drawable和string,即对应存在3中Type;有:
- Type为drawable的资源由1个Config,即icon.png。该Config有3个Entry:res/drawable-ldip/icon.png、res/drawable-mdip/icon.png、res/drawable-hdip/icon.png。
- Type为layout的资源由2个Config,即main.xml和sub.xml。
- main.xml的Config对应的Entry为:res/layout/main.xml。
- sub.xml的Config对应的Entry为:res/layout/subxml。
ResTable分析
从代码中看下资源存储的具体结构,参考源码为Android6.0,4.4源码与现有代码已很大不同。
PackageGroup
该结构定义在ResourceTYpes.cpp中。
/**
* 一个PackageGroup保存了所有资源ID相同的Package信息
* PackageGroup中第一个Package是root package
*/
struct ResTable::PackageGroup
{
PackageGroup(ResTable* _owner, const String16& _name, uint32_t _id)
: owner(_owner)
, name(_name)
, id(_id)
, largestTypeId(0)
, bags(NULL)
, dynamicRefTable(static_cast<uint8_t>(_id)){ }
//析构函数,清理,代码忽略
~PackageGroup() {
clearBagCache();
......
}
//清理内存
void clearBagCache() {
......
}
......
const ResTable* const owner; //指向其所在的ResTable
String16 const name; //root package的包名
uint32_t const id; //
//当前PackageGroup加载的Packages,这些Packages的Package ID相同
Vector<Package*> packages;
//该PackageGroup下所有Package的类型,即一个TypeList对应了所有Package的相同类型资源?
//如,所有Package的drawable类型资源,保存在一个TypeList中
ByteBucketArray<TypeList> types;
uint8_t largestTypeId;
// Computed attribute bags, first indexed by the type and second
// by the entry in that type.
ByteBucketArray<bag_set**>* bags;
// The table mapping dynamic references to resolved references for
// this package group.
// TODO: We may be able to support dynamic references in overlays
// by having these tables in a per-package scope rather than
// per-package-group.
DynamicRefTable dynamicRefTable;
}
Package
该结构定义在ResourceTYpes.cpp中。
//一个apk对应一个Package?
struct ResTable::Package
{
Package(ResTable* _owner, const Header* _header, const ResTable_package* _package)
: owner(_owner), header(_header), package(_package), typeIdOffset(0) {
if (dtohs(package->header.headerSize) == sizeof(package)) {
// The package structure is the same size as the definition.
// This means it contains the typeIdOffset field.
typeIdOffset = package->typeIdOffset;
}
}
const ResTable* const owner;
const Header* const header;
const ResTable_package* const package;
//Type池,即保存了该Package下所有的资源类型
ResStringPool typeStrings;
//key池,即保存了该Package下所有的资源名称
ResStringPool keyStrings;
//指示当前Package在其PackageGroup中的偏移量?
size_t typeIdOffset;
};
Type
该结构定义在ResourceTYpes.cpp中。
//资源类型
struct ResTable::Type
{
Type(const Header* _header, const Package* _package, size_t count)
: header(_header), package(_package), entryCount(count),
typeSpec(NULL), typeSpecFlags(NULL) { }
const Header* const header;
const Package* const package;
const size_t entryCount;
const ResTable_typeSpec* typeSpec;
const uint32_t* typeSpecFlags;
IdmapEntries idmapEntries;
//一个Type由多个config组成,config用ResTable_type表示
Vector<const ResTable_type*> configs;
};
Entry
该结构定义在ResourceTYpes.cpp中。
struct ResTable::Entry {
ResTable_config config;
const ResTable_entry* entry;
const ResTable_type* type; //所在config
uint32_t specFlags;
const Package* package; //指向所在Pakcage
StringPoolRef typeStr; //指向Package的Type池
StringPoolRef keyStr; //指向Package的key池
};
Header
Header保存了ResTable的部分信息,该结构定义在ResourceTYpes.cpp中。
struct ResTable::Header
{
Header(ResTable* _owner) : owner(_owner), ownedData(NULL), header(NULL),
resourceIDMap(NULL), resourceIDMapSize(0) { }
~Header()
{
free(resourceIDMap);
}
const ResTable* const owner;
void* ownedData;
const ResTable_header* header;
size_t size;
const uint8_t* dataEnd;
size_t index;
int32_t cookie; //该值为索引值
ResStringPool values;
uint32_t* resourceIDMap;
size_t resourceIDMapSize;
};
ResTable
看完了上面一些结构的定义,现在分析下ResTable。ResTable是个用来进行资源操作的类,该类定义在ResourceTyps.h,实现在ResourceTYpes.cpp中。
class ResTable
{
public:
ResTable();
ResTable(const void* data, size_t size, const int32_t cookie,
bool copyData=false);
~ResTable();
//add方法,用来添加资源,内部调用addInternal方法
status_t add(const void* data, size_t size, const int32_t cookie=-1, bool copyData=false);
status_t add(const void* data, size_t size, const void* idmapData, size_t idmapDataSize,const
int32_t cookie=-1, bool copyData=false);
status_t add(Asset* asset, const int32_t cookie=-1, bool copyData=false);
status_t add(Asset* asset, Asset* idmapAsset, const int32_t cookie=-1, bool copyData=false);
//添加其它ResTable
status_t add(ResTable* src);
//添加空的ResTable,此时只添加Header
status_t addEmpty(const int32_t cookie);
status_t getError() const;
void uninit();
//资源名称结构
struct resource_name
{
const char16_t* package;
size_t packageLen;
const char16_t* type;
const char* type8;
size_t typeLen;
const char16_t* name;
const char* name8;
size_t nameLen;
};
//根据资源ID获取资源名称
bool getResourceName(uint32_t resID, bool allowUtf8, resource_name* outName) const;
bool getResourceFlags(uint32_t resID, uint32_t* outFlags) const;
//根据资源ID获取资源的值,返回值为资源在 索引值
ssize_t getResource(uint32_t resID, Res_value* outValue, bool mayBeBag = false,
uint16_t density = 0,
uint32_t* outSpecFlags = NULL,
ResTable_config* outConfig = NULL) const;
......
//根据资源的全限定名,获取其资源ID
uint32_t identifierForName(const char16_t* name, size_t nameLen,
const char16_t* type = 0, size_t typeLen = 0,
const char16_t* defPackage = 0,
size_t defPackageLen = 0,
uint32_t* outTypeSpecFlags = NULL) const;
......
private:
struct Header;
struct Type;
struct Entry;
struct Package;
struct PackageGroup;
struct bag_set;
typedef Vector<Type*> TypeList;
//添加资源
status_t addInternal(const void* data, size_t size, const void* idmapData, size_t idmapDataSize,const int32_t cookie, bool copyData);
//根据资源Id获取资源在mPackageGroups的索引
ssize_t getResourcePackageIndex(uint32_t resID) const;
//获取具体资源信息
status_t getEntry(
const PackageGroup* packageGroup, int typeIndex, int entryIndex,
const ResTable_config* config,
Entry* outEntry) const;
//获取具体资源的资源ID
uint32_t findEntry(const PackageGroup* group, ssize_t typeIndex, const char16_t* name,size_t nameLen, uint32_t* outTypeSpecFlags) const;
......
ResTable_config mParams;
//当前ResTable添加的其它ResTable的Header信息
Vector<Header*> mHeaders;
//一个PackageID对应一个PackageGroup
Vector<PackageGroup*> mPackageGroups;
//保存了PackageID到mPckageGroups索引的映射关系
uint8_t mPackageMap[256];
uint8_t mNextPackageId;
};
ReTable方法还是比较多的,这里分析其几个比较重要的方法。
getResourceName方法
//根据资源ID查询资源名称
bool ResTable::getResourceName(uint32_t resID, bool allowUtf8, resource_name* outName) const
{
if (mError != NO_ERROR) {
return false;
}
//获取资源在mPackageGroups的索引
const ssize_t p = getResourcePackageIndex(resID);
//Type ID
const int t = Res_GETTYPE(resID);
//Entry ID
const int e = Res_GETENTRY(resID);
if (p < 0) {
if (Res_GETPACKAGE(resID)+1 == 0) { //Pckage ID为-1,不存在
ALOGW("No package identifier when getting name for resource number 0x%08x", resID);
} else {
ALOGW("No known package when getting name for resource number 0x%08x", resID);
}
return false;
}
if (t < 0) {
ALOGW("No type identifier when getting name for resource number 0x%08x", resID);
return false;
}
//资源所在PackageGroup
const PackageGroup* const grp = mPackageGroups[p];
if (grp == NULL) {
ALOGW("Bad identifier when getting name for resource number 0x%08x", resID);
return false;
}
Entry entry;
//在目的PackageGroup中查找对应资源
status_t err = getEntry(grp, t, e, NULL, &entry);
//未找到资源,返回false
if (err != NO_ERROR) {
return false;
}
/**
* 将查找到的资源名称保存到outName返回
**/
//资源Package名称,即其所在PackageGroup的名称
outName->package = grp->name.string();
outName->packageLen = grp->name.size();
//资源Type、Entry名称
if (allowUtf8) {
//Type名称在其Package的Type池中查找
outName->type8 = entry.typeStr.string8(&outName->typeLen);
//Entry名称在其Package的Key池中查找
outName->name8 = entry.keyStr.string8(&outName->nameLen);
} else {
outName->type8 = NULL;
outName->name8 = NULL;
}
if (outName->type8 == NULL) {
outName->type = entry.typeStr.string16(&outName->typeLen);
if (outName->type == NULL) {
return false;
}
}
if (outName->name8 == NULL) {
outName->name = entry.keyStr.string16(&outName->nameLen);
if (outName->name == NULL) {
return false;
}
}
return true;
}
分析:
该方法先根据资源的PackageID找到资源所在的PackageGroup,再在该PackageGroup中查找。在PackageGroup的查找调用getEntry方法实现。
getEntry方法
/**
* 在PackageGroup查找具体的资源
* pakcageGroup : 目的PackageGroup
* typeIndex:资源Type ID
* entryIndex:资源Entry ID
* config: 资源配置信息
* outEntry:返回值
*/
status_t ResTable::getEntry(
const PackageGroup* packageGroup, int typeIndex, int entryIndex,
const ResTable_config* config,
Entry* outEntry) const
{
//根据Type ID查找到目的PacakgeGroup下的目标类型
const TypeList& typeList = packageGroup->types[typeIndex];
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));
//注:一个TypeList存储了所有Package下相同类型的资源
const size_t typeCount = typeList.size();
//遍历每个Package的目标Type
for (size_t i = 0; i < typeCount; i++) {
const Type* const typeSpec = typeList[i];
int realEntryIndex = entryIndex;
int realTypeIndex = typeIndex;
bool currentTypeIsOverlay = false;
//是否有idmap资源
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) {
specFlags |= dtohl(typeSpec->typeSpecFlags[realEntryIndex]);
} else {
specFlags = -1;
}
const size_t numConfigs = typeSpec->configs.size();
//遍历每个Type下的configs
for (size_t c = 0; c < numConfigs; c++) {
const ResTable_type* const thisType = typeSpec->configs[c];
if (thisType == NULL) {
continue;
}
ResTable_config thisConfig;
thisConfig.copyFromDtoH(thisType->config);
//与所查找资源配置不相同
if (config != NULL && !thisConfig.match(*config)) {
continue;
}
//获取当前config下所有entry
const uint32_t* const eindex = reinterpret_cast<const uint32_t*>(
reinterpret_cast<const uint8_t*>(thisType) + dtohs(thisType->header.headerSize));
//在entry列表中查找是否存在匹配的Entry
uint32_t thisOffset = dtohl(eindex[realEntryIndex]);
if (thisOffset == 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;
}
//读取最佳配置对应的Entry
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;
}
//将最佳配置的Entry保存到outEntry,并返回
if (outEntry != NULL) {
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;
}
分析:
该方法的整理流程还是挺清楚的,主要有:
- 在目标PackageGroup中,根据TypeID找到对应的资源类型集合TypeList。(注:一个TypeList保存了当前PakcageGroup某种类型下所有Package的资源)
- 遍历TypeList,在每个Type中查找。
- 遍历每个Type中的configs列表,即查找不同的名称的资源。
- 在每个configs对应的entry中查找是否有有匹配的资源,有则保存返回。查找过程中,如果查到多个匹配的资源,则选择配置最佳的资源返回。
getResource方法
//根据资源ID查询资源值
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;
}
const ssize_t p = getResourcePackageIndex(resID); //mPacageksGroups下的索引
const int t = Res_GETTYPE(resID); //Type ID
const int e = Res_GETENTRY(resID); //Entry ID
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;
}
//资源所在PackageGroup
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;
//查找目标PakcageGroup下的资源Entry
status_t err = getEntry(grp, t, e, &desiredConfig, &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;
}
return entry.package->header->index;
}
分析
该方法与getResourceName方法类似,即先找到目标PackageGroup,再在目标PackageGroup中调用getEntry方法找到对应的Entry。然后再读取Entry中的值,保存并返回。
identifierForName方法
//由资源名称获取资源ID
uint32_t ResTable::identifierForName(const char16_t* name, size_t nameLen,
const char16_t* type, size_t typeLen,
const char16_t* package,
size_t packageLen,
uint32_t* outTypeSpecFlags) const
{
......
if (mError != NO_ERROR) {
return 0;
}
bool fakePublic = false;
/**
* 根据全限定名读取具体的Package、Type、Entry的名称和长度
*/
const char16_t* packageEnd = NULL;
const char16_t* typeEnd = NULL;
const char16_t* const nameEnd = name+nameLen;
const char16_t* p = name;
while (p < nameEnd) {
if (*p == ':') packageEnd = p;
else if (*p == '/') typeEnd = p;
p++;
}
if (*name == '@') {
name++;
if (*name == '*') {
fakePublic = true;
name++;
}
}
if (name >= nameEnd) {
return 0;
}
if (packageEnd) {
package = name;
packageLen = packageEnd-name;
name = packageEnd+1;
} else if (!package) {
return 0;
}
if (typeEnd) {
type = name;
typeLen = typeEnd-name;
name = typeEnd+1;
} else if (!type) {
return 0;
}
if (name >= nameEnd) {
return 0;
}
nameLen = nameEnd-name;
if (kDebugTableNoisy) {
printf("Looking for identifier: type=%s, name=%s, package=%s\n",
String8(type, typeLen).string(),
String8(name, nameLen).string(),
String8(package, packageLen).string());
}
const String16 attr("attr");
const String16 attrPrivate("^attr-private");
//所有的PackageGroup,一个Package ID对应一个PackageGroup
const size_t NG = mPackageGroups.size();
//遍历每个PackageGroup
for (size_t ig=0; ig<NG; ig++) {
const PackageGroup* group = mPackageGroups[ig];
//找到目标PackageGroup
//packageGroup的名称是否与资源包名相同,packagegroup的名称是root package的名称
if (strzcmp16(package, packageLen,
group->name.string(), group->name.size())) {
if (kDebugTableNoisy) {
printf("Skipping package group: %s\n", String8(group->name).string());
}
continue;
}
//packagegroup下的所有package
const size_t packageCount = group->packages.size();
//遍历每个package
for (size_t pi = 0; pi < packageCount; pi++) {
const char16_t* targetType = type;
size_t targetTypeLen = typeLen;
do {
//在当前package中查找是否存在目标Type,存在则返回该package的Type池索引
ssize_t ti = group->packages[pi]->typeStrings.indexOfString(
targetType, targetTypeLen);
if (ti < 0) {
continue;
}
//索引加上偏移值,该偏移值为当前Package在PackageGroup的偏移?
ti += group->packages[pi]->typeIdOffset;
//根据Type索引找到目标资源Entry,并返回资源ID
const uint32_t identifier = findEntry(group, ti, name, nameLen,
outTypeSpecFlags);
if (identifier != 0) {
if (fakePublic && outTypeSpecFlags) {
*outTypeSpecFlags |= ResTable_typeSpec::SPEC_PUBLIC;
}
//资源ID返回
return identifier;
}
} while (strzcmp16(attr.string(), attr.size(), targetType, targetTypeLen) == 0
&& (targetType = attrPrivate.string())
&& (targetTypeLen = attrPrivate.size())
);
}
break;
}
return 0;
}
分析
该方法根据资源全限定名获取资源ID:
- 遍历ResTable中的所有PackageGroup,即mPackageGroups
- 根据查找资源的包名,找到目标PackageGroup。
- 遍历目标PackageGroup的每个Package
- 在每个Package中查找资源Type,存在,则返回该Type在当前Package的偏移。
- 调用findEntry方法在当前PackageGroup中查找资源,如果查找到则返回资源ID。
findEntry方法
/**
* 在目标PacakgeGroup中查找资源,并返回对应的资源ID
*/
uint32_t ResTable::findEntry(const PackageGroup* group, ssize_t typeIndex, const char16_t* name,
size_t nameLen, uint32_t* outTypeSpecFlags) const {
//目标PakcageGroup下所有Package的目标Type资源
const TypeList& typeList = group->types[typeIndex];
const size_t typeCount = typeList.size();
//遍历每个Package的目标Type
for (size_t i = 0; i < typeCount; i++) {
const Type* t = typeList[i];
//当前Package的key池中查找资源,返回资源在key池中的索引。
const ssize_t ei = t->package->keyStrings.indexOfString(name, nameLen);
if (ei < 0) {
continue;
}
const size_t configCount = t->configs.size();
//遍历当前package下目标Tyle的configs
for (size_t j = 0; j < configCount; j++) {
const TypeVariant tv(t->configs[j]);
//遍历当前configs下的每个Entry
for (TypeVariant::iterator iter = tv.beginEntries();
iter != tv.endEntries();
iter++) {
const ResTable_entry* entry = *iter;
if (entry == NULL) {
continue;
}
//entry的资源索引是否与key池中索引相同,找到
if (dtohl(entry->key.index) == (size_t) ei) {
//根据PackageID、TypeID、entry出现的顺序生成 资源ID
uint32_t resId = Res_MAKEID(group->id - 1, typeIndex, iter.index());
if (outTypeSpecFlags) {
Entry result;
if (getEntry(group, typeIndex, iter.index(), NULL, &result) != NO_ERROR) {
ALOGW("Failed to find spec flags for 0x%08x", resId);
return 0;
}
*outTypeSpecFlags = result.specFlags;
}
return resId;
}
}
}
}
return 0;
}
分析
这个方法流程跟getEntry还是很相似的:
- 在目标PackageGroup中根据Type ID找到对应的TypeList
- 遍历找到的TypeList,即遍历每个Package该类型下的Type;
- 通过Type持有其所在Package的引用,找到Package,再在该Package中的key池中根据资源名称查找。返回结果为资源在Pakcage的key池中的偏移值,偏移值大于0则表示找到该资源。
- 找到目标Type后,再遍历该Type下的configs,并遍历每个configs下的Entry。如果Entry的资源索引与第3步返回的偏移值相同,则该Entry就是目标资源条目。
- 找到Entry,根据Pcakge ID、Type ID、Entry ID生成并返回资源ID。注意:其中Entry ID为资源Entry对应的config在Type中的顺序。
AssetManager与ResTable
AssetManager类的定义还是挺庞大的,这里就不贴出来了,从AssetManager的几个方法入手,了解其工作原理。在了解AssetManager方法前,先介绍几个AssetManager重要的成员变量。
几个成员变量
Vector<asset_path> mAssetPaths
- 该变量保存了AssetManager已加载的资源文件(apk)的路径
ResTable* mResources
- 资源表,所有已加载的apk资源都会添加到该资源表中。
- 即一个AssetManager与一个ResTable相关联。
几个方法
addAssetPath方法
/**
* 添加资源文件(apk)
* path:资源文件路径
*cookie:mAssetPaths列表中该apk索引
*/
bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
AutoMutex _l(mLock);
asset_path ap;
//资源路径校验
String8 realPath(path);
if (kAppZipName) {
realPath.appendPath(kAppZipName);
}
ap.type = ::getFileType(realPath.string());
if (ap.type == kFileTypeRegular) {
ap.path = realPath;
} else {
ap.path = path;
ap.type = ::getFileType(path.string());
if (ap.type != kFileTypeDirectory && ap.type != kFileTypeRegular) {
ALOGW("Asset path %s is neither a directory nor file (type=%d).",
path.string(), (int)ap.type);
return false;
}
}
//该资源文件已加载,返回
for (size_t i=0; i<mAssetPaths.size(); i++) {
if (mAssetPaths[i].path == ap.path) {
if (cookie) {
*cookie = static_cast<int32_t>(i+1);
}
return true;
}
}
ALOGV("In %p Asset %s path: %s", this,
ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());
//检查资源文件路径下是否有AndroidManifest.xml文件
Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked(
kAndroidManifest, Asset::ACCESS_BUFFER, ap);
if (manifestAsset == NULL) {
// This asset path does not contain any resources.
delete manifestAsset;
return false;
}
delete manifestAsset;
//添加资源文件路径
mAssetPaths.add(ap);
// new paths are always added at the end
//更新cookie值,cookie值为ap在mAssetPaths中的索引
if (cookie) {
*cookie = static_cast<int32_t>(mAssetPaths.size());
}
#ifdef HAVE_ANDROID_OS
//加载重叠资源文件
asset_path oap;
for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) {
mAssetPaths.add(oap);
}
#endif
if (mResources != NULL) {
//添加资源到资源表
appendPathToResTable(ap);
}
return true;
}
分析
该方法主要根据资源文件路径,将其添加到AssetManager。方法主要流程有:
- 校验资源文件路径。
- 判断资源文件是否已加载,即已加载会将资源文件路径添加到mAssetPaths中。
- 调用openNonAssetInPathLocked方法,打开资源文件,检查文件中(即apk中)是否有AndroidManifest文件,没有则返回结束流程。
- 添加资源文件路径到mAssetPaths,并更新其cookie值。
- 如果有重叠资源文件包,则加载重叠资源文件。
- 调用appendPathToResTable方法,将资源文件下的资源添加到资源表中。
从上面流程可以看出,资源的添加是通过appendPathToResTable方法实现的。
appendPathToResTable方法
/**
* 添加资源到资源表
* ap:资源文件路径
*/
bool AssetManager::appendPathToResTable(const asset_path& ap) const {
// skip those ap's that correspond to system overlays
if (ap.isSystemOverlay) {
return true;
}
Asset* ass = NULL;
ResTable* sharedRes = NULL; //临时资源表
bool shared = true;
bool onlyEmptyResources = true;
MY_TRACE_BEGIN(ap.path.string());
//打开路径下的idmap资源
Asset* idmap = openIdmapLocked(ap);
//返回资源表中已加载资源包总数
size_t nextEntryIdx = mResources->getTableCount();
ALOGV("Looking for resource asset in '%s'\n", ap.path.string());
if (ap.type != kFileTypeDirectory) {
//如果是系统资源包
if (nextEntryIdx == 0) {
//读取缓存的系统ResTable
sharedRes = const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTable(ap.path);
//ResTable缓存命中
if (sharedRes != NULL) {
//更新索引
nextEntryIdx = sharedRes->getTableCount();
}
}
/**
* case 1:系统资源包,ResTable缓存未命中
* case 2: 应用资源包
*/
if (sharedRes == NULL) {
//读取缓存的资源Asset
ass = const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTableAsset(ap.path);
//Asset缓存未命中,读取文件
if (ass == NULL) {
ALOGV("loading resource table %s\n", ap.path.string());
//打开路径下的resources.arsc文件,读取资源Asset
ass = const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
//缓存Asset,避免重复读取resources.arsc文件资源
if (ass != NULL && ass != kExcludedAsset) {
ass = const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTableAsset(ap.path, ass);
}
}
//系统资源包
if (nextEntryIdx == 0 && ass != NULL) {
// If this is the first resource table in the asset
// manager, then we are going to cache it so that we
// can quickly copy it out for others.
ALOGV("Creating shared resources for %s", ap.path.string());
sharedRes = new ResTable();
//添加资源文件到sharedRes
sharedRes->add(ass, idmap, nextEntryIdx + 1, false);
#ifdef HAVE_ANDROID_OS
//处理重叠包
const char* data = getenv("ANDROID_DATA");
LOG_ALWAYS_FATAL_IF(data == NULL, "ANDROID_DATA not set");
String8 overlaysListPath(data);
overlaysListPath.appendPath(kResourceCache);
overlaysListPath.appendPath("overlays.list");
addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);
#endif
//系统资源缓存到ResTable
sharedRes = const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTable(ap.path, sharedRes);
}
}
} else {
ALOGV("loading resource table %s\n", ap.path.string());
ass = const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
shared = false;
}
if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
ALOGV("Installing resource asset %p in to table %p\n", ass, mResources);
if (sharedRes != NULL) {
ALOGV("Copying existing resources for %s", ap.path.string());
//添加系统资源到mResources(AssetManager关联的ResTable)
mResources->add(sharedRes);
} else {
ALOGV("Parsing resources for %s", ap.path.string());
//添加应用资源
mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
}
onlyEmptyResources = false;
if (!shared) {
delete ass;
}
} else {
ALOGV("Installing empty resources in to table %p\n", mResources);
//添加空的ResTable,只更新mResources的Header信息
mResources->addEmpty(nextEntryIdx + 1);
}
if (idmap != NULL) {
delete idmap;
}
MY_TRACE_END();
return onlyEmptyResources;
}
分析
该方法主要是讲资源文件中的资源添加到AssetManager中的资源表中,注释已经说明大部分问题,但仍旧需要注意以下几点:
- 添加资源时,都是先从缓存中读取。这里需要区别的是,系统资源包是缓存在资源表(ResTable)中,而应用资源包资源是缓存在Asset中。
- 缓存未命中时,读取resources.arsc文件中内容,并缓存。
- 无论缓存是否命中,最后都会将读取的资源添加到资源表mResources中。资源添加调用了ResTable的add方法,见上ResTable方法分析。
网友评论