理解Android資源索引

今天來說說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
Restable_type
寫入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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章