上一篇我们介绍了resources.arsc以及与之相关的主要数据结构,这些数据结构大多以ResTable_开头,主要是用来描述resources.arsc非常方便。不过,要用这些数据结构来对Android资源进行管理,还是有些吃力,Android为了更加方便地管理资源,还有另外一套数据结构,它们主要是ResTable
和ResTable
的内部类。
与resources.arsc相关的数据结构当中,最主要的是ResTable_package
、ResTable_type
、ResTable_entry
这三级架构,也和资源id形式0xpptteeee(pp表示最高的一个字节表示包id,第二高的一个字节表示type id,最后两个字节表示entry id)相一致。但是,要对实现对这三级资源架构的管理,要依靠的绝对不仅仅是这三级。确切地说,Android资源管理中用到,但是resources.arsc中没有用到数据结构,主要是ResTable
、ResTable::PackageGroup
、ResTable::Package
、ResTable::TypeList
、ResTable::Type
、ResTable::Entry
。其中,ResTable::Package
、ResTable::Entry
和ResTable_package
、ResTable_entry
对应;ResTable::Type
和ResTable_type
的意义完全不一样;ResTable
、ResTable::PackageGroup
、ResTable::TypeList
是resources.arsc中没有类似的数据结构与之对应。我们这里再着重说一下,ResTable_
开头的数据结构偏重于对resources.arsc的描述,而ResTable::
系列的数据结构是用来对资源信息进行管理的,AssetManager中的许多接口功能的实现都要依赖它们。
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
对象中直接存放一个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包为准。
//frameworks/base/include/androidfw/ResourceTypes.h
typedef Vector<Type*> TypeList;
ResTable::TypeList
的定义非常简单。我们前面说过,访问资源时,通过ResTable::PackageGroup
,到ResTable::TypeList
,再到ResTable::Type
,再到ResTable::Entry
。我们看到,ResTable::PackageGroup
的types
成员是一个数组,这个数组中的每一个元素也就是ResTable::TypeList
代表一种特定类型的资源,比如drawable、string等。但是,ResTable::TypeList
本身又是一个ResTable::Type
类型的数组,这个我们又该怎么理解呢?ResTable::TypeList
里的每一个ResTable::Type
对应一个不同的配置吗? No!实际情况是这样的:ResTable::TypeList
和ResTable::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中的资源组织到一起,访问也方便。
//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
表示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_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
中就可以拿到具体的结果了。
我们画一张图来描述相关的数据结构:
我们看到,这张图中少了Restable::Entry
,因为它可以看做是对Restable:_entry
的封装,它的存在是为了我们从上层更加方便地get资源。不过,它却是是游离于ResTable
、ResTable::PackageGroup
、ResTable::Package
、ResTable::TypeList
、ResTable::Type
之外的,它们都没用引用Restable::Entry
。
AssetManager
是native层资源管理的接口,它实际上是对ResTable的封装
,我们可以粗略地认为,AssetManager
负责加载资源包,并从中解压出各种资源文件,特别是resources.arsc,然后将它丢给ResTable
,后面的事儿就由ResTable
负责了。AssetManager
在管理资源包的过程中用到的AssetDir
、Asset
、_FileAsset
、_CompressedAsset
等数据结构,我们可以看做是对一般目录、文件、压缩包的封装;AssetManager::SharedZip
、AssetManager::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_config
、ResTable_ref
、ResStringPool等,在描述resources.arsc和管理资源时都会用到。
我们前面讲资源项的时候说到,一般情况下,一个资源项其实是一个键值对儿:键表示资源项的名字,用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_entry
和ResTable_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::count
个ResTable_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资源就可以了。