Android资源管理框架-------之资源管理的基本数据结构和Bag资源(四)

资源管理的基本数据结构

        上一篇我们介绍了resources.arsc以及与之相关的主要数据结构,这些数据结构大多以ResTable_开头,主要是用来描述resources.arsc非常方便。不过,要用这些数据结构来对Android资源进行管理,还是有些吃力,Android为了更加方便地管理资源,还有另外一套数据结构,它们主要是ResTableResTable的内部类。

        与resources.arsc相关的数据结构当中,最主要的是ResTable_packageResTable_typeResTable_entry这三级架构,也和资源id形式0xpptteeee(pp表示最高的一个字节表示包id,第二高的一个字节表示type id,最后两个字节表示entry id)相一致。但是,要对实现对这三级资源架构的管理,要依靠的绝对不仅仅是这三级。确切地说,Android资源管理中用到,但是resources.arsc中没有用到数据结构,主要是ResTableResTable::PackageGroupResTable::PackageResTable::TypeListResTable::TypeResTable::Entry。其中,ResTable::PackageResTable::EntryResTable_packageResTable_entry对应;ResTable::TypeResTable_type的意义完全不一样;ResTableResTable::PackageGroupResTable::TypeList是resources.arsc中没有类似的数据结构与之对应。我们这里再着重说一下,ResTable_开头的数据结构偏重于对resources.arsc的描述,而ResTable::系列的数据结构是用来对资源信息进行管理的,AssetManager中的许多接口功能的实现都要依赖它们。

ResTable

        Android资源管理框架-------之Android中的资源包(二)中,我们知道一个AssetManager可能会加载许多资源包,包括Android系统资源包也就是framework-res.apk、Soc厂商资源包、手机厂商资源包以及App本身,甚至还有可能存在overlay包、资源共享库等等。那么这些资源包中的resources.arsc加载后被存到了哪里呢?最终它们都会被存放到一个ResTable的对象中,并且这个对象是native层AssetManager的一个成员。也就是说,一个ResTable对象会存储它所在的AssetManager加载的所有资源包中的resources.arsc,并且对这些resources.arsc做统一的管理,这一点我们一定要注意。

//frameworks/base/include/androidfw/ResourceTypes.h
class ResTable
{
    //......省略无关代码
    
    private:
    /**
    * 当前设备配置信息,我们去获取资源时,要根据它来选择合适的资源
    * 我们给AssetManager设置或者更新配置信息时,最终也会存在这里
    */
    ResTable_config             mParams;
    /**
    * 存放我们加载的所有资源包中的resources.arsc
    * 当然它不会把整个resources.arsc都放进来,只是放入了每个resources.arsc的header信息
    * 这样通过header我们可以访问整个resources.arsc
    */
    Vector<Header*>             mHeaders;
    
    //我们加载过来的资源包相关的信息都会放到这个数据结构中
    Vector<PackageGroup*>       mPackageGroups;
    /**
    * 这是一个Map,key为下标,具体为加载的package的id,
    * value为,这个package在mPackageGroups中的索引 + 1
    */
    uint8_t                     mPackageMap[256];
    
    //分配给下一个资源共享库的临时PackageId
    uint8_t                     mNextPackageId;
}

        我们看到一个内部,存储的关键信息就三个:mParams存储配置信息、mHeaders存储加载的resources.arsc、mPackageGroups存储加载的资源包相关的信息。如果不考虑Runtime Resources Overlay,一个id为packageId的资源包的信息为mPackageGroups[mPackageMap[packageId] - 1]。另外,在resources.arsc中,资源共享库的packageId都是0,所以加载的时候要为它们动态分配一个id,mNextPackageId就是做这个用的。

ResTable::PackageGroup

        按照一般的逻辑,一个ResTable对象中直接存放一个ResTable::Package的集合就可以了,为什么我们看到的却是存放了一个ResTable::PackageGroup的集合呢?并且从ResTable::PackageGroup的实现来看它的内部确确实实又是存在多个ResTable::Package的,这个怎么理解呢?

//frameworks/base/lib/androidfw/ResourceTypes.cpp
struct ResTable::PackageGroup
{

    //......省略无关代码

    //这个PackageGroup所属的ResTable
    const ResTable* const           owner;

