在Android资源管理框架-------之Android中的资源包(二)一文中,我们讲了Android对资源包的管理,当然这个管理我们说得比较宏观。在了解了Android资源管理相关的一些数据结构后,我们就可以深入地学习Android对资源包的管理和组织了,本篇我们先说资源信息的加载。
在Android资源管理框架-------之Android中的资源包(二)一文中,我们讲到当应用启动后,framework会通过ResourcesManager.getTopLevelResources()
方法来创建AssetManager
对象,并调用AssetManager
的相关方法,将该应用本身(它自己也是一个资源包)、资源共享库、overlay包统统添加进去。具体怎么实现的呢?我们接着往下看:
//framework/base/core/java/android/content/res/AssetManager.java
public final int addAssetPath(String path) {
synchronized (this) {
//添加资源包,res是cookie值,也就是该资源包在AssetManager中所有资源包中的索引值+1
int res = addAssetPathNative(path);
//提取出该资源包的Global(Value) String Pool
makeStringBlocks(mStringBlocks);
return res;
}
}
public final int addOverlayPath(String idmapPath) {
synchronized (this) {
//添加资源包,res是cookie值,也就是该资源包在AssetManager中所有资源包中的索引值+1
int res = addOverlayPathNative(idmapPath);
//提取出该资源包的Global(Value) String Pool
makeStringBlocks(mStringBlocks);
return res;
}
}
其中,添加应用本身和资源共享库调用的是addAssetPath
方法,添加overlay包调用的是addOverlayPath
方法。makeStringBlocks
以及addOverlayPath
方法的实现我们分别在Android资源管理框架-------之Android中的资源包(二)和Android资源管理中的Runtime Resources Overlay-------之overlay包的加载(四)中已经讲过,这里不再赘述。我们看addAssetPathNative
方法,它对应的native实现是:
//frameworks/base/core/jni/android_util_AssetManager.cpp
static jint android_content_AssetManager_addAssetPath(JNIEnv* env, jobject clazz,
jstring path)
{
//判空检查
ScopedUtfChars path8(env, path);
if (path8.c_str() == NULL) {
return 0;
}
//拿到native层的AssetManager对象的地址,其实这个地址保存在java层的一个变量中,这里只是获取一下
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
int32_t cookie;
//添加之,cookie是输出参数
bool res = am->addAssetPath(String8(path8.c_str()), &cookie);
//添加成功则返回cookie值,否则返回0
return (res) ? static_cast<jint>(cookie) : 0;
}
这个比较简单,我们看AssetManager
的addAssetPath
方法的实现:
//frameworks/base/libs/androidfw/AssetManager.cpp
bool AssetManager::addAssetPath(const String8& path, int32_t* cookie)
{
//路径合法性检查,略去
......
//如果已经添加过则不再重复添加
for (size_t i=0; i<mAssetPaths.size(); i++) {
if (mAssetPaths[i].path == ap.path) {
if (cookie) {
//cookie 的值为 索引 + 1
*cookie = static_cast<int32_t>(i+1);
}
return true;
}
}
//检查资源包中是否存在AndroidManifest.xml,如果不存在,则返回false
......
//路径列表中添加该路径
mAssetPaths.add(ap);
if (cookie) {
//cookie 的值为 索引 + 1
*cookie = static_cast<int32_t>(mAssetPaths.size());
}
#ifdef HAVE_ANDROID_OS
// 顺便还会查找和添加overlay package
asset_path oap;
for (size_t idx = 0; mZipSet.getOverlay(ap.path, idx, &oap); idx++) {
mAssetPaths.add(oap);
}
#endif
//这里才是重点,资源包中的resources.arsc就是在这里加载的
if (mResources != NULL) {
appendPathToResTable(ap);
}
return true;
}
我们看到,这个方法主要做了两件事情:把path
加入到mAssetPaths
中去;加载path
对应的资源包中的resources.arsc。其中,后者是在appendPathToResTable
方法中实现的。我们注意到,执行这个方法前先去判断mResources
是否为空。mResources
是AssetManager
类的成员,它的类型为ResTable
,并且在构造AssetManager
对象的时候,并不会给这个成员赋值,也就是说,如果在AssetManager
对象刚构造完,就调用addAssetPath方法添加资源包,这个时候,资源包是不会被加载的。那这种情况下,到底在什么时候加载呢?
//frameworks/base/libs/androidfw/AssetManager.cpp
const ResTable* AssetManager::getResTable(bool required) const
{
//如果已经构造过,则返回
ResTable* rt = mResources;
if (rt) {
return rt;
}
mResources = new ResTable();
//主要是更新Locale信息,设置资源配置信息
updateResourceParamsLocked();
bool onlyEmptyResources = true;
const size_t N = mAssetPaths.size();
//在这里会按mAssetPaths中所有的路径来加载resources.arsc
for (size_t i=0; i<N; i++) {
bool empty = appendPathToResTable(mAssetPaths.itemAt(i));
onlyEmptyResources = onlyEmptyResources && empty;
}
//required默认是true,还未见用false的场景,也就是说只要mAssetPaths中
//有一个路径下找不到resources.arsc,就会清空mResources
if (required && onlyEmptyResources) {
ALOGW("Unable to find resources file resources.arsc");
delete mResources;
mResources = NULL;
}
}
也就是说,AssetManager
的addAssetPath
方法,未必会立即去加载资源包中的resources.arsc,有可能会有一个延迟,延迟到构造它的mResources
成员,也就是ResTable
实例的时候再去加载。那么这个getResTable方法具体会在什么时候调用呢?在我们查寻AssetManager
中资源id、资源项、加载资源、获取StringBlock等的时候,都会调用这个方法,进而加载resources.arsc。
接下来看看appendPathToResTable
是如何加载resources.arsc的:
//frameworks/base/libs/androidfw/AssetManager.cpp
bool AssetManager::appendPathToResTable(const asset_path& ap) const {
/*
* 在这里ass表示我们的资源包中的resources.arsc,
* sharedRes则是用来存放资源包中的resources.arsc中的具体数据
* 由于一个ResTable对象可以添加多个资源包,所以ResTable可以简单认为是Asset的集合
*/
Asset* ass = NULL;
ResTable* sharedRes = NULL;
bool shared = true;
bool onlyEmptyResources = true;
MY_TRACE_BEGIN(ap.path.string());
//idmap 是RRO相关的概念,这里不再多说
Asset* idmap = openIdmapLocked(ap);
/*
* 这里要先看一下已经加载过多少个资源包了,
* 如果没有加载过,那么就认为默认加载的头一个资源包是framework-res.apk
* 也就是Android的系统资源包
*/
size_t nextEntryIdx = mResources->getTableCount();
/*
* 我们要加载resources.arsc,大多数情况这个文件位于压缩包也就是apk中
* 对应我们这个if分支
* 但AssetManager也是支持对解压出来的resources.arsc的
* 这种情况对应下面的else分支
*/
//resources.arsc在apk中
if (ap.type != kFileTypeDirectory) {
if (nextEntryIdx == 0) {//加载的是framework-res.apk,也就是Google的Android系统资源包
//先去缓存中查找系统资源包及其overlay包是否已经加载过,如果加载过,同时会创建
//ResTable对象,并缓存,也就是sharedRes得到的值肯定不为空
sharedRes = const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTable(ap.path);
if (sharedRes != NULL) {
/**
* 如果已经创建过了,那么要添加的这个资源包,就应该放到它们后面
* 这种情况出现在在同一个进程创建多个不同的AssetManager或者Resources对象的时候,
* 每个AssetManager中都会添加系统资源包,这时候就用上这个缓存了
*/
nextEntryIdx = sharedRes->getTableCount();
}
}
//如果当前要加载的不是系统资源或者是系统资源,但是之前并未加载过,走这里
if (sharedRes == NULL) {
//查缓存,看看之前是否加载并创建过Asset对象
ass = const_cast<AssetManager*>(this)->
mZipSet.getZipResourceTableAsset(ap.path);
//如果没有找到,则加载之,即打开resources.arsc,并将得到的ass放入缓存
if (ass == NULL) {
ALOGV("loading resource table %s\n", ap.path.string());
//打开resources.arsc
ass = const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
//放入缓存
if (ass != NULL && ass != kExcludedAsset) {
ass = const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTableAsset(ap.path, ass);
}
}
//如果要加载的是系统资源,但这时候还从未加载过,则连同系统资源包的overlay package一并加载
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());
//创建ResTable对象
sharedRes = new ResTable();
//加入资源包中的resources.arsc以及idmap
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");
//加载系统资源包的overlay package,并将这些资源包也加入sharedRes,
addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);
#endif
//缓存sharedRes
sharedRes = const_cast<AssetManager*>(this)->
mZipSet.setZipResourceTable(ap.path, sharedRes);
}
}
} else {//resources.arsc不在apk中,而在某个路径下
//加载到内存,创建Asset对象
ass = const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
//不是apk中的不缓存,不共享,添完就删
shared = false;
}
if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
if (sharedRes != NULL) {
ALOGV("Copying existing resources for %s", ap.path.string());
//如果是系统资源包,则将系统资源包连同它的overlay package一起添加到我们的
//mResources对象当中去
mResources->add(sharedRes);
} else {
ALOGV("Parsing resources for %s", ap.path.string());
//否则只添加我们要加载的这个资源包(其实仅仅是它的resources.arsc和idmap文件而已)
mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
}
onlyEmptyResources = false;
//不共享的话,就没必要留着了
if (!shared) {
delete ass;
}
} else {
//没找到资源
ALOGV("Installing empty resources in to table %p\n", mResources);
mResources->addEmpty(nextEntryIdx + 1);
}
if (idmap != NULL) {
delete idmap;
}
MY_TRACE_END();
return onlyEmptyResources;
}
appendPathToResTable
方法总结起来也很简单,就是查缓存、加载资源(这中间可能创建Asset
、ResTable
等对象)、存缓存、异常情况处理(没找到资源时)。但是,由于涉及到等情况比较多,包括:Zygote起来后preload系统资源、应用加载系统资源、应用第一次加载其它资源(包括应用本身、资源共享库、overlay package)、应用第二次加载其它资源共四种情况。
先说Zygote起来后preload的这种情况。我们知道,在Zygote进程加载完VM,进入java世界后,会去执行ZygoteInit.java的main方法。它会创建serverSocket,用来响应SystemServier进程发出的创建Android应用进程的请求,然后就会预加载很多东西了,包括我们常用的类以及Android的系统资源。这样在Android应用进程从Zygote进程fork出来以后,就不用重新加载了,节约应用的启动时间。这个时候它会创键一个system AssetManager,此时appendPathToResTable
方法的流程是:
nextEntryIdx = 0
,从mZipSet
缓存中获取sharedRes
,结果是NULL
,然后从mZipSet
缓存中获取ass
,结果还是NULL
,然后调用openNonAssetInPathLocked
打开系统资源包中的resources.arsc文件,并赋值给ass
,并把该ass
存入mZipSet
缓存中,以后就可以从缓存中获取了。然后创建ResTable
对象并赋给sharedRes
,并把ass
添加进sharedRes
,然后同样加载系统资源包的overlay包,并添加到sharedRes
中。然后再把sharedRes
整个存入缓存,这样以后加载系统资源包,就不用一个一个ass
地添加了,而是把整个sharedRes
一起添加进去就可以了,然后我们看到mResources->add(sharedRes);
这一句就是连同系统资源包和它的overlay包一并添加到mResources
中的。当然mResources
是ResTable
的一个实例,也是Assetmanager
中最重要的一个成员,资源都加载到它里面了。这样这个system的AssetManager
的资源就加载完了。当然,overlay package是如何被添加到sharedRes
中的,我们一句带过,想要详细了解请参考Android资源管理中的Runtime Resources Overlay-------之overlay包的加载(四)。
再说应用加载系统资源的这种情况,我们前文讲过每一个Android应用的AssetManager
中都会加载系统的资源信息,具体是怎么加载的详见Android资源管理框架-------之Android中的资源包(二)。在加载的时候appendPathToResTable
方法的流程是:
nextEntryIdx = 0
,从mZipSet
缓存中获取sharedRes
,结果不为NULL
,修正nextEntryIdx = sharedRes->getTableCount();
,mResources->add(sharedRes);
这样,系统资源包就都添加到mResources
中了。
我们再看应用第一次加载其它资源(包括应用本身、资源共享库、overlay package)的情况:nextEntryIdx = mResources->getTableCount();
不为0,因为此时已经添加过系统资源包及其overlay包了,sharedRes
为空,缓存中查找ass
为空,调用openNonAssetInPathLocked
方法打开资源包中的resources.arsc文件,并赋值给ass
,并把该ass
存入mZipSet
缓存中,以后就可以从缓存中获取了。此时ass
不为空,调用mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
完成资源包的加载。
最后看应用第二次加载其它资源(包括应用本身、资源共享库、overlay package)的情况:nextEntryIdx = mResources->getTableCount();
不为0,因为此时已经添加过系统资源包及其overlay包了,sharedRes
为空,缓存中查找ass
不为空,调用mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
完成资源包的加载。
到这里AssetManager
的appendPathToResTable
方法总算说完了,这个方法中,最关键的一步是调用ResTable
类的对象mResources
的add
方法。add方法重载了好多个,但最终都会走到一下两个方法中的某一个:
//frameworks/base/libs/androidfw/ResourceTypes.cpp
status_t ResTable::add(ResTable* src)
{
mError = src->mError;
//添加Headers
for (size_t i=0; i<src->mHeaders.size(); i++) {
mHeaders.add(src->mHeaders[i]);
}
//添加PackageGroups
for (size_t i=0; i<src->mPackageGroups.size(); i++) {
PackageGroup* srcPg = src->mPackageGroups[i];
PackageGroup* pg = new PackageGroup(this, srcPg->name, srcPg->id);
//对每个PackageGroup,添加package
//需要说明的是overlay package 会和它的target package 放到同一个PackageGroup中
for (size_t j=0; j<srcPg->packages.size(); j++) {
pg->packages.add(srcPg->packages[j]);
}
///对每个PackageGroup添加TypeList
for (size_t j = 0; j < srcPg->types.size(); j++) {
if (srcPg->types[j].isEmpty()) {
continue;
}
TypeList& typeList = pg->types.editItemAt(j);
typeList.appendVector(srcPg->types[j]);
}
//添加DynamicRefTable
pg->dynamicRefTable.addMappings(srcPg->dynamicRefTable);
pg->largestTypeId = max(pg->largestTypeId, srcPg->largestTypeId);
mPackageGroups.add(pg);
}
//
memcpy(mPackageMap, src->mPackageMap, sizeof(mPackageMap));
return mError;
}
这个方法非常简单,就是对每个ResTable
中的数据结构进行添加。DynamicRefTable是资源共享库相关的东西,详见Android资源管理中的SharedLibrary和Dynamic Reference-------之AssetManager的处理(四)。下面我们看另外一个方法:
//frameworks/base/libs/androidfw/ResourceTypes.cpp
/*
* data 从表示resources.arsc的Asset对象中获取,表示resources.arsc文件的内容
* dataSize data的大小
* idmapData 从表示idmap文件的Asset对象中获取,表示idmap文件的内容
* idmapDataSize idmapData的大小
* cookie 该资源包的index + 1
* copyData是否复制data到该ResTable中,对于appendPathToResTable方法中shared
* 为false的copyData为true,否则为false
*/
status_t ResTable::addInternal(const void* data, size_t dataSize, const void* idmapData,
size_t idmapDataSize, const int32_t cookie, bool copyData)
{
//......例行检查,代码省略
//一个Header对应一个resources.arsc,也就是一个资源包
Header* header = new Header(this);
//mHeaders中的元素一般和资源包也就是resources.arsc是对应的
header->index = mHeaders.size();
//cookie = mHeaders.size() + 1
header->cookie = cookie;
//为idmap数据分配内存,header也会持有其指针
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表示这个ResTable(或者说AssetManager中所有的资源包信息)
mHeaders.add(header);
//......大小端的处理,如有必要还会复制resources.arsc的数据
//resources.arsc开头就是整个文件数据的header
header->header = (const ResTable_header*)data;
header->size = dtohl(header->header->header.size);
//dataEnd,起始地址加上整个resources.arsc的大小
header->dataEnd = ((const uint8_t*)header->header) + header->size;
size_t curPackage = 0;
/**
*跳过resources.arsc中第一个header
*也就是整个资源索引表的header
*需要说明的是整个resources.arsc有一个总的header,前面我们
*已经解析过,所以跳过
*另外,resources.arsc的一级子元素只有两种类型:
*RES_STRING_POOL_TYPE类型的global string pool
*RES_TABLE_PACKAGE_TYPE类型的资源包
*/
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))) {
//获取当前子元素的数据块的大小和类型
const size_t csize = dtohl(chunk->size);
const uint16_t ctype = dtohs(chunk->type);
if (ctype == RES_STRING_POOL_TYPE) {
// global string pool
if (header->values.getError() != NO_ERROR) {
//把global string pool提取出来
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) {
//解析包信息
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);
}
//具体解析工作交给parsePackage方法
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) {
//包中没有global string pool
ALOGW("No string values found in resource table!");
}
TABLE_NOISY(ALOGV("Returning from add with mError=%d\n", mError));
return mError;
}
这个方法其实就是解析resources.arsc的二级chunk,然后通过ResTable::Header的形式记录之,并把它添加到ResTable的mHeaders中。我们接下来看parsePackage方法,这个方法比较长,跟本文关系不大的内容这里就不贴出来了:
//frameworks/base/libs/androidfw/ResourceTypes.cpp
status_t ResTable::parsePackage(const ResTable_package* const pkg,
const Header* const header)
{
const uint8_t* base = (const uint8_t*)pkg;
const uint32_t pkgSize = dtohl(pkg->header.size);
//拿到这个资源包resources.arsc中写的包id
uint32_t id = dtohl(pkg->id);
//idmap相关的东西,和本文关系不大,略去......
PackageGroup* group = NULL;
Package* package = new Package(this, header, pkg);
/**
*写入type string pool,不过最后一个参数是错的
*不过ResStringPool有自己的解析方法,这个参数的值无所谓
*/
err = package->typeStrings.setTo(base+dtohl(pkg->typeStrings),
header->dataEnd-(base+dtohl(pkg->typeStrings)));
/**
*写入type string pool,不过最后一个参数是错的
*不过ResStringPool有自己的解析方法,这个参数的值无所谓
*/
err = package->keyStrings.setTo(base+dtohl(pkg->keyStrings),
header->dataEnd-(base+dtohl(pkg->keyStrings)));
//mPackageMap的值-1就是该id对应的包所在的PackageGroup在mPackageGroups中的索引
//另外我们看到,这个id的值是target Package的id也就是说此时它是按target pacakge来处理的
size_t idx = mPackageMap[id];
if (idx == 0) {
//idx = 0,表示这个包还没有加载过,修正为它真正的idx也就是index + 1
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]));
/**
* 创建PackageGroup对象,我们不考虑overlay package的情况下,
* 一个资源包,也就对应一个PackageGroup
*/
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);
}
//资源共享库相关的处理,这里可以不用关注
size_t N = mPackageGroups.size();
for (size_t i = 0; i < N; i++) {
//给ResTable中的所有PackageGroup,添加运行时该包的id,
//方便其它包引用当前包里面的资源
mPackageGroups[i]->dynamicRefTable.addMapping(
group->name, static_cast<uint8_t>(group->id));
}
} else {
//这种情况下,如果有overlay package,我们在这里不考虑,略去......
//否则就是往同一个AssetManager对象中,添加了两个id相同的包,
//最好不要这么干,有可能引起typeId混乱,而出错。
}
//把new出来的package添加到group中
err = group->packages.add(package);
//跳过package的header
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);
/**
*解析resources.arsc中package数据块的内部数据
*一共有三种子结构(type stringpool、keystring pool除外,前面我们已经提取这两个了):
*RES_TABLE_TYPE_SPEC_TYPE
*RES_TABLE_TYPE_TYPE
*RES_TABLE_LIBRARY_TYPE
*/
while (((const uint8_t*)chunk) <= (endPos-sizeof(ResChunk_header)) &&
((const uint8_t*)chunk) <= (endPos-dtohl(chunk->size))) {
//当前数据块的大小和类型
const size_t csize = dtohl(chunk->size);
const uint16_t ctype = dtohs(chunk->type);
//处理TYPE_SPEC数据块
if (ctype == RES_TABLE_TYPE_SPEC_TYPE) {
const ResTable_typeSpec* typeSpec = (const ResTable_typeSpec*)(chunk);
//拿到占用的空间和entryCount
const size_t typeSpecSize = dtohl(typeSpec->header.size);
const size_t newEntryCount = dtohl(typeSpec->entryCount);
//有entry的话
if (newEntryCount > 0) {
//type都是从1开始计数的,所以要 - 1
uint8_t typeIndex = typeSpec->id - 1;
//idmap相关的东西,略去......
//根据typeIndex直接找到typeList,不考虑overlay package的情况下,此时的typeList应该是空的
TypeList& typeList = group->types.editItemAt(typeIndex);
if (!typeList.isEmpty()) {
//target包的Type
const Type* existingType = typeList[0];
/**
*这个if语句出现的情景是:同一个AssetManager加载了两个id相同的包
*/
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并写入相关信息
Type* t = new Type(header, package, newEntryCount);
t->typeSpec = typeSpec;
t->typeSpecFlags = (const uint32_t*)(
((const uint8_t*)typeSpec) + dtohs(typeSpec->header.headerSize));
//idmap相关的东西,略去......
//加入到target pakage对应的typeList里面
typeList.add(t);
}
} else if (ctype == RES_TABLE_TYPE_TYPE) {
const ResTable_type* type = (const ResTable_type*)(chunk);
//拿到占用的空间和entryCount
const uint32_t typeSize = dtohl(type->header.size);
const size_t newEntryCount = dtohl(type->entryCount);
//如果有entry
if (newEntryCount > 0) {
//该type的索引
uint8_t typeIndex = type->id - 1;
//idmap相关的东西,略去......
//拿到我们之前添加进去的Type对象
Type* t = typeList.editItemAt(typeList.size() - 1);
//把该种配置下的资源添加进去
t->configs.add(type);
}
} else if (ctype == RES_TABLE_LIBRARY_TYPE) {
//资源共享库相关的东西,和本文关系不大,如有兴趣:
//https://blog.csdn.net/dayong198866/article/details/95226237
} else {
//......
}
chunk = (const ResChunk_header*)
(((const uint8_t*)chunk) + csize);
}
return NO_ERROR;
}
这个方法就是根据传过来的resources.arsc中的RES_TABLE_PACKAGE_TYPE类型的chunk,创建PackageGroup
、Package
、TypeList
、Type
等对象,并把该chunk中的数据地址写入到这些创建的数据结构中,完成对resources.arsc的解析。后面我们就可以通过这些数据结构,来进行对资源的管理工作了。另外,本文力求简洁,略去了RRO和idmap相关的处理,如果您对这部分感兴趣,请移步Android资源管理中的Runtime Resources Overlay-------之overlay包的加载(四)。
我们这里介绍的东西,绝大部分都和Android资源管理的那些数据结构关系非常紧密,如果您对这些数据结构不太熟悉,可能读起本文来会比较费力,因此建议您先仔细阅读前面的两篇文章,对这些数据结构比较熟悉后,读起本文就会容易许多。