今天来说说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块

创建索引的时候资源正忙 资源索引文件_xml

                 XMLNode.flatten_node 压平node

创建索引的时候资源正忙 资源索引文件_Android_02


创建索引的时候资源正忙 资源索引文件_字符串_03


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

创建索引的时候资源正忙 资源索引文件_创建索引的时候资源正忙_04


写入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