    /**
    * 这个PackageGroup的name,为这个PackageGroup中第一个Package的name
    */
    String16 const                  name;
    uint32_t const                  id;

    //这个PackageGroup内的所有Package
    Vector<Package*>                packages;
    /**
    * 每一个元素都代表一种类型的资源
    * 当我们访问资源时,是通过PackageGroup到TypeList,再到Type,再到Entry
    * 中间并不会经过上面的packages变量
    */
    ByteBucketArray<TypeList>       types;

    //最大typeId
    uint8_t                         largestTypeId;

    //这个PackageGroup所有的bag资源,这个后面会单讲,现在可以先无视之
    ByteBucketArray<bag_set**>*     bags;

    //资源共享库相关的数据结构
    DynamicRefTable                 dynamicRefTable;
}

        其实,ResTable::PackageGroup的存在,是为了Runtime Resources Overlay(简称RRO),一般情况下一个ResTable::PackageGroup里只会有一个ResTable::Package,包括我们的应用包(id:0x7f)、系统资源包(id:0x01)、Soc厂商资源包、手机厂商资源包以及资源共享库包等等。但是有RRO时,系统会把target包和overlay包放到同一个ResTable::PackageGroup中,以便获取资源时可以快速地定位overlay包中的资源。并且target包会被放到packages[0],overlay包则会放到后面,这个ResTable::PackageGroup的包名和id都会以target包为准。

ResTable::TypeList

//frameworks/base/include/androidfw/ResourceTypes.h

typedef Vector<Type*> TypeList;

        ResTable::TypeList的定义非常简单。我们前面说过,访问资源时,通过ResTable::PackageGroup,到ResTable::TypeList,再到ResTable::Type,再到ResTable::Entry。我们看到,ResTable::PackageGrouptypes成员是一个数组,这个数组中的每一个元素也就是ResTable::TypeList代表一种特定类型的资源,比如drawable、string等。但是,ResTable::TypeList本身又是一个ResTable::Type类型的数组,这个我们又该怎么理解呢?ResTable::TypeList里的每一个ResTable::Type对应一个不同的配置吗? No!实际情况是这样的:ResTable::TypeListResTable::PackageGroup类似,也是为了处理Runtime Resources Overlay。一般情况下,它的里面也是只有一个元素的,当有RRO的时候,我们可以通过下面的例子来理解:

//target package
 <resources>
     ......
    
     <string name="app_greeting">Goog Morningstring>
     <string name="app_appologize">Sorrystring>
     
      ......
resources>

// overlay package
<resources>
     <string name="app_greeting">Goog Nightstring>
resources>

        在这个例子中,我们试图通过overlay package来改变字符串app_greeting的值。那么,在加载完target package后,overlay package也会被加载到 target package所在的ResTable中,并且会和target package放到同一个ResTable::PackageGroup中。并且,overlay package中的所有string类型的资源都会被系统放到target package中的string类型的资源所在的ResTable::TypeList中。也就是说 overlay package和target package中相同类型的资源,会被放到同一个ResTable::TypeList中,只不过,target package中的资源会放在头一个元素的位置,overlay package的放到后面。这样做,可以很方便地把target package和overlay package中的资源组织到一起,访问也方便。

ResTable::Package

//frameworks/base/lib/androidfw/ResourceTypes.cpp
struct ResTable::Package
{
    //......省略无关代码

    //所属的ResTable
    const ResTable* const           owner;
    //对应的resources.arsc
    const Header* const             header;
    //对应的resources.arsc中的ResTable_package
    const ResTable_package* const   package;
    // Type String Pool
    ResStringPool                   typeStrings;
    // Key String Pool
    ResStringPool                   keyStrings;
    //......省略无关代码
}

        通常一个ResTable::Package就会对应一个resources.arsc了。并且它里面还会记录这个resources.arsc中关键的数据块,比如 ResTable_package、Type String Pool、Key String Pool等。

ResTable::Type

        ResTable::Type的意义和ResTable_type完全不同。我们前文讲过ResTable_type表示resources.arsc中某种配置下的某类型的所有资源,而ResTable::Type则表示一个包中某种类型的所有资源。注意,是某种类型的所有资源,包括所有配置!其实从这个角度来讲,它倒是更像ResTable_typeSpec,我们看看ResTable::Type的实现:

//frameworks/base/lib/androidfw/ResourceTypes.cpp

struct ResTable::Type
{
    Type(const Header* _header, const Package* _package, size_t count)
        : header(_header), package(_package), entryCount(count),
          typeSpec(NULL), typeSpecFlags(NULL) { }
    //所属的resources.arsc
    const Header* const             header;
    //所属的资源包
    const Package* const            package;
    //资源项的个数
    const size_t                    entryCount;
    //资源对应的ResTable_typeSpec
    const ResTable_typeSpec*        typeSpec;
    //flags
    const uint32_t*                 typeSpecFlags;
    //RRO相关的数据结构
    IdmapEntries                    idmapEntries;
    //表示不同配置下的所有资源了
    Vector<const ResTable_type*>    configs;
};

        我们看到,一个ResTable::Type中包含多个ResTable_type,并且对于这个ResTable_type集合对象的命名也叫configs。自然,它里面的每一个元素就对应一种配置了。

ResTable::Entry

        ResTable::EntryResTable_entry的区别倒不大,只不过是对后者又做了进一步的封装,添加了它所属的类型、Package等信息。

//frameworks/base/lib/androidfw/ResourceTypes.cpp

struct ResTable::Entry {
    //所属ResTable_type的配置
    ResTable_config config;
    //对应的ResTable_entry
    const ResTable_entry* entry;
    //所属的ResTable_type
    const ResTable_type* type;
    uint32_t specFlags;
    //所属的Package
    const Package* package;
 
    //所属的类型的名字在Type String Pool中的索引 
    StringPoolRef typeStr;
    //该资源项的名字在Key String Pool中的索引
    StringPoolRef keyStr;
};

        另外,还有几个数据结构也值得一说,先看ResTable::Header

//frameworks/base/lib/androidfw/ResourceTypes.cpp

struct ResTable::Header
{
    Header(ResTable* _owner) : owner(_owner), ownedData(NULL), header(NULL),
        resourceIDMap(NULL), resourceIDMapSize(0) { }

    ~Header()
    {
        free(resourceIDMap);
    }
    //所属的ResTable对象
    const ResTable* const           owner;
    //用来存储整个resources.arsc,默认情况下这个是NULL,不会使用
    void*                           ownedData;
    //用来存储整个resources.arsc的头部
    const ResTable_header*          header;
    //整个resources.arsc中数据的大小
    //size = header->size
    size_t                          size;
    //整个resources.arsc的结尾地址
    const uint8_t*                  dataEnd;
    /**
    * 该resources.arsc或者说该资源包在owner对象中的索引
    * 或者说是该header对象在ResTable::mHeaders中的索引
    * 都是一会事儿
    */
    size_t                          index;
    /**
    * 该资源包是owner对象加载的第几个资源包,从1开始
    * 一般cookie = index + 1
    */
    int32_t                         cookie;
    //resources.arsc的Key String Pool
    ResStringPool                   values;
    //资源共享库相关
    uint32_t*                       resourceIDMap;
    //资源共享库相关
    size_t                          resourceIDMapSize;
};

        我们看到ResTable::Header内部的数据结构,大都是resources.arsc相关的,也就是说,它主要用来在资源管理的过程中来描述一个resources.arsc,而通常一个资源包对应一个resources.arsc,所以也可以理解为它和一个资源包是一一对应的。这里的index成员好理解,cookie我们简单说明以下。在Android的资源管理框架中cookie的出现频率极高,不过它们的含义基本一致,都是代表一个资源包或者说一个resources.arsc或者说一个Key String Pool(它们三个是一一对应的)在它所属的ResTable中的索引值(从1开始计数)。毕竟一个AssetManager也好,一个ResTable也好,它的内部都会存在多个资源包,当我们返回一个资源信息时,少不了要返回这个资源所属的资源包,这个就是cookie值了。

        再看下面两个数据结构:

//frameworks/base/include/androidfw/ResourceTypes.h
struct ResTable_ref
{
    uint32_t ident;
};

