Android资源管理框架-------之resources.arsc(三)

        上一篇我们介绍了Android中资源包相关的东西以及Framework是怎么一步步加载system和应用的资源包的,但是我们的介绍到java层的AssetManager就中止了,没有继续深入,原因是继续深入的话,主要就涉及到ResTable相关的东西了,而ResTable对Android资源的管理,非常依赖于resources.arsc,并且会有大量的数据结构,比较复杂。我们本篇先介绍Android底层资源管理的基础resources.arsc文件以及相关的数据结构。

        Android对资源进行管理,那就肯定要有资源相关的信息,资源相关的信息放在哪里呢?当然是resources.arsc里! resources.arsc是AAPT在编译android应用程序资源的时候生成的(具体生成过程是APPT绝对的重头戏,我们后面介绍AAPT的时候会专门讲)一个二进制文件,它会存储values类型的资源(这些资源都是非常小的文本资源,直接存储在resources.arsc没问题),以及其它非Asset类型的资源相关的信息(图片、raw以及xml等类型的资源会存储其路径,毕竟这种资源可能非常大,直接打包到resources.arsc中有可能会让resources.arsc臃肿无比)。在我们获取资源的时候,如果要获取的资源是values类型(string、style、integer、dimen、attr、color、array等等),Android资源管理框架直接就可以从resources.arsc中拿到结果;否则,拿到的将会是资源相关的信息,比如一个布局文件的路径,或者一张图片的路径,然后再根据这些信息去加载具体的资源。另外,AAPT在编译资源的时候,也会同时生成R.java。我们知道R.java里的字段都是整形的,当resources.arsc被加载到内存后,如果我们要获取某个资源相关的信息,我们当然可以根据资源的名字去查寻,但是这样的话效率会比较低。高效的方法是使用R.java,一个R.java文件是一个resources.arsc中资源的索引,这样基于索引来访问效率肯定高很多了。

        说了这么多,那resources.arsc的结构到底长啥样呢?我们来看一下这个图:

        介绍resources.arsc的图文有很多,大家会发现我们的这个结构图和其它的相比不太一样,多出了一个类型为RES_TABLE_LIBRARY_TYPE的数据结构。另外,我们能可能地标明了每个字段所占用的空间,单位为字节。下面我们对这些数据结构一一做出说明:

ResChunk_header

        resources.arsc中的数据是按照chunk来存储的,一个ResTable是一个chunk,一个ResStringPool是一个chunk,一个ResTable_package是一个chunk,一个ResTable_typeSpec是一个chunk,一个ResTable_type是一个chunk,一个ResTable_lib_header也是一个chunk。再加上ResTable_config、ResTable_entry、ResTable_map_entry、ResTable_map、Res_value、ResTable_lib_entry等数,它们构成了描述resources.arsc的基本数据结构,它们都定义在frameworks/base/include/androidfw/ResourceTypes.h中,我们结合上面的图,来做些简单介绍:

//frameworks/base/include/androidfw/ResourceTypes.h
struct ResChunk_header
{
    //表示一个chunk的类型,它的值是下面的枚举
    uint16_t type;

    //每一个chunk都会有一个header,来描述这个chunk本身的信息,
    //headerSize表示这个chunk的header的大小,单位:字节
    uint16_t headerSize;

    // Total size of this chunk (in bytes).  This is the chunkSize plus
    // the size of any data associated with the chunk.  Adding this value
    // to the chunk allows you to completely skip its contents (including
    // any child chunks).  If this value is the same as chunkSize, there is
    // no data associated with the chunk.
    //这个chunk的大小,单位:字节
    //包括这个chunk的header和内容,总的大小
    uint32_t size;
};
enum {
    RES_NULL_TYPE               = 0x0000,
    //对应ResStringPool,表示一个chunk的类型是字符串池
    RES_STRING_POOL_TYPE        = 0x0001,
    //没有数据结构与之对应,一般表示一个chunk的类型是一个资源表,也就是这个resources.arsc的全部内容
    RES_TABLE_TYPE              = 0x0002,
    //对应ResXMLTree,表示一个chunk是一个xmltree
    RES_XML_TYPE                = 0x0003,

