上一篇我们讲过了system_server、installd以及idmap,但是还是没有看到idmap文件到底使如何生成的。今天我们将会介绍AssetManager的处理,idmap文件的内容最终就是在这里产生的,不过它的主要逻辑在ResourceTypes.cpp里面。作为铺垫,我们先简单说下AssetManager中重要的数据结构:
class AssetManager : public AAssetManager {
//......省略无关代码
//各个资源包的路径
Vector<asset_path> mAssetPaths;
//存贮管理resources.arsc里的信息
mutable ResTable* mResources;
//设备的配置信息
ResTable_config* mConfig;
//......省略无关代码
}
- AssetManager 我们资源管理的接口类,上层要访问资源、获取资源信息都要通过它提供的接口来实现;当然,资源信息的加载存储,也是在它里面。
class ResTable
{
//......省略无关代码
mutable Mutex mLock;
status_t mError;
ResTable_config mParams;
// Array of all resource tables.
Vector<Header*> mHeaders;//所有resources.arsc的Header
// Array of packages in all resource tables.
Vector<PackageGroup*> mPackageGroups;//所有的PackageGroup
// Mapping from resource package IDs to indices into the internal
// package array.
//下标PackageGroup ID,value-1即为该PackageGroup在mPackageGroups中的索引
uint8_t mPackageMap[256];
//分配给资源共享库的package Id,从2开始,累加
uint8_t mNextPackageId;
}
- ResTable 主要用来存储资源AssetManager中加载的所有资源相关的信息,其实就是加载的resources.arsc。AssetManager对象中有一个
mutable ResTable* mResources;
对象,我们通过addAssetPath添加的资源的resources.arsc里面的资源包都会存储到mResources中。关于resources.arsc的数据格式和结构,我们后面会稍作说明。
struct ResTable::PackageGroup
{
//所属于的ResTable
const ResTable* const owner;
//PackageGroup的name,为该PackageGroup中第一个Package的name
String16 const name;
//PackageGroup的id,为该PackageGroup中第一个Package的id
uint32_t const id;
//PackageGroup里所有的Package
Vector<Package*> packages;
//PackageGroup里所有的类型
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;
//详见:https://blog.csdn.net/dayong198866/article/details/95635910
//不过跟本文关系不大,可无视之
DynamicRefTable dynamicRefTable;
}
- PackageGroup 表示一组相关的资源包。什么叫相关呢,有两种情况:一、两个包的id相同(这种情况在同一个AssetManager中出现,虽然不会导致系统崩溃,但应尽量避免),二、一个包是另外一个包的overlay package。这两种情况下,包会被放到同一个PackageGroup里。
struct ResTable::Package
{
//所属于的ResTable
const ResTable* const owner;
//所属于的resources.arsc, 没错一个Header就对应一个resources.arsc中的ResTable_header
const Header* const header;
//resources.arsc中的package数据段
const ResTable_package* const package;
//type string pool drawable、string、layout eg.
ResStringPool typeStrings;
//key string pool the name of an entry
ResStringPool keyStrings;
size_t typeIdOffset;
}
- Package 我们可以简单认为一个Package对应一个资源包或者resources.arsc。
typedef Vector<Type*> TypeList;
- TypeList TypeList表示同一个PackageGroup所有包中,某一类型的资源的总和。比如,一个PackageGroup里有一个target包和一个overlay包,那么一个TypeList中会有两个元素,分别表示不同包中的同一类型的资源。和PackageGroup的情况类似,正常情况下,如果这个TypeList所属的Package没有overlay包,那么这个TypeList中的元素的个数应该只有1个。
struct ResTable::Type
{
//所属于的resources.arsc, 没错一个Header就对应一个resources.arsc中的ResTable_header
const Header* const header;
//所属于的Package
const Package* const package;
//该package中该Type的资源项的个数
const size_t entryCount;
//该Type的一些描述信息,和resources.arsc中的RES_TABLE_TYPE_SPEC_TYPE数据块对应
const ResTable_typeSpec* typeSpec;
const uint32_t* typeSpecFlags;
//存放该种type的各个资源项的映射信息
IdmapEntries idmapEntries;
//存放同一种类型的资源项
Vector<const ResTable_type*> configs;
}
- Type 表示一个包中,某一类型的所有资源项,包括不同的配置下的所有资源项。它的configs变量是个Vector,这个Vector的每一项对应一种配置。比如同是类型为drawable的资源,横屏下的drawable-land和竖屏下的drawable-port就分别对应configs中的一个元素。
struct ResTable::Entry {
//配置信息
ResTable_config config;
//对应的资源项
const ResTable_entry* entry;
//该资源项所属的type
const ResTable_type* type;
uint32_t specFlags;
//该资源项所属的Package
const Package* package;
//该资源项所属的type 在type string pool中的索引
StringPoolRef typeStr;
//该资源项的名称在 key string pool中的索引
StringPoolRef keyStr;
};
- Entry 一个entry我们就可以理解为一个资源项了,它是一个key-value对(bag资源除外,先不谈它)。比如在竖屏的时候,某一个资源项的key是icon,value是drawable-port/icon.png。
以上这些数据结构在ResTable中主要用于资源的管理;而下面的数据结构则用于描述resources.arsc,它们都会对应于resources.arsc中的一个数据块:
class ResStringPool
{
status_t mError;
//数据块
void* mOwnedData;
//描述这个string pool的相关信息
const ResStringPool_header* mHeader;
//数据块占用的空间
size_t mSize;
mutable Mutex mDecodeLock;
//表示string pool中的每一个字符串相对于 mStrings的偏移量
const uint32_t* mEntries;
//表示string pool中的每一个style相对于 mStyles的偏移量
const uint32_t* mEntryStyles;
//string 数据块
const void* mStrings;
char16_t mutable** mCache;
// number of uint16_t
uint32_t mStringPoolSize;
//style块
const uint32_t* mStyles;
// number of uint32_t
uint32_t mStylePoolSize;
}
- ResStringPool 用来存储字符串的字符串池,一个resources.arsc中一般会有三个字符串池:global string pool、type string pool和key string pool。
struct ResTable_package
{
//头信息:类型、头大小、整个段大小
struct ResChunk_header header;
/**
*包id,android系统包(framework-res.apk)id是0x01
*应用包id是0x7f
*资源共享库包id是0x00
*资源共享库是作者自己的叫法,详见:
*https://blog.csdn.net/dayong198866/article/details/95226237
*/
uint32_t id;
// Actual name of this package, \0-terminated.
uint16_t name[128];
// type string pool
uint32_t typeStrings;
// Last index into typeStrings that is for public use by others.
uint32_t lastPublicType;
// key string pool
uint32_t keyStrings;
// Last index into keyStrings that is for public use by others.
uint32_t lastPublicKey;
uint32_t typeIdOffset;
}
- ResTable_package ResTable_package对应resources.arsc中的类型为RES_TABLE_PACKAGE_TYPE的数据块,注意它和ResTable::Package的区分,ResTable::Package是对ResTable_package的一个封装。
struct ResTable_config
{
//数据块的大小
uint32_t size;
//mcc mnc
union {
struct {
// Mobile country code (from SIM). 0 means "any".
uint16_t mcc;
// Mobile network code (from SIM). 0 means "any".
uint16_t mnc;
};
uint32_t imsi;
};
//......数据太多,不再一一列举
}
- ResTable_config 内部数据比较多,我们在这里只需要知道它用来描述设备的当前配置即可。
struct ResTable_typeSpec
{
//头信息:类型、头大小、整个段大小
struct ResChunk_header header;
//type 的 id,从1开始
uint8_t id;
// Must be 0.
uint8_t res0;
// Must be 0.
uint16_t res1;
// Number of uint32_t entry configuration masks that follow.
uint32_t entryCount;
enum {
// Additional flag indicating an entry is public.
SPEC_PUBLIC = 0x40000000
};
};
- ResTable_typeSpec 用来描述某一类型的资源本身,比如有多少资源项等信息。
struct ResTable_type
{
//头信息:类型、头大小、整个段大小
struct ResChunk_header header;
enum {
NO_ENTRY = 0xFFFFFFFF
};
//type 的 id,从1开始,和它对应的ResTable_typeSpec中的id值相等
uint8_t id;
// Must be 0.
uint8_t res0;
// Must be 0.
uint16_t res1;
//资源项的个数
uint32_t entryCount;
//资源项相对与该type的ResChunk_header的起始地址的偏移量
uint32_t entriesStart;
// Configuration this collection of entries is designed for.
ResTable_config config;
};
- ResTable_type 用来描述resources.arsc中某一特定配置下的一种类型的所有资源。我们要注意的是一个ResTable_typeSpec可能对应多个ResTable_type,因为它们的ResTable_config可能不一样。请注意它和ResTable::Type的区分:一个ResTable::Type中有多个ResTable_type,ResTable::Type包含了某一种类型的所有配置的所有资源。
struct ResTable_entry
{
// Number of bytes in this structure.
uint16_t size;
enum {
// If set, this is a complex entry, holding a set of name/value
// mappings. It is followed by an array of ResTable_map structures.
FLAG_COMPLEX = 0x0001,
// If set, this resource has been declared public, so libraries
// are allowed to reference it.
FLAG_PUBLIC = 0x0002
};
uint16_t flags;
// Reference into ResTable_package::keyStrings identifying this entry.
//资源的名称在key string pool中的索引
struct ResStringPool_ref key;
}
- ResTable_entry 一个资源项,确切地说是一个资源项的名称,在resources.arsc中这个结构体后面会跟一个ResTable_value作为资源项的值(先不考虑bag资源)。ResTable::Entry是对ResTable_entry的一个封装。
struct Res_value
{
// Number of bytes in this structure.
uint16_t size;
// Always set to 0.
uint8_t res0;
uint8_t dataType;
uint32_t data;
}
- Res_value 在resources.arsc中这个结构体会跟在ResTable_entry后面,表示一个资源项的值。
AssetManager相关的数据结构总算介绍完了!因为资源这一块儿相对比较复杂,所以涉及的数据结构比较多,但这是理解Android资源管理的基础。资源管理最主要的工作就是resources.arsc的解析和管理,所以我们还要对resources.arsc有个直观的认识,这个网上介绍的文章很多,大家可以去看看。
前文我们讲到idmap进程最终还是把idmap文件的生成工作交给了AssetManager来完成,那么我们继续看看AssetManager的实现:
bool AssetManager::createIdmap(const char* targetApkPath, const char* overlayApkPath,
uint32_t targetCrc, uint32_t overlayCrc, uint32_t** outData, size_t* outSize)
{
AutoMutex _l(mLock);
const String8 paths[2] = { String8(targetApkPath), String8(overlayApkPath) };
ResTable tables[2];
for (int i = 0; i < 2; ++i) {
asset_path ap;
ap.type = kFileTypeRegular;
ap.path = paths[i];
//代开对应的resources.arsc
Asset* ass = openNonAssetInPathLocked("resources.arsc", Asset::ACCESS_BUFFER, ap);
if (ass == NULL) {
ALOGW("failed to find resources.arsc in %s\n", ap.path.string());
return false;
}
//构造ResTable
tables[i].add(ass);
}
return tables[0].createIdmap(tables[1], targetCrc, overlayCrc,
targetApkPath, overlayApkPath, (void**)outData, outSize) == NO_ERROR;
}
我们看到AssetManager会根据target包和overlay包的路径,分别去打开并加载他们的resources.arsc文件,然后分别创建Restable的实例,剩下的工作就看Restable了:
//用来表示某一类型的资源的idmap信息,这个结构体很简单,没什么好说的
struct IdmapTypeMap {
ssize_t overlayTypeId;
size_t entryOffset;
Vector<uint32_t> entryMap;
};
status_t ResTable::createIdmap(const ResTable& overlay,
uint32_t targetCrc, uint32_t overlayCrc,
const char* targetPath, const char* overlayPath,
void** outData, size_t* outSize) const
{
//......略去次要代码
//用来保存所有的idmap数据,key:target typeindex
KeyedVector<uint8_t, IdmapTypeMap> map;
//在target ResTable中我们只加载了target package 一个资源包,此时里面应该只有一个元素
const PackageGroup* pg = mPackageGroups[0];
// starting size is header
*outSize = ResTable::IDMAP_HEADER_SIZE_BYTES;
// target package id and number of types in map
*outSize += 2 * sizeof(uint16_t);
// overlay packages are assumed to contain only one package group
const ResTable_package* overlayPackageStruct = overlay.mPackageGroups[0]->packages[0]->package;
char16_t tmpName[sizeof(overlayPackageStruct->name)/sizeof(overlayPackageStruct->name[0])];
strcpy16_dtoh(tmpName, overlayPackageStruct->name, sizeof(overlayPackageStruct->
name)/sizeof(overlayPackageStruct->name[0]));
//overlay PackageName
const String16 overlayPackage(tmpName);
/**
*按照typeIndex对所有类型的资源做处理
*另外,我们看到这个循环是根据pg变量(也就是target package)来做的,
*所以,如果我们的overlay包里新添加了某种类型的资源,将会被无视。
*/
for (size_t typeIndex = 0; typeIndex < pg->types.size(); ++typeIndex) {
const TypeList& typeList = pg->types[typeIndex];
if (typeList.isEmpty()) {
continue;
}
//此时target ResTable中只有一个target资源包,所以每一个typeList中都最多只有一个元素
const Type* typeConfigs = typeList[0];
//用来存储一种type的所有entry的映射关系
IdmapTypeMap typeMap;
//overlay package中的type id
typeMap.overlayTypeId = -1;
/**
*从target package中id 为typeIndex的类型的资源的entryOffset项开始做idmap
*为什么要有这个偏移量呢?为了节省资源:
*假设我们要覆盖target包中drawable中的第7,8,12项资源
*那么前面的0~6项我们没必要处理,从第7项开始就可以了,所以才有这个entryOffset变量
*/
typeMap.entryOffset = 0;
//记录每一个资源项的映射关系。
for (size_t entryIndex = 0; entryIndex < typeConfigs->entryCount; ++entryIndex) {
resource_name resName;
//做检查,target包中都没有的资源还处理个啥,跳过
if (!this->getResourceName(resID, false, &resName)) {
//但是要记录我们跳过了一个资源entryOffset++
if (typeMap.entryMap.isEmpty()) {
typeMap.entryOffset++;
}
continue;
}
const String16 overlayType(resName.type, resName.typeLen);
const String16 overlayName(resName.name, resName.nameLen);
//根据资源名称获取资源id,string pool遍历操作,效率极低,所以只在生成索引的时候
//做一次,后面的就直接根据id(也就是索引来了)。
uint32_t overlayResID = overlay.identifierForName(overlayName.string(),
overlayName.size(),
overlayType.string(),
overlayType.size(),
overlayPackage.string(),
overlayPackage.size());
//target包中有,但overlay 包中没有的资源,不做映射,但要记录一下我们跳过去了
if (overlayResID == 0) {
if (typeMap.entryMap.isEmpty()) {
typeMap.entryOffset++;
}
continue;
}
//写入target type id 和overlay type id的映射关系,
//target type id就是这个typeMap在变量map中的key
if (typeMap.overlayTypeId == -1) {
typeMap.overlayTypeId = Res_GETTYPE(overlayResID) + 1;
}
if (typeMap.entryOffset + typeMap.entryMap.size() < entryIndex) {
/**
*对于target包中entryOffset后面的资源,如果overlay package中没有
*则对应entry映射关系写成:
*target: entryOffset+index-----> overlay: 0xffffffff
*0xffffffff(-1)表示不需要做映射,也即overlay package里没有的资源
*/
size_t index = typeMap.entryMap.size();
size_t numItems = entryIndex - (typeMap.entryOffset + index);
//overlay package里没有的资源补成0xffffffff
if (typeMap.entryMap.insertAt(0xffffffff, index, numItems) < 0) {
return NO_MEMORY;
}
}
//添加entry映射关系:
//entryOffset+index-----> overlay: Res_GETENTRY(overlayResID)
typeMap.entryMap.add(Res_GETENTRY(overlayResID));
}
//处理完一个类型的资源的映射,如果有映射数据
if (!typeMap.entryMap.isEmpty()) {
//添加到最终的记录里面去
if (map.add(static_cast<uint8_t>(typeIndex), typeMap) < 0) {
return NO_MEMORY;
}
//输出数据的szie加上一个IdmapTypeMap的实际大小
*outSize += (4 * sizeof(uint16_t)) + (typeMap.entryMap.size() * sizeof(uint32_t));
}
}
/**
*第一阶段工作完成,记录target package和overlay package中所有type和所有entry的映射关系
*保存在map变量中,并且计算出了idmap数据的大小outSize。
*下面开始输出idmap的具体数据到outData
*/
//分配内存
if ((*outData = malloc(*outSize)) == NULL) {
return NO_MEMORY;
}
//写入相关头信息
uint32_t* data = (uint32_t*)*outData;
*data++ = htodl(IDMAP_MAGIC);
*data++ = htodl(IDMAP_CURRENT_VERSION);
*data++ = htodl(targetCrc);
*data++ = htodl(overlayCrc);
//写入targetPath、overlayPath
const char* paths[] = { targetPath, overlayPath };
for (int j = 0; j < 2; ++j) {
char* p = (char*)data;
const char* path = paths[j];
const size_t I = strlen(path);
if (I > 255) {
ALOGV("path exceeds expected 255 characters: %s\n", path);
return UNKNOWN_ERROR;
}
for (size_t i = 0; i < 256; ++i) {
*p++ = i < I ? path[i] : '\0';
}
data += 256 / sizeof(uint32_t);
}
//写入target包id和types count
const size_t mapSize = map.size();
uint16_t* typeData = reinterpret_cast<uint16_t*>(data);
*typeData++ = htods(pg->id);
*typeData++ = htods(mapSize);
//写入每一个entry的映射信息
for (size_t i = 0; i < mapSize; ++i) {
uint8_t targetTypeId = map.keyAt(i);
const IdmapTypeMap& typeMap = map[i];
//写入target type
*typeData++ = htods(targetTypeId + 1);
//写入overlay type
*typeData++ = htods(typeMap.overlayTypeId);
//写入entry count
*typeData++ = htods(typeMap.entryMap.size());
//写入entry offset
*typeData++ = htods(typeMap.entryOffset);
const size_t entryCount = typeMap.entryMap.size();
uint32_t* entries = reinterpret_cast<uint32_t*>(typeData);
for (size_t j = 0; j < entryCount; j++) {
//写入每一个entry的映射信息
//key:typeMap.entryOffset+j
//value:typeMap.entryMap[j]
entries[j] = htodl(typeMap.entryMap[j]);
}
//指针移动 entryCount * 2 * 2 = entryCount * sizeof(uint32_t)个字节
typeData += entryCount * 2;
}
return NO_ERROR;
}
至此,idmap总算生成了,这个方法本身并不长,只是全是我加的注释,哈哈。关于idmap,我们要注意一下几点:
- idmap本质上是从target包的某些资源到overlay包的资源的id的映射。我们知道资源的id是0xpptteeee形式的,所以这个映射本身也是分为三部分:target packageId到overlay packageId的映射、target typeId到overlay typeId的映射、target entryId到overlay entryId的映射。
- 其中target entryId到overlay entryId的映射比较复杂,target entryId = entryMapIndex + entry offset。entryMapIndex指该项在本type中出现的index。对于entry offset后面的资源,如果 overlay包中没有则Map的值为0xffffffff。Android的这种处理方式也是用空间换时间,因为访问时,它可以基于索引直接计算出对应的overlay package中entryId,速度肯定快;副作用是对于不需要映射的资源也要做记录0xffffffff,占用空间。其实我们完全可以做一个Map,它的key为需要映射的target Package的resId,value为对应的overlay package中的resId,不需要映射的我们根本不用记录。这样我们要记录的数据就会少很多。但访问时,就要遍历我们的这个Map去查寻,而不是基于索引去访问,运行效率会降低。
- 从idmap中映射关系的生成过程来看,里面的遍历都是基于target包的,这样overlay 包只能覆盖,而不能新增资源。新增的将被无视掉。
现在RRO中最关键的组件idmap文件已经生成了,剩下的就是如何加载和生效了,下期讲。