struct ResStringPool_ref
{
    uint32_t index;
};

        首先这两个数据结构不仅在进行资源管理的时候会用到,在resources.arsc中也会用到。其中ResTable_ref,表示一项资源的引用,32位整型值就是形如0xpptteeee的一个id,跟R文件中的id一模一样,也就是说,它实际上指向了另一个资源的id。从这里我们也可以看到,android的资源管理是支持资源引用的,这在我们使用资源的时候将会非常非常方便,但缺点是,当我们获取资源时,如果结果的类型是TYPE_REFERENCE,我们获取到的将不会是最终的结果,如果想得到最终的具体值,则需要增加对引用的解析这个步骤。而ResStringPool_ref则是一个字符串在ResStringPool中的索引。通常当我们要访问的资源是一个字符串的时候,Android资源管理框架的native层不会直接返回一个字符串,而是返回一个ResStringPool_ref和一个cookie值,前者表示索引,后者表示结果字符串哪个ResStringPool中,然后我们根据索引到对应的ResStringPool中就可以拿到具体的结果了。

        我们画一张图来描述相关的数据结构:

Android资源管理框架-------之资源管理的基本数据结构和Bag资源(四)_第1张图片
        我们看到,这张图中少了Restable::Entry,因为它可以看做是对Restable:_entry的封装,它的存在是为了我们从上层更加方便地get资源。不过,它却是是游离于ResTableResTable::PackageGroupResTable::PackageResTable::TypeListResTable::Type之外的,它们都没用引用Restable::Entry

AssetManager

        AssetManager是native层资源管理的接口,它实际上是对ResTable的封装,我们可以粗略地认为,AssetManager负责加载资源包,并从中解压出各种资源文件,特别是resources.arsc,然后将它丢给ResTable,后面的事儿就由ResTable负责了。AssetManager在管理资源包的过程中用到的AssetDirAsset_FileAsset_CompressedAsset等数据结构,我们可以看做是对一般目录、文件、压缩包的封装;AssetManager::SharedZipAssetManager::ZipSet,则是为了方便我们对APK包的访问以及缓存。这些数据结构算是资源管理的工具吧,它们和资源管理本身关系不大,我们这里一句带过了。

//frameworks/base/include/androidfw/AssetManager.h

#ifdef __cplusplus
extern "C" {
#endif

struct AAssetManager { };

#ifdef __cplusplus
};
#endif

class AssetManager : public AAssetManager {

     //.....省略次要代码

     //加载的资源包的路径
     Vector<asset_path> mAssetPaths;
     //ResTable
     mutable ResTable* mResources;
     //配置信息
     ResTable_config* mConfig;
     
     //.....省略次要代码
}

        到这里,Android资源管理相关的底层的主要数据结构我们已经介绍的差不多了,当然Runtime Resources Overlay、资源共享库相关的数据结构我们没有介绍,如果大家有兴趣,点开连接可以详细了解它们的实现和原理。还有就是XML相关的数据结构,它们比较独立,和Android资源管理关系不是那么不可分割,有机会我们可以搞个专题。在这里我们注意区分开ResTable_ResTable::打头的资源即可:前者大多用来描述resources.arsc;后者用于资源管理的逻辑的实现。当然ResTable_configResTable_ref、ResStringPool等,在描述resources.arsc和管理资源时都会用到。

Bag资源

        我们前面讲资源项的时候说到,一般情况下,一个资源项其实是一个键值对儿:键表示资源项的名字,用ResTable_entry表示;值表示资源项的相关信息(具体值或者资源的路径等等),用Res_value表示。但是还有二般情况,有一些资源我们仅仅用一个键值对儿是描述不清的,比如style类型的资源:

<resources>

    .......
    
    <style name="WorkspaceIcon">
        "android:layout_width">match_parent
        "android:layout_height">match_parent
        "android:layout_gravity">center
        "android:gravity">center_horizontal
        "android:singleLine">true
        "android:ellipsize">end
        "android:textSize">@dimen/workspace_icon_text_size
        "android:textColor">@color/workspace_icon_text_color
        "android:shadowRadius">2.0
        "android:shadowColor">#B0000000
    style>

    <style name="WorkspaceIcon.Portrait">
        "android:drawablePadding">@dimen/app_icon_drawable_padding
        "android:paddingStart">4dp
        "android:paddingEnd">4dp
        "android:paddingTop">@dimen/app_icon_padding_top
        "android:paddingBottom">4dp
    style>

    ......