    // xml相关的chunk
    RES_XML_FIRST_CHUNK_TYPE    = 0x0100,
    RES_XML_START_NAMESPACE_TYPE= 0x0100,
    RES_XML_END_NAMESPACE_TYPE  = 0x0101,
    RES_XML_START_ELEMENT_TYPE  = 0x0102,
    RES_XML_END_ELEMENT_TYPE    = 0x0103,
    RES_XML_CDATA_TYPE          = 0x0104,
    RES_XML_LAST_CHUNK_TYPE     = 0x017f,
    // This contains a uint32_t array mapping strings in the string
    // pool back to resource identifiers.  It is optional.
    RES_XML_RESOURCE_MAP_TYPE   = 0x0180,

    //对应ResTable_package, 表示当前chunk是一个package
    RES_TABLE_PACKAGE_TYPE      = 0x0200,
    //对应ResTable_type, 表示当前chunk是一个type
    RES_TABLE_TYPE_TYPE         = 0x0201,
    //对应ResTable_typeSpec, 表示当前chunk是一个ResTable_typeSpec
    RES_TABLE_TYPE_SPEC_TYPE    = 0x0202,
    //对应ResTable_lib_header, 表示当前chunk是依赖的资源库信息
    RES_TABLE_LIBRARY_TYPE      = 0x0203
};

        ResChunk_header用于描述一个chunk的类型、这个chunk的header的大小、这个chunk总共的大小。这样,我们拿到一个chunk的起始地址后,马上就可以定位出header的结束地址、内容的起始地址,整个chunk的结束地址,这样做很有意义,当我们想跳过这个chunk的header时,直接拿这个chunk的起始地址+headerSize就是这个chunk的内容,非常方便。并且这样非常利于相对地址的使用,非常灵活,独立性更强。

ResStringPool

        从结构图上我们可以看到,一个resources.arsc就是一个类型为RES_TABLE_TYPE的chunk。它的头部就是一个ResChunk_header,剩下的数据结构全是它的内容。不过在ResourceTypes.h中,并没有专门定义一个数据结构来描述这个类型为RES_TABLE_TYPE的chunk。我们看到在这个chunk内部有两个子chunk:一个chunk的类型为RES_STRING_POOL_TYPE,另一个chunk的类型为RES_TABLE_PACKAGE_TYPE。其中第一个chunk,也就是类型RES_STRING_POOL_TYPE的chunk在frameworks/base/include/androidfw/ResourceTypes.h中对应的数据结构为ResStringPool,也就是我们所说的字符串池。它的头部对应的数据结构为ResStringPool_header

//frameworks/base/include/androidfw/ResourceTypes.h
struct ResStringPool_header
{
    /**
    * type = RES_STRING_POOL_TYPE
    * headerSize = sizeof(ResStringPool_header)= 0x1c
    * size 的值要根据resStringPool具体大小来确定了
    */
    struct ResChunk_header header;

    //这个resStringPool中存放的String的数量
    uint32_t stringCount;

    //这个resStringPool中存放的style的数量
    uint32_t styleCount;

    // Flags.
    enum {
        // If set, the string index is sorted by the string values (based
        // on strcmp16()).
        SORTED_FLAG = 1<<0,

        // String pool is encoded in UTF-8
        UTF8_FLAG = 1<<8
    };
    uint32_t flags;

    // 字符串数据段相对于这个resStringPool的header的起始地址的偏移量
    uint32_t stringsStart;

