前文我们介绍了idmap文件的生成过程,剩下来的就是overlay package和idmap的加载和生效了。
我们在Android资源管理中的Runtime Resources Overlay-------之PMS、installd和idmap的处理(二)中讲到PackageManagerService中的createIdmapForPackagePairLI方法的时候,不知道大家有没有注意到这些代码:
private boolean createIdmapForPackagePairLI(PackageParser.Package pkg,
PackageParser.Package opkg) {
//......省略
//把所有overlay package的路径写入target package的applicationInfo
pkg.applicationInfo.resourceDirs = new String[overlayArray.length];
int i = 0;
for (PackageParser.Package p : overlayArray) {
pkg.applicationInfo.resourceDirs[i++] = p.baseCodePath;
}
}
也就是说,创建完idmap,PMS还会把overlay package的路径写入到target包的applicationInfo里面去,供我们的target package起来的时候加载。关于应用的启动过程,我们就不在这里多说了,应用起来后会调到ResourcesManager的getTopLevelResources方法:
//frameworks/base/core/java/android/app/ResourcesManager.java
public Resources getTopLevelResources(String resDir, String[] splitResDirs,
String[] overlayDirs, String[] libDirs, int displayId,
Configuration overrideConfiguration, CompatibilityInfo compatInfo, IBinder token) {
Resources r;
//先去查缓存,如果已经加载过,直接返回
//..............
//创建AssetManager,真正的资源管理类对象
AssetManager assets = new AssetManager();
//我们的应用本身也是一个资源包,应该把它的路径加入到AssetManager
if (resDir != null) {
if (assets.addAssetPath(resDir) == 0) {
return null;
}
}
//...............
//加入所有overlay package的路径
if (overlayDirs != null) {
for (String idmapPath : overlayDirs) {
assets.addOverlayPath(idmapPath);
}
}
//.................
//创建Resources类对象
r = new Resources(assets, dm, config, compatInfo, token);
return r;
}
这里的overlayDirs就是target package启动的时候从它的ApplicationInfo中获取的。AssetManager实例会把所有的overlay package的路径添加进去,然后加载里面的resources.arsc文件:
//frameworks/base/core/java/android/content/res/AssetManager.java
public final int addOverlayPath(String idmapPath) {
synchronized (this) {
//native 方法
int res = addOverlayPathNative(idmapPath);
/**
*我们要注意的是,此时AssetManager已经加载了系统资源包0x01和它本身0x7f
*也就是说现在已经有至少两个资源包(如果有SOC或者手机厂商的系统资源包的话也会加载)了
*一般每多一个资源包也就会多一个StringBlock,一个StringBlock就对应一个
*资源包中resources.arsc中的global string pool。所以这个makeStringBlocks
*也就是把overlay package的global string pool加载进来。
*/
makeStringBlocks(mStringBlocks);
return res;
}
}
我们走到JNI层,先看看addOverlayPathNative的实现:
//framework/base/core/jni/android_util_AssetManager.cpp
static jint android_content_AssetManager_addOverlayPath(JNIEnv* env, jobject clazz,
jstring idmapPath)
{
//idmapPath这个变量名差评,这明明是overlay包的路径,叫overlayPath比较合适
ScopedUtfChars idmapPath8(env, idmapPath);
if (idmapPath8.c_str() == NULL) {
return 0;
}
AssetManager* am = assetManagerForJavaObject(env, clazz);
if (am == NULL) {
return 0;
}
int32_t cookie;
//交给AssetManager,返回cookie值
bool res = am->addOverlayPath(String8(idmapPath8.c_str()), &cookie);
return (res) ? (jint)cookie : 0;
}
这里的cookie到底是什么呢?我们知道前面我们已经加载了android系统资源包framework-res.apk(id:0x01)和应用本身资源包(0x7f),如果此时AssetManager没有加载别的资源包,那么cookie的值将会是2.也就是说这里的cookie就是我们要添加的资源包在AssetManager中的索引值 + 1。我们先看一个函数:
//frameworks/base/libs/androidfw/AssetManager.cpp
String8 idmapPathForPackagePath(const String8& pkgPath)
{
const char* root = getenv("ANDROID_DATA");// /data
LOG_ALWAYS_FATAL_IF(root == NULL, "ANDROID_DATA not set");
String8 path(root);
path.appendPath(kResourceCache);// /data/resource-cache
char buf[256]; // 256 chars should be enough for anyone...
strncpy(buf, pkgPath.string(), 255);
buf[255] = '\0';
char* filename = buf;
//这里的pkgPath是绝对路径,所以跳过第一个/
while (*filename && *filename == '/') {
++filename;
}
char* p = filename;
//将剩下的/替换为@
while (*p) {
if (*p == '/') {
*p = '@';
}
++p;
}
// /data/resource-cache/xxx@[email protected]
path.appendPath(filename);
// /data/resource-cache/xxx@[email protected]@idmap
path.append("@idmap");
return path;
}
这个函数的作用就是根据我们overlay package的路径,返回idmap文件的路径。如果我们的overlay package路径是/vendor/overlay/demo.apk,那么返回的就是/data/resource-cache/vendor@[email protected]@idmap。我们看addOverlayPath方法的实现:
//frameworks/base/libs/androidfw/AssetManager.cpp
bool AssetManager::addOverlayPath(const String8& packagePath, int32_t* cookie)
{
//根据overlay package的路径,创建idmap文件的路径
const String8 idmapPath = idmapPathForPackagePath(packagePath);
//mAssetPaths存储已经添加的所有资源包的路径
for (size_t i = 0; i < mAssetPaths.size(); ++i) {
//如果这个路径已经添加过了,就直接返回
if (mAssetPaths[i].idmap == idmapPath) {
//返回cookie的值,索引+1
*cookie = static_cast<int32_t>(i + 1);
return true;
}
}
/**
*一连串的检查,包括idmap文件能否访问、里面的target path与overlay path是否正确等等
*/
//创建 asset_path,类型、path、idmap 三元组;
oap.path = overlayPath;
oap.type = ::getFileType(overlayPath.string());
oap.idmap = idmapPath;
//加进去,mAssetPaths是一个Vector,存放本AssetManager所有的资源包路径
mAssetPaths.add(oap);
//索引值+1
*cookie = static_cast<int32_t>(mAssetPaths.size());
//将该资源包的resources.arsc加载、解析、加入ResTable
//在Android5.x上,居然没有这关键的一句,整个RRO完全不能用。
if (mResources != NULL) {
appendPathToResTable(oap);
}
return true;
}
在AssetManager眼里,overlay package和应用本身一样,也是一个资源包,它的路径也会放到mAssetPaths里面去。其实,不仅仅如此,overlay package还会和系统资源包以及我们的target package一样,被添加到ResTable里。
//frameworks/base/libs/androidfw/AssetManager.cpp
bool AssetManager::appendPathToResTable(const asset_path& ap) const
{
//打开idmap文件
Asset* idmap = openIdmapLocked(ap);
//先从ResTable以及已经加载了的resources.arsc缓存里查,如果查到了,直接加载
//......
//如果没有找到
//打开overlay package里的resources.arsc
ass = const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
ap);
/**
*mResources是ResTable的实例
*ass resources.arsc
*idmap idmap文件
*/
mResources->add(ass, idmap, nextEntryIdx + 1, !shared);
//......
}
这个方法主要负责打开或者找到overlay package的resources.arsc和idmap文件,然后加入到ResTable中去。
//frameworks/base/libs/androidfw/ResourceTypes.cpp
status_t ResTable::add(Asset* asset, Asset* idmapAsset, const int32_t cookie, bool copyData) {
//拿到resources.arsc的内容
const void* data = asset->getBuffer(true);
if (data == NULL) {
ALOGW("Unable to get buffer of resource asset file");
return UNKNOWN_ERROR;
}
size_t idmapSize = 0;
const void* idmapData = NULL;
//拿到idmap文件的内容
if (idmapAsset != NULL) {
idmapData = idmapAsset->getBuffer(true);
if (idmapData == NULL) {
ALOGW("Unable to get buffer of idmap asset file");
return UNKNOWN_ERROR;
}
idmapSize = static_cast<size_t>(idmapAsset->getLength());
}
return addInternal(data, static_cast<size_t>(asset->getLength()),
idmapData, idmapSize, cookie, copyData);
}
这个方法主要负责拿到resources.arsc和idmap文件的内容。在看方法的实现之前,我们先看一个结构体:
//frameworks/base/libs/androidfw/ResourceTypes.cpp
/**
*ResTable::Header可以看作是对ResTable_header的封装,而一个ResTable_header
*则对应一个resources.arsc或者说一个资源包。
*/
struct ResTable::Header
{
Header(ResTable* _owner) : owner(_owner), ownedData(NULL), header(NULL),
resourceIDMap(NULL), resourceIDMapSize(0) { }
~Header()
{
free(resourceIDMap);
}
//所属于的ResTable
const ResTable* const owner;
//
void* ownedData;
//resources.arsc中的RES_TABLE_TYPE数据块,也就是整个resources.arsc
const ResTable_header* header;
//大小
size_t size;
//这个在解析string pool的时候会引用到,但是根本没什么卵用
//因为string pool有自己的一套计算方法
const uint8_t* dataEnd;
//这个header在该ResTable中的所有headers中的索引
size_t index;
//这个资源包的cookie,通常一个header对应一个资源包
//所以 cookied应该等于index + 1
int32_t cookie;
// global string pool (或者叫 value string pool)
ResStringPool values;
//idmap文件的内容
uint32_t* resourceIDMap;
//idmap大小
size_t resourceIDMapSize;
}
很简单,但也很重要,我们还是顺便了解一下比较好,因为addInternal方法本质就是将resources.arsc和idmap文件构造成一个一个的数据对象:
//frameworks/base/libs/androidfw/ResourceTypes.cpp
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) {
// 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) {
//解析包信息
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的一级子元素,然后通过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的内容
*key: overlay typeId
*value: 该type下的所有entryId的映射关系
*/
KeyedVector<uint8_t, IdmapEntries> idmapEntries;
//如果有idmap数据
if (header->resourceIDMap != NULL) {
uint8_t targetPackageId = 0;
//解析所有的idmap内容,这个后面我们会详细分析
status_t err = parseIdmap(header->resourceIDMap, header->resourceIDMapSize, &targetPackageId, &idmapEntries);
if (err != NO_ERROR) {
ALOGW("Overlay is broken");
return (mError=err);
}
/**
*overlay包的id居然被记录成了别的东西!!!
*它被记录成了对应的target包的Id,这个Id被用来作为PackageGroup的id,
*也就是说,所有的overlay package,都会被放到它们所对应的
*target package所在的那个PackageGroup里面去!!!
*/
id = targetPackageId;
}
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,表示这个包还没有加载
*target Pacakge肯定已经加载了,这里走不到,不表
*/
} else {
group = mPackageGroups.itemAt(idx - 1);
}
//我们看到,把overlay包加入到了target包所在的PackageGroup里面去了
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;
ssize_t idmapIndex = idmapEntries.indexOfKey(typeSpec->id);
if (idmapIndex >= 0) {
//根据overlay package的typeId,从idmap数据中找到
//target package中对应的type的ID,再 -1得索引
typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
}
//根据typeIndex直接找到typeList,此时的typeList里
//要么是空,要么只有target package的对应Type和其它overlay package的对应Type
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));
//会把该type中所有entry的映射关系写进去
if (idmapIndex >= 0) {
t->idmapEntries = idmapEntries[idmapIndex];
}
//加入到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在overlay package中的索引
uint8_t typeIndex = type->id - 1;
//该type在idmap中的索引
ssize_t idmapIndex = idmapEntries.indexOfKey(type->id);
if (idmapIndex >= 0) {
//找到该type在target package中的索引
typeIndex = idmapEntries[idmapIndex].targetTypeId() - 1;
}
//根据索引拿到对应的typeList
TypeList& typeList = group->types.editItemAt(typeIndex);
//拿到typeList中最后一个元素
//为什么是最后一个?
//因为最后一个才是我们刚才在RES_TABLE_TYPE_SPEC_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;
}
parsePackage方法主要对我们的overlay package的resources.arsc进行解析,然后把解析出来的数据放到target package所对应的PackageGroup中相应的数据结构中去。比如package会添加到target package所在的PackageGroup中,Type也会放到target package所在的TypeList中。另外,在parsePackage的过程中,必须对idmap数据进行加载和解析:
//frameworks/base/libs/androidfw/ResourceTypes.cpp
status_t parseIdmap(const void* idmap, size_t size, uint8_t* outPackageId, KeyedVector<uint8_t, IdmapEntries>* outMap) {
/**
*idmap是整个idmap文件的数据
*一个IdmapEntries表示一个Type的所有entry的映射信息
*/
if (!assertIdmapHeader(idmap, size)) {
return UNKNOWN_ERROR;
}
//跳过idmap header
size -= ResTable::IDMAP_HEADER_SIZE_BYTES;
if (size < sizeof(uint16_t) * 2) {
ALOGE("idmap: too small to contain any mapping");
return UNKNOWN_ERROR;
}
//跳过idmap header
const uint16_t* data = reinterpret_cast<const uint16_t*>(
reinterpret_cast<const uint8_t*>(idmap) + ResTable::IDMAP_HEADER_SIZE_BYTES);
//拿到targetPackageId
uint16_t targetPackageId = dtohs(*(data++));
if (targetPackageId == 0 || targetPackageId > 255) {
ALOGE("idmap: target package ID is invalid (%02x)", targetPackageId);
return UNKNOWN_ERROR;
}
//拿到types count
uint16_t mapCount = dtohs(*(data++));
if (mapCount == 0) {
ALOGE("idmap: no mappings");
return UNKNOWN_ERROR;
}
if (mapCount > 255) {
ALOGW("idmap: too many mappings. Only 255 are possible but %u are present", (uint32_t) mapCount);
}
//解析DATA BLOCK,其实就是一个Type一个Type地创建IdmapEntries
while (size > sizeof(uint16_t) * 4) {
IdmapEntries entries;
status_t err = entries.setTo(data, size);
if (err != NO_ERROR) {
return err;
}
ssize_t index = outMap->add(entries.overlayTypeId(), entries);
if (index < 0) {
return NO_MEMORY;
}
data += entries.byteSize() / sizeof(uint16_t);
size -= entries.byteSize();
}
//返回targetPackageId
if (outPackageId != NULL) {
*outPackageId = static_cast<uint8_t>(targetPackageId);
}
return NO_ERROR;
}
这个方法就是一个Type一个Type地解析所有的entry的映射信息。那么每个Type内部是怎么解析的呢:
//frameworks/base/libs/androidfw/ResourceTypes.cpp
class IdmapEntries {
public:
IdmapEntries() : mData(NULL) {}
//是否有entry
bool hasEntries() const {
if (mData == NULL) {
return false;
}
return (dtohs(*mData) > 0);
}
size_t byteSize() const {
if (mData == NULL) {
return 0;
}
/**
*mData[0] = target type id
*mData[1] = overlay type id
*mData[2] = entry count
*mData[3] = entry offset
*/
uint16_t entryCount = dtohs(mData[2]);
return (sizeof(uint16_t) * 4) + (sizeof(uint32_t) * static_cast<size_t>(entryCount));
}
uint8_t targetTypeId() const {
if (mData == NULL) {
return 0;
}
return dtohs(mData[0]);
}
uint8_t overlayTypeId() const {
if (mData == NULL) {
return 0;
}
return dtohs(mData[1]);
}
//setTo很简单,提取处targetTypeId、overlayTypeId
//指针指过去,完事
status_t setTo(const void* entryHeader, size_t size) {
if (reinterpret_cast<uintptr_t>(entryHeader) & 0x03) {
ALOGE("idmap: entry header is not word aligned");
return UNKNOWN_ERROR;
}
if (size < sizeof(uint16_t) * 4) {
ALOGE("idmap: entry header is too small (%u bytes)", (uint32_t) size);
return UNKNOWN_ERROR;
}
const uint16_t* header = reinterpret_cast<const uint16_t*>(entryHeader);
const uint16_t targetTypeId = dtohs(header[0]);
const uint16_t overlayTypeId = dtohs(header[1]);
if (targetTypeId == 0 || overlayTypeId == 0 || targetTypeId > 255 || overlayTypeId > 255) {
ALOGE("idmap: invalid type map (%u -> %u)", targetTypeId, overlayTypeId);
return UNKNOWN_ERROR;
}
uint16_t entryCount = dtohs(header[2]);
if (size < sizeof(uint32_t) * (entryCount + 2)) {
ALOGE("idmap: too small (%u bytes) for the number of entries (%u)",
(uint32_t) size, (uint32_t) entryCount);
return UNKNOWN_ERROR;
}
mData = header;
return NO_ERROR;
}
/**
* entryId 输入 targetId
* outEntryId 输出 overlayId
*/
status_t lookup(uint16_t entryId, uint16_t* outEntryId) const {
uint16_t entryCount = dtohs(mData[2]);
uint16_t offset = dtohs(mData[3]);
if (entryId < offset) {
// The entry is not present in this idmap
return BAD_INDEX;
}
//idmap里的映射关系是从target包的第offset项开始的
entryId -= offset;
if (entryId >= entryCount) {
// The entry is not present in this idmap
return BAD_INDEX;
}
//跳过type header
const uint32_t* entries = reinterpret_cast<const uint32_t*>(mData) + 2;
uint32_t mappedEntry = dtohl(entries[entryId]);
//0xffffffff表示不需要映射
if (mappedEntry == 0xffffffff) {
// This entry is not present in this idmap
return BAD_INDEX;
}
*outEntryId = static_cast<uint16_t>(mappedEntry);
return NO_ERROR;
}
private:
const uint16_t* mData;
}
IdmapEntries表示某一种类型的所有entry的映射关系。还记得我们在
Android资源管理中的Runtime Resources Overlay-------之PMS、installd和idmap的处理(二)说的的那个例子吗?假设我们的target 包里有10个drawable资源,它们的名称为drawable0~drawable9,id为0x7f020000~0x7f020009;overlay包里有3个资源它们的名称分别是drawable4、drawable5、drawable8,id分别是0x7f030000、0x7f030001、0x7f030002。这样就表示overlay包要覆盖target包里的drawable4、drawable5、drawable8三个资源。那么它生成的idmap的主要内容应该如下:
target type 0x00000002
overlay type 0x00000003
entry offset 0x00000004
entry count 0x00000006
entry 0x00000000 drawable/drawable4
entry 0x00000001 drawable/drawable5
entry 0xffffffff drawable/drawable6
entry 0xffffffff drawable/drawable7
entry 0x00000002 drawable/drawable8
entry 0xffffffff drawable/drawable9
结合idmap的生成过程,target包中第一个要映射的资源是drawable4,其entry ID为0x00000004,所以entry offset =0x00000004
因为drawable4在overlay包中的ID是0x00000000,所以记作
entry 0x00000000 drawable/drawable4
因为drawable5在overlay包中的ID是0x00000001
entry 0x00000001 drawable/drawable5
因为drawable6、drawable7不需要映射,所以记作
entry 0xffffffff drawable/drawable6
entry 0xffffffff drawable/drawable7
因为drawable8在overlay包中的ID是0x00000002,所以记作
entry 0x00000002 drawable/drawable8
因为drawable9不需要映射,所以记作
entry 0xffffffff drawable/drawable9
我们可以试一下lookup函数,如果我们传入entryId=0x00000008,offset=0x00000004,entryId-offset=0x00000004,映射表的第4项(从第0项开始)刚好是0x00000002,也就是overlay package中drawable8的entry ID。
另外,我们知道android系统的资源我们也是可以通过RRO来改变的,前文我们说到系统资源的RRO是在native层做的。在AssetManager添加系统资源包的时候,会调用到addOverlayPath,然后调到appendPathToResTable方法:
//frameworks/base/libs/androidfw/AssetManager.cpp
bool AssetManager::appendPathToResTable(const asset_path& ap) const {
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");
//overlaysListPath="/data/resource-cache/overlays.list";
addSystemOverlays(overlaysListPath.string(), ap.path, sharedRes, nextEntryIdx);
}
void AssetManager::addSystemOverlays(const char* pathOverlaysList,
const String8& targetPackagePath, ResTable* sharedRes, size_t offset) const
{
//打开/data/resource-cache/overlays.list
FILE* fin = fopen(pathOverlaysList, "r");
if (fin == NULL) {
return;
}
char buf[1024];
while (fgets(buf, sizeof(buf), fin)) {
//每一行表示一个overlay package,并且格式是
// 空格
char* space = strchr(buf, ' ');
char* newline = strchr(buf, '\n');
asset_path oap;
if (space == NULL || newline == NULL || newline < space) {
continue;
}
//解析出overlay package的路径和idmap文件的路径
oap.path = String8(buf, space - buf);
oap.type = kFileTypeRegular;
oap.idmap = String8(space + 1, newline - space - 1);
//打开idmap文件
Asset* oass = const_cast<AssetManager*>(this)->
openNonAssetInPathLocked("resources.arsc",
Asset::ACCESS_BUFFER,
oap);
if (oass != NULL) {
Asset* oidmap = openIdmapLocked(oap);
offset++;
//ResTable::add方法,进入了熟悉的流程,我们前面已经分析过这个方法了
sharedRes->add(oass, oidmap, offset + 1, false);
const_cast<AssetManager*>(this)->mAssetPaths.add(oap);
const_cast<AssetManager*>(this)->mZipSet.addOverlay(targetPackagePath, oap);
}
}
fclose(fin);
}
我们看到系统资源包framework-res.apk的RRO很简单,我们只需要准备好overlay package, 然后通过idmap这个bin文件生成一下idmap文件,然后在/data/resource-cache/overlays.list文件里写一下overlay package的路径和生成的idmap文件的路径即可,系统就会自己去加载了。
到这里overlay包和idamp文件的加载解析已经完成,也就是说AssetManager中已经有了overlay package相关的全部信息和资源,剩下的就是我们去引用和生效了,下期讲。