resources>

        我们看styleWorkspaceIcon.Portrait,抛开它的parent style WorkspaceIcon不谈,它的名称似乎可以用一个ResTable_entry来表示,但是它的值我们可以用一个Res_value表示吗?显然不能!!!

        这个style的值不是一个简单的值,而是由5个键值对儿组成:

 android:drawablePadding   --->  @dimen/app_icon_drawable_padding
 android:paddingStart   --->  4dp
 android:paddingEnd   --->  4dp
 android:paddingTop   ---> @dimen/app_icon_padding_top
 android:paddingBottom   ---> 4dp

        对于类似style的这种资源,既然不能使用ResTable_entry + Res _value来表示,那就只能使用新的数据结构了,也就是ResTable_map_entryResTable_map

//frameworks/base/include/androidfw/ResourceTypes.h
struct ResTable_map_entry : public ResTable_entry
{
   // Resource identifier of the parent mapping, or 0 if there is none.
   // This is always treated as a TYPE_DYNAMIC_REFERENCE.
   ResTable_ref parent;
   // Number of name/value pairs that follow for FLAG_COMPLEX.
   uint32_t count;
};

        我们看到ResTable_map_entry继承了ResTable_entry,并且添加了两个新的字段,分别来表示它的父style和item的个数。

 //frameworks/base/include/androidfw/ResourceTypes.h
struct ResTable_map
{
     //表示键值对儿中的键
     ResTable_ref name;

     //......省略次要代码
     
     //表示键值对儿中的值
     Res_value value;
}

        在一个resources.arsc中,一个ResTable_map_entry后面会跟ResTable_map_entry::countResTable_map。具体到这个例子当中:

ResTable_map_entry::size = 16		/*16个字节*/

//FLAG_COMPLEX表示这个结构体后面跟的不再是简单的Res_value,而是一个个的mapping
ResTable_map_entry::flags = FLAG_COMPLEX

//key 是我们这个style的名字,当然这里是WorkspaceIcon.Portrait在key string pool中的索引
ResTable_map_entry::key = "WorkspaceIcon.Portrait"在key string pool中的索引

//parent 当然是WorkspaceIcon这个style了
ResTable_map_entry::parent = WorkspaceIcon这个style的id

//后面跟的键值对儿的个数,也就是style的item的个数
ResTable_map_entry::count = 5

//后面会有5个ResTable_map,它们的值分别为:

/*ResTable_map::value的类型是Res_value, 我们为了方便,在这里只写出了其data字段,没有列出其data_type等其它字段*/
ResTable_map::name = android:drawablePadding的id        ResTable_map::value = dimen/app_icon_drawable_padding的id
ResTable_map::name = android:paddingStart的id        ResTable_map::value = 4dp
ResTable_map::name = android:paddingEnd的id        ResTable_map::value = 4dp
ResTable_map::name = android:paddingTop的id        ResTable_map::value = dimen/app_icon_padding_top的id
ResTable_map::name = android:paddingBottom的id        ResTable_map::value = 4dp

         以上的结构非常好理解,我们用一个一个的键值对儿来描述一项比较复杂的资源,这每一个键值对儿就叫该资源的一个Bag。也就是说,style的每一个item都是style的Bag。我们的style WorkspaceIcon.Portrait自身有5个Bag,它的parent style WorkspaceIcon有10个Bag,并且不存在覆盖的情况,所以style WorkspaceIcon.Portrait总共有15个Bag。

         当然,拥有Bag的资源不只style一种,除此之外,还有bag、plurals、array、string-array、integer-array、attr共7种。

         bag类型的资源只在aapt中有定义,无论是Android 系统源码还是一般应用当中都还没有发现使用,这里就不说了。
         array、string-array、integer-array这三种资源差不多可以看成一种。一个array同样会包含多个item,比如:

<array name="sim_colors">
    <item>@color/Teal_700item>
    <item>@color/Blue_700item>
    <item>@color/Indigo_700item>
    <item>@color/Purple_700item>
    <item>@color/Pink_700item>
    <item>@color/Red_700item>