    // style数据段相对于这个resStringPool的header的起始地址的偏移量
    uint32_t stylesStart;
}

        ResStringPool_header非常好理解,它内部有一个ResChunk_header,来描述本chunk的类型,header的大小以及本chunk的大小,然后就是这个字符串池里的字符串相关的信息了。这里的style是指string的style,比如粗体、斜体等。在resources.arsc中,一个ResStringPool_header后面跟着的就是这个字符串池的内容了,如上面的结构图,它包括四部分:mEntries、mEntryStyles、mStrings、mStyles:

//frameworks/base/include/androidfw/ResourceTypes.h
class ResStringPool
{
public:
    ResStringPool();
    ResStringPool(const void* data, size_t size, bool copyData=false);
    ~ResStringPool();

    void setToEmpty();
    status_t setTo(const void* data, size_t size, bool copyData=false);

    status_t getError() const;

    void uninit();

    // Return string entry as UTF16; if the pool is UTF8, the string will
    // be converted before returning.
    inline const char16_t* stringAt(const ResStringPool_ref& ref, size_t* outLen) const {
        return stringAt(ref.index, outLen);
    }
    const char16_t* stringAt(size_t idx, size_t* outLen) const;

    // Note: returns null if the string pool is not UTF8.
    const char* string8At(size_t idx, size_t* outLen) const;

    // Return string whether the pool is UTF8 or UTF16.  Does not allow you
    // to distinguish null.
    const String8 string8ObjectAt(size_t idx) const;

    const ResStringPool_span* styleAt(const ResStringPool_ref& ref) const;
    const ResStringPool_span* styleAt(size_t idx) const;

    ssize_t indexOfString(const char16_t* str, size_t strLen) const;

    size_t size() const;
    size_t styleCount() const;
    size_t bytes() const;

    bool isSorted() const;
    bool isUTF8() const;

private:
    status_t                    mError;
    //指向这个字符串池的起始地址,也就是它的ResStringPool_header的地址,不过这个指针只有
    //在构造时,copy data的时候才会用到
    void*                       mOwnedData;
    //指向header
    const ResStringPool_header* mHeader;
    //字符串池的大小,包括header的大小
    //mSize = mHeader->header.size;
    size_t                      mSize;
    mutable Mutex               mDecodeLock;
    //string偏移数组,它的元素的个数为mHeader.stringCount
    const uint32_t*             mEntries;
    //style偏移数组,它的元素的个数为mHeader.styleCount
    const uint32_t*             mEntryStyles;
    //指向字符串起始地址,存放所该字符串池中所有的字符串
    const void*                 mStrings;
    char16_t mutable**          mCache;
    uint32_t                    mStringPoolSize;    // number of uint16_t
    //指styles起始地址,存放所该字符串池中所有的style
    const uint32_t*             mStyles;
    uint32_t                    mStylePoolSize;    // number of uint32_t
};

        在resources.arsc中,会存储mEntriesmEntryStylesmStringsmStyles所指向的区域:mEntries是一个字符串偏移数组,它的每一个元素表示一个字符串的其实地址相对于mStrings的偏移量,而mStrings的地址则等于mHeader->stringsStart,所以字符串池中第i个字符串的其实地址就是:字符串池地址 + mHeader->stringsStart + mEntries[i](假设字符编码是UTF-8)。在mString数组后面,会有一个mEntryStyles数组,它表示的是style的偏移量,作用和mEntries类似。再后面是就是mStrings、mStyles,他们存放字符串池中所有的string和style。

        我们总结一下,在resources.arsc中,一个字符串池包括header、string偏移数组、style偏移数组、mStrings、mStyles五个部分。另外,我们看到类型为RES_TABLE_PACKAGE_TYPE的chunk内部也还有两个字符串池,它们的结构和我们这里的一模一样,也就是说在一个resources.arsc中通常会有三个字符串池,最外面的那个叫做Global String Pool,也叫Value String Pool,类型为RES_TABLE_PACKAGE_TYPE的chunk内部的两个字符串池,一个叫做Type String Pool,一个叫做 Key String Pool。它们的不同在于:Global String Pool用来存储一个资源项的Value相关的信息,Type String Pool用来存储一个资源项的类型相关的信息,Key String Pool用来存储一个资源项的名字相关的信息。只是这么说可能有些抽象,举个例子就很清楚了:

