上一篇我们介绍了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的数据结构。另外,我们能可能地标明了每个字段所占用的空间,单位为字节。下面我们对这些数据结构一一做出说明:
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的内容,非常方便。并且这样非常利于相对地址的使用,非常灵活,独立性更强。
从结构图上我们可以看到,一个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中,会存储mEntries
、mEntryStyles
、mStrings
、mStyles
所指向的区域: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.png
和res/drawable-ldpi/app_bg.9.png
这两个字符串(图片路径)位于Global String Pool或者说Value String Pool;app_bg
(资源名称)这个字符串位于Key String Pool;drawable
位于Type String Pool。
除了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_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_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_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_entry
和 Res_value
的代码很容易理解。需要说明的是,Res_value
的dataType
字段,也就是资源的值的类型,它可以是:
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值)。我们要获得其最终的值,还需要系统给我们进一步解析这个引用。
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_header
和ResTable_lib_entry
也是资源共享库相关的数据类型,当这两个数据结构出现在一个ResTable_package
中时,则说明这个ResTable_package
有使用资源共享库。一个ResTable_lib_header
后面会跟count
个ResTable_lib_entry
,每个ResTable_lib_entry
都代表一个资源共享库。
到这里resources.arsc相关的主要数据结构我们已经说得差不多了。