array>

        大家有没有发现和style不一样的地方?style的每一个item都是一个键值对儿,但是array的每一个item都只有值,并没有键!!!array的item表面上确实没有键,但其实它是有的,那就是索引,也就是每一个item在array中的位置或者说顺序。并且aapt在编译array(包括string-array和integer-array)的时候,会同时记录其索引的,并且这个索引用^index_%d来表示,其中%d表示索引值,比如0、 1 、2…
        plurals大家应该也经常接触,它主要用来处理多语言环境下,字符串的单复数形式:

    <plurals name="copy_begin">
        <item quantity="one">Copying <xliff:g id="count" example="1">%1$dxliff:g> file.item>
        <item quantity="other">Copying <xliff:g id="count" example="3">%1$dxliff:g> files.item>
    plurals>

        比如这个,quantity的值自然就是key了,android中quantity的值总共可以取:^zero、 ^one、^two、^few、^many、^other六种。当然,不同的语言环境可能只用其中的某一些,比如英语,只有单复数,所以只用^one、^other两种,这个和具体的语言相关,这里就不多说了。

        attr我们大家都非常熟悉了,它也有Bag资源?一个形如的attr,一个ResTable_entry和一个Res_value似乎是够了的。我们不急,先来看看这个attr:

    <attr name="orientation">
        
        <enum name="horizontal" value="0" />
        
        <enum name="vertical" value="1" />
    attr>

        这个就是Android源生的orientation属性的定义,它是一个枚举类型,这时候一个ResTable_entry + 一个Res_value显然是不行的。其实,形如的attr,用一个ResTable_entry + 一个Res_value也是不行的,因为它的键值可以是这个属性的名字textAppearance,但是值呢?显然是某个资源的id,而不能是reference。事实上,format=“reference” 这一句,我们可以看作是对textAppearance这个attr的描述。当然,和也一样是对orientation这个attr的描述,表示它只的值只能是二者之一,而不能随意去取。aapt在编译的时候,会把format也作为一个Bag记录下来,表示这个attr的类型,并用^type来表示。我们再来看看另外完整版的ResTable_map

//frameworks/base/include/androidfw/ResourceTypes.h
struct ResTable_map
{
    ResTable_ref name;
    
    enum {
        ATTR_TYPE = Res_MAKEINTERNAL(0),
        ATTR_MIN = Res_MAKEINTERNAL(1),
        ATTR_MAX = Res_MAKEINTERNAL(2),
        ATTR_L10N = Res_MAKEINTERNAL(3),
        ATTR_OTHER = Res_MAKEINTERNAL(4),
        ATTR_ZERO = Res_MAKEINTERNAL(5),
        ATTR_ONE = Res_MAKEINTERNAL(6),
        ATTR_TWO = Res_MAKEINTERNAL(7),
        ATTR_FEW = Res_MAKEINTERNAL(8),
        ATTR_MANY = Res_MAKEINTERNAL(9)
    };
    
    enum {
        TYPE_ANY = 0x0000FFFF,
        TYPE_REFERENCE = 1<<0,
        TYPE_STRING = 1<<1,
        TYPE_INTEGER = 1<<2,
        TYPE_BOOLEAN = 1<<3,
        TYPE_COLOR = 1<<4,
        TYPE_FLOAT = 1<<5,
        TYPE_DIMENSION = 1<<6,
        TYPE_FRACTION = 1<<7,
        TYPE_ENUM = 1<<16,
        TYPE_FLAGS = 1<<17
    };

    enum {
        L10N_NOT_REQUIRED = 0,
        L10N_SUGGESTED    = 1
    };

    Res_value value;
};

        我们看到,它里面有三个枚举类型的数据,它们是做什么的呢?最后一个显然是本地化相关的,我们不做讨论。我们先说第一个,当我们的资源是attr的时候,它们可以作为ResTable_map::name的值,用来描述这个attr相关的元信息。比如,这个属性的类型、最大取值、最取小值等等,当ResTable_map::name的值为ATTR_TYPE的时候,Res_value::data的值该是什么呢?答案就是第二个enum中的值;当我们的资源是plurals的时候,它的Bag的key只能是^zero、 ^one、^two、^few、^many、^other,值就是item当中的字符串;当我们的资源是array(包括string-array和integer-array)的时候,它的Bag的key只能是^index_%d.

        Bag资源相关的东西我们就先介绍到这里,后面讲aapt的时候我们还会详细讲Bag资源的编译。这里我们只需要知道什么是Bag资源就可以了。

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