//demo/res/values/strings.xml
<resources>
    <string name="settings">设置string>
resources>

        假设我们的App目录下有这么个字符串资源,我们编译以后生成的resources.arsc中,设置位于Global String Pool或者说Value String Pool;settings这个字符串位于Key String Pool;另外每种资源都是有类型的,这个字符串资源的类型为string,那么string就位于Type String Pool中。

demo/res/drawable-hdpi/app_bg.9.png
demo/res/drawable-ldpi/app_bg.9.png

        假设我们的项目在不同分辨率下,都有一张App的背景图片,那么编译后生成的resources.arsc中res/drawable-hdpi/app_bg.9.pngres/drawable-ldpi/app_bg.9.png这两个字符串(图片路径)位于Global String Pool或者说Value String Pool;app_bg(资源名称)这个字符串位于Key String Pool;drawable位于Type String Pool。

ResTable_package

        除了ResStringPool外,类型为RES_TABLE_TYPE的chunk的另外一个元素就是类型为RES_TABLE_PACKAGE_TYPE的chunk了,这个chunk在ResourceTypes.h中对应的数据结构为ResTable_package,和ResStringPool不同,从上面的结构图我们就可以看出来,ResTable_package内部是有子chunk的。

//frameworks/base/include/androidfw/ResourceTypes.h
struct ResTable_package
{
    struct ResChunk_header header;

    /**
    * 包的id,通常Android系统资源包framework-res.apk的id为0x01
    * App包的id为0x7f
    * 资源共享包的id为0x00
    */
    uint32_t id;

    // 包名
    uint16_t name[128];

    //Type String Pool的起始位置
    uint32_t typeStrings;

    //public资源相关,暂时未探究其用处
    uint32_t lastPublicType;

    //Key String Pool的起始位置
    uint32_t keyStrings;

    //public资源相关,暂时未探究其用处
    uint32_t lastPublicKey;
    //暂时未探究其用处
    uint32_t typeIdOffset;
};

        ResTable_package这个数据结构更像是一个header,它的类容是以子chunk的形式呈现的,如结构图中所示,它的子chunk为一系列的类型为RES_TABLE_TYPE_SPEC_TYPE的chunk,外加一个类型为RES_TABLE_LIBRARY_TYPE的chunk。

ResTable_typeSpec

        ResTable_typeSpec用来描述某一类型的资源,一般一个ResTable_package中会有多个ResTable_typeSpec,在resources.arsc中每个ResTable_typeSpec后面会紧跟一个config change flags数组,再后面会跟N个ResTable_type

//frameworks/base/include/androidfw/ResourceTypes.h
struct ResTable_typeSpec
{
    struct ResChunk_header header;

    // The type identifier this chunk is holding.  Type IDs start
    // at 1 (corresponding to the value of the type bits in a
    // resource identifier).  0 is invalid.
    uint8_t id;
    
    // Must be 0.
    uint8_t res0;
    // Must be 0.
    uint16_t res1;
    
    // 该类型的资源项的个数.
    uint32_t entryCount;

    enum {
        // Additional flag indicating an entry is public.
        SPEC_PUBLIC = 0x40000000
    };
};

ResTable_type

        我们前面讲到一个ResTable_typeSpec后面除了跟一个config change flags数组外,还会跟N个ResTable_type。按照一般的理解,它后面应该跟entryCount个具体的资源项才对,为什么会是N个ResTable_type呢?这就说到Android的资源组织形式了,我们知道,即使是同一类型的资源,我们在开发的时候,也会根据不同的配置,建立不同的目录来存放资源比如:

res/drawable-ldpi/
-----app_bg.9.png
-----app_icon.9.png

