今天来说说Android的资源索引系统, Android的资源编译使用aapt工具, 查找则使用AssertManager.
Android如何来索引资源呢,首先我们从一个应用程序来找资源进行分析.
Android中通过id来进行资源的查找, 那么怎么通过id来查找呢
我们对id进行分割, id是一个32位的数字, 前8位为packageid, 后面8为为typeid,最后16位是在该type下的资源索引.
首先根据packageid找到资源索引文件,也就是resources.arsc文件. 根据在根据type找到对应的type集合, type类型一般有drawable, layout, anim等等, 再根据id的后16为找到对应id描述的资源, 最后根据当前系统配置,选择最合适的资源,
Aapt
AaptDir: 描述一个类型的文件夹,比如 drawable,drawable-xxxdpi都对应drawler AaptDir, Aaptdir中存放多个AaptGroup
AaptGroup: 则描述AaptDir下相同名称的文件, 比如drawable/a.xml drawable-xxxdpi/a.xml, 都放在同一个AaptGroup下, 每个文件对应一个AaptFile
AaptFile: 则具体描述了一个资源文件.
aapt编译的过程首先就是组织文件,也就是描述AaptDir->AaptFile的树状结构, 之后编译文件.也就是对文件名称进行索引.
之后编译values文件夹下的资源生成索引
之后编译xml
剩下的就是写文件,写R.java
编译的过程其实就是将AaptDir->AaptFile转换为
Type
ConfigList
Entry的过程
Type对应AaptDir 描述一类资源如 drawable,drawable-xxxdpi 都属于drawable类型资源,一个type下有多个ConfigList
ConfigList: 描述相同id资源的不同属性资源如 xxdpi xdpi属于不同属性, 索引最终要就是找到ConfigList, 每个ConfigList下的不同属性相同id的资源用Entry描述
Entry:描述一个资源的值以及配置, 系统会根据配置选择ConfigList的Enrty
assets->slurpFromArgs(bundle) 收集文件.
AaptDir 表示一个文件夹
AaptFile对应一个文件, AaptFile 的mGroupEntry成员变量类型为 AaptGroupEntry,代表文件的属性信息,如 drawler-xxhdpi , 属性信息为xxhdpi
AaptGroup 为一组同名不同属性的文件
编译资源步骤
parsePackage 从Manifest解析出包名
ResourceTable.addIncludedResources 添加引用资源 如android.jar
collect_files 收集资源文件
applyFileOverlay 执行overlay
makeFileResources 收集资源项
compileResourceFile 编译values 文件
ResourceTable.assignResourceIds() 分配资源id
Entry->generateAttributes(this, p->getName()) 给bag资源分配id
Entry->assignResourceIds(this, p->getName()) 给bag资源分配到的id保存到bagKeyId 中
compileXmlFile 编译xml文件
XMLNode.assignResourceIds 给xml的attribute分配id
XMLNode.parseValues 给xml的属性设置value
XMLNode.flatten 压平
XMLNode.collect_resid_strings 收集属性字符串,属性id
XMLNode.collect_strings 收集字符串
写xml头, 写字符串池块,写id池块
XMLNode.flatten_node 写node块
XMLNode.flatten_node 压平node
compileXmlFile 编译AndroidManifest.xml文件
ResourceTable.addSymbols 生成R文件内容
ResourceTable.flatten 生成包级信息
StringPool valueStrings(useUTF8) 值字符串池,全局
StringPool typeStrings(useUTF8); 类型字符串池. 包级
StringPool keyStrings(useUTF8); 资源名字符串池, 包级
configTypeName = “1complex”; 编译类型
configTypeName = “2value”; 值类型
Package.setTypeStrings(typeStrings.createStringBlock()); 收集typestrings
Package.setKeyStrings(keyStrings.createStringBlock()); 收集keystrings
1 收集typeStrings, keyStrings ,valueStrings
2Entry.prepareFlatten(&valueStrings, this,
&configTypeName, &config) 收集keystrings
3 写RES_TABLE_PACKAGE_TYPE 头文件ResTable_package 每个包对应一个
4 写typestrings
5 写keystrings
6 写依赖库信息RES_TABLE_LIBRARY_TYPE ResTable_lib_header
7 写RES_TABLE_TYPE_SPEC_TYPE ResTable_typeSpec 用于描述每种type下每个资源支持的配置信息,通过uint32_t类型数类描述支持的配置信息
8 写RES_TABLE_TYPE_TYPE ResTable_type 用于描述每种type下每种配置下对应的资源项, 也即没重类型的每个配置对应一个ResTable_type. 如下图, 该结构分为三部分,分别是头部, 描述资源具体信息的偏移信息,以及资源的具体信息 ResTable_entry. 所谓ResTable_entry其实保存的就是Res_Value(对于数字类型的Res_value->data为具体的数字, 对于字符串类型的Res_value->data为字符串池中的索引值,文件名其实也是字符串类型, 对于引用类型Res_value->data保存id值), 对于bag类型则保存bag信息. 这里不展开bag
写入RES_TABLE_TYPE ResTable_header 到resources.arsc
写如 valuestrings字符串池
写入ResourceTable.flatten 生成的包级信息
写R.java文件
所以总结一下resources.arsc的格式
ResTable_header
StringPool: valuestrings
PackageInfo: 0 … package_size
ResTable_package
StringPool: typestrings
StringPool: keystrings
ResTable_lib_header
0…type_size
ResTable_typeSpec 0…config_size
ResTable_type 0…config_size
attribute_entry 描述xml的一个属性
struct attribute_entry {
String16 ns; xml属性的命名空间
String16 name; 属性名
String16 string; // 值字符串
Res_value value; // 解析后的值
uint32_t index; // 第几个属性
uint32_t nameResId; 属性对应的id
mutable uint32_t namePoolIdx; // 在字符串池中的索引
};
Res_value 描述一个值
struct Res_value
{
// Number of bytes in this structure.
uint16_t size; // 结构提占用的内存大小
// Always set to 0.
uint8_t res0; // 总是设置成0
uint8_t dataType; 数据类型
// The data for this item, as interpreted according to dataType.
typedef uint32_t data_type;
data_type data; // 数据值
};
对于数字类型的Res_value->data为具体的数字, 对于字符串类型的Res_value->data为字符串池中的索引值,文件名其实也是字符串类型, 对于引用类型Res_value->data保存id值
class Entry{
private:
String16 mName; // 名字
String16 mParent; // 父名字
type mType; // 类型 TYPE_ITEM 和TYPE_BAG
Item mItem; // 当类型为TYPE_ITEM 使用该item存放value
int32_t mItemFormat; // entry表示的类型
KeyedVector<String16, Item> mBag; // TYPE_BAG 描述该资源下的bag,其实每个bag也是一个Item
int32_t mNameIndex; // 字符串池中的索引
uint32_t mParentId;
SourcePos mPos;
};
class Type {
private:
String16 mName; //名字
SourcePos* mFirstPublicSourcePos;
DefaultKeyedVector<String16, Public> mPublic;
DefaultKeyedVector<String16, sp<ConfigList> > mConfigs; //资源同名项
Vector<sp<ConfigList> > mOrderedConfigs; ///排序同名资源项
SortedVector<String16> mCanAddEntries;
int32_t mPublicIndex;
int32_t mIndex; //第几项
SourcePos mPos;
}
class Package {
const String16 mName; //包名
const size_t mPackageId; // id
DefaultKeyedVector<String16, sp<Type> > mTypes; // 支持的类型
Vector<sp<Type> > mOrderedTypes; // 排序后的类型
sp<AaptFile> mTypeStringsData; // 类型字符串池文件
sp<AaptFile> mKeyStringsData; // 名称字符串池文件
ResStringPool mTypeStrings; // 类型字符串池
ResStringPool mKeyStrings;// 名称字符串池
DefaultKeyedVector<String16, uint32_t> mTypeStringsMapping;
DefaultKeyedVector<String16, uint32_t> mKeyStringsMapping;
}
所以resource的编译过程包括两部分, 分别是xml编译和resources.arsc 字符串索引
最重要的数据结构 Res_value , Entry, Item , attribute_entry, StringPool