res/drawable-hdpi/
-----app_bg.9.png
-----app_icon.9.png

res/drawable-mdpi/
-----app_bg.9.png
-----app_icon.9.png

        我们为app_bg和app_icon这两个资源创建了drawable-ldpi、drawable-mdpi、drawable-hdpi三个目录。同样,在resources.arsc中,android会为每一种资源配置创建一个ResTable_type,还以这个为例的话,resources.arsc中类型为drawable的资源会有三个ResTable_type分别对应三种不同的屏幕密度。

//frameworks/base/include/androidfw/ResourceTypes.h
struct ResTable_type
{
    struct ResChunk_header header;

    enum {
        NO_ENTRY = 0xFFFFFFFF
    };
    
    // The type identifier this chunk is holding.  Type IDs start
    // at 1 (corresponding to the value of the type bits in a
    // resource identifier).  0 is invalid.
    uint8_t id;
    
    // Must be 0.
    uint8_t res0;
    // Must be 0.
    uint16_t res1;
    
    // 该ResTable_type中资源项的个数.
    uint32_t entryCount;

    // 资源项偏移量数组相对与该ResTable_type的偏移量.
    uint32_t entriesStart;
    
    // 对应的配置信息.
    ResTable_config config;
};

        我们看到ResTable_type中有一个ResTable_config字段,也就是说,同一类型的资源在resources.arsc中是按照不同的config来组织的,一个config对应一个ResTable_type,所以一个ResTable_typeSpec后面可能跟多个ResTable_type。另外,一个ResTable_type后面会跟一个资源项偏移量数组。这个数组的第i项表示第i个资源项相对于资源项起始地址的偏移量,也就是说第i个资源项的地址= 该ResTable_type的地址 + entriesStart +偏移数组[i]。当然,entriesStart数组的元素个数为entryCount

ResTable_entry和Res_value

        一个ResTable_type后面会跟一个偏移量数组,偏移量数组后面就是一个一个的资源项了,这个资源项用ResTable_entry + Res_value来表示(先不考虑Bag资源)。ResTable_entry表示一个资源项的名称相关的信息,Res_value`则表示一个资源值相关的信息,这样在resources.arsc中,它们就组成了一个个的键值对儿,来表示一个资源的完整信息。

//frameworks/base/include/androidfw/ResourceTypes.h
struct ResTable_entry
{
    // 很明显 8个字节
    uint16_t size;

    enum {
        //处理Bag资源时用到,先不考虑这个,我们后面会专门讲
        FLAG_COMPLEX = 0x0001,
        // If set, this resource has been declared public, so libraries
        // are allowed to reference it.
        FLAG_PUBLIC = 0x0002,
        // If set, this is a weak resource and may be overriden by strong
        // resources of the same name/type. This is only useful during
        // linking with other resource tables.
        FLAG_WEAK = 0x0004
    };
    uint16_t flags;
    
    //它表示资源项的名称字符串在 Key String Pool中的索引
    struct ResStringPool_ref key;
};


struct Res_value
{
    // 很明显 8个字节
    uint16_t size;

    // Always set to 0.
    uint8_t res0;

    //资源值的类型
    uint8_t dataType;
    
    //资源的值
    typedef uint32_t data_type;
    data_type data;
}

        ResTable_entryRes_value的代码很容易理解。需要说明的是,Res_valuedataType字段,也就是资源的值的类型,它可以是:

    enum {
        // The 'data' is either 0 or 1, specifying this resource is either
        // undefined or empty, respectively.
        TYPE_NULL = 0x00,
        // The 'data' holds a ResTable_ref, a reference to another resource
        // table entry.
        TYPE_REFERENCE = 0x01,
        // The 'data' holds an attribute resource identifier.
        TYPE_ATTRIBUTE = 0x02,
        // The 'data' holds an index into the containing resource table's
        // global value string pool.
        TYPE_STRING = 0x03,
        // The 'data' holds a single-precision floating point number.
        TYPE_FLOAT = 0x04,
        // The 'data' holds a complex number encoding a dimension value,
        // such as "100in".
        TYPE_DIMENSION = 0x05,
        // The 'data' holds a complex number encoding a fraction of a
        // container.
        TYPE_FRACTION = 0x06,
        // The 'data' holds a dynamic ResTable_ref, which needs to be
        // resolved before it can be used like a TYPE_REFERENCE.
        TYPE_DYNAMIC_REFERENCE = 0x07,
        // The 'data' holds an attribute resource identifier, which needs to be resolved
        // before it can be used like a TYPE_ATTRIBUTE.
        TYPE_DYNAMIC_ATTRIBUTE = 0x08,

        // Beginning of integer flavors...
        TYPE_FIRST_INT = 0x10,

        // The 'data' is a raw integer value of the form n..n.
        TYPE_INT_DEC = 0x10,
        // The 'data' is a raw integer value of the form 0xn..n.
        TYPE_INT_HEX = 0x11,
        // The 'data' is either 0 or 1, for input "false" or "true" respectively.
        TYPE_INT_BOOLEAN = 0x12,

        // Beginning of color integer flavors...
        TYPE_FIRST_COLOR_INT = 0x1c,

        // The 'data' is a raw integer value of the form #aarrggbb.
        TYPE_INT_COLOR_ARGB8 = 0x1c,
        // The 'data' is a raw integer value of the form #rrggbb.
        TYPE_INT_COLOR_RGB8 = 0x1d,
        // The 'data' is a raw integer value of the form #argb.
        TYPE_INT_COLOR_ARGB4 = 0x1e,
        // The 'data' is a raw integer value of the form #rgb.
        TYPE_INT_COLOR_RGB4 = 0x1f,

        // ...end of integer flavors.
        TYPE_LAST_COLOR_INT = 0x1f,

        // ...end of integer flavors.
        TYPE_LAST_INT = 0x1f
    };

        我们看到,Res_value的类型可以是COLOR、INT、DIMENSION、STRING等等。其中TYPE_DYNAMIC_REFERENCE、TYPE_DYNAMIC_ATTRIBUTE、TYPE_REFERENCE需要特别说明一下:TYPE_DYNAMIC_REFERENCE和TYPE_DYNAMIC_ATTRIBUTE是资源共享库相关的数据类型;TYPE_REFERENCE表示这个Res_value是另外一个资源的引用,看下面的例子:

<resources>
    <color name="color_blue">#0000ffstring>
    <color name="color_sky">@color/color_bluestring>
resources>

        在上面的xml中color_blue对应的Res_value的dataType是TYPE_INT_COLOR_RGB8,它是一个具体的RGB颜色值;而color_sky对应的Res_value的dataType是TYPE_REFERENCE,它的值将会是color_blue的id值(也就是形如0x7f030002的id值)。我们要获得其最终的值,还需要系统给我们进一步解析这个引用。

ResTable_lib_header和ResTable_lib_entry

struct ResTable_lib_header
{
    struct ResChunk_header header;

    // The number of shared libraries linked in this resource table.
    uint32_t count;
};

struct ResTable_lib_entry
{
    // The package-id this shared library was assigned at build time.
    // We use a uint32 to keep the structure aligned on a uint32 boundary.
    uint32_t packageId;

    // The package name of the shared library. \0 terminated.
    uint16_t packageName[128];
};

        ResTable_lib_headerResTable_lib_entry也是资源共享库相关的数据类型,当这两个数据结构出现在一个ResTable_package中时,则说明这个ResTable_package有使用资源共享库。一个ResTable_lib_header后面会跟countResTable_lib_entry,每个ResTable_lib_entry都代表一个资源共享库。

        到这里resources.arsc相关的主要数据结构我们已经说得差不多了。

你可能感兴趣的:(#,AssetManager)