文章中所使用软件和代码资源
示例apk
示例代码
binary view二进制文件查看工具:
android 6.0系统源码(网上搜索下载,这里暂不提供资源)
chunk
整个文件是由一系列的chunk构成的,算是整个文件划分的基本单位吧,实际上就是把整个文件无差别的划分成多个模块,每个模块就是一个chunk,结构更加清晰。每个chunk是最前面是一个ResChunk_header的结构体,描述这个chunk的信息。
所有的chunk的header定义在android-6.0.0_r1\frameworks\base\include\androidfw\ResourceTypes.h
/** ********************************************************************
* Base Types
*
* These are standard types that are shared between multiple specific
* resource types.
*
*********************************************************************** */
/**
* Header that appears at the front of every data chunk in a resource.
*/
struct ResChunk_header
{
// Type identifier for this chunk. The meaning of this value depends
// on the containing chunk.
uint16_t type;
// Size of the chunk header (in bytes). Adding this value to
// the address of the chunk allows you to find its associated data
// (if any).
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.
uint32_t size;
};
enum {
RES_NULL_TYPE = 0x0000,
RES_STRING_POOL_TYPE = 0x0001,
RES_TABLE_TYPE = 0x0002,
RES_XML_TYPE = 0x0003,
// Chunk types in RES_XML_TYPE
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,
// Chunk types in RES_TABLE_TYPE
RES_TABLE_PACKAGE_TYPE = 0x0200,
RES_TABLE_TYPE_TYPE = 0x0201,
RES_TABLE_TYPE_SPEC_TYPE = 0x0202,
RES_TABLE_LIBRARY_TYPE = 0x0203
};
如注释解释,每个chunk的头都会包含ResChunk_header,它是一个基础类型。上面的枚举定义了chunk的类型。ResChunk_header的成员变量解释:
- type 定义chunk的类型,与上面定义的枚举中的值对应。
- headerSize 定义每个chunk头的大小。
- size 定义每个chunk数据块的大小。
文件header(ResTable_header)
resources.arsc整个文件内容也是一个chunk,是ResTable。头部为ResTable_header。我们看到定义
/** ********************************************************************
* RESOURCE TABLE
*
*********************************************************************** */
/**
* Header for a resource table. Its data contains a series of
* additional chunks:
* * A ResStringPool_header containing all table values. This string pool
* contains all of the string values in the entire resource table (not
* the names of entries or type identifiers however).
* * One or more ResTable_package chunks.
*
* Specific entries within a resource table can be uniquely identified
* with a single integer as defined by the ResTable_ref structure.
*/
struct ResTable_header
{
struct ResChunk_header header;
// The number of ResTable_package structures.
uint32_t packageCount;
};
这是整个资源表的头,资源中的chunk也是按照一定的顺序进行存储。
- 字符串资源表,包含了所有的表的值。这个字符串池包含整个资源中表中的字符串。如:名称,类型等等。
- 一个或多个资源包chunk
我们先查看resources.arsc文件的开头:
从ResTable_header的结构体定义我们进行解析:
- 02 00 前两个字节是 type。chunk的类型,根据上面的枚举定义
RES_TABLE_TYPE = 0x0002
知道这是一个资源表的chunk. - 0C 00 这两个字节存储的是headerSize的值,也就是chunk head的大小。我们看到ResTable_header内是一个ResChunk_header和packageCount,计算得到是12个字节,正好想对应。
- 78 74 03 00 这四个字节存储的是
uint32_t size;
真个chunk的大小。转化为十进制等于 226424。这是第一个chunk也是最外层的chunk。所以它的大小是整个文件。我们查看一下resources.arsc的大小。
结果是相符的。 - 01 00 这个存储的是packageCount。包的数量为1。
这里的二进制的存储是小端对齐。如果不懂的可以google补习一下。对uint32_t、uint16_t不太了解的也可以搜一下。这里我们就不跑题了。
到这里第一个chunk,也就是最外层的chunk的头我们已经解析完成了。
全局字符串池
紧接着是Global String Pool,全局字符串池,这也是Resources.arsc存在最重要的一个原因之一,就是把所有字符串放到这个池子里,大家复用这些字符串,可以很大的减小APK包的尺寸。
/**
* Definition for a pool of strings. The data of this chunk is an
* array of uint32_t providing indices into the pool, relative to
* stringsStart. At stringsStart are all of the UTF-16 strings
* concatenated together; each starts with a uint16_t of the string's
* length and each ends with a 0x0000 terminator. If a string is >
* 32767 characters, the high bit of the length is set meaning to take
* those 15 bits as a high word and it will be followed by another
* uint16_t containing the low word.
*
* If styleCount is not zero, then immediately following the array of
* uint32_t indices into the string table is another array of indices
* into a style table starting at stylesStart. Each entry in the
* style table is an array of ResStringPool_span structures.
*/
struct ResStringPool_header
{
struct ResChunk_header header;
// Number of strings in this pool (number of uint32_t indices that follow
// in the data).
uint32_t stringCount;
// Number of style span arrays in the pool (number of uint32_t indices
// follow the string indices).
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;
// Index from header of the string data.
uint32_t stringsStart;
// Index from header of the style data.
uint32_t stylesStart;
};
同样全局池的head里有一个ResChunk_header。我们继续解析resources.arsc文件
-
01 00
type=1则为RES_STRING_POOL_TYPE = 0x0001
-
1C 00
headerSize=28 -
3C E8 00 00
size=59452 -
A8 06 00 00
stringCount=1704 等于字符串的数量。 -
00 00 00 00
styleCount=0 字符串的样式的数量。 -
00 01 00 00
flags =0x00000100==1<<8 。UTF-8编码,枚举中有定义
等于0、SORTED_FLAG、UTF8_FLAG或者它们的组合值,用来描述字符串资源串的属性,例如,SORTED_FLAG位等于1表示字符串是经过排序的,而UTF8_FLAG位等于1表示字符串是使用UTF8编码的,否则就是UTF16编码的。// String pool is encoded in UTF-8 UTF8_FLAG = 1<<8
-
BC 1A 00 00
stringsStart=6844 等于字符串内容块相对于其头部的距离 -
00 00 00 00
stylesStart=0 等于字符串样式块相对于其头部的距离。
除了ResStringPool_header头部、字符串内容块和字符串样式内容块之外,还有两个偏移数组,分别是字符串偏移数组和字符串样式偏移数组,这两个偏移数组的大小就分别等于字符串的数量stringCount和styleCount的值,而每一个元素都是一个无符号整数。整个字符中资源池的组成就如图13所示:
上面我们解析到stringCount=1704 ,1704*4=6816 而6816+28=6844正好等于stringsStart。
我们解析第一个字符串。在String Offset Array的前四个字节为0。所以它是从String Content的起始位置开始存储。由前面计算知道String content相对head为6844。则相对绝对地址为6844+12=6856=0x1AC8。上面截图显示。
无论是UTF8,还是UTF16的字符串编码,每一个字符串的前面都有2个字节表示其长度,而且后面以一个NULL字符结束。对于UTF8编码的字符串来说,NULL字符使用一个字节的0x00来表示,而对于UTF16编码的字符串来说,NULL字符使用两个字节的0x0000来表示。
的到的结果就是res/anim/abc_fade_out.xml。这个我们用到的appcompat兼容库中包含这个文件。
同样的方法再往后解析就是res/drawable/abc_list_selector_background_transition_holo_dark.xml,这个同样在兼容库中。
后面的解析以此类推,我就不做过多的介绍了
Package解析
/**
* A collection of resource data types within a package. Followed by
* one or more ResTable_type and ResTable_typeSpec structures containing the
* entry values for each resource type.
*/
struct ResTable_package
{
struct ResChunk_header header;
// If this is a base package, its ID. Package IDs start
// at 1 (corresponding to the value of the package bits in a
// resource identifier). 0 means this is not a base package.
uint32_t id;
// Actual name of this package, \0-terminated.
uint16_t name[128];
// Offset to a ResStringPool_header defining the resource
// type symbol table. If zero, this package is inheriting from
// another base package (overriding specific values in it).
uint32_t typeStrings;
// Last index into typeStrings that is for public use by others.
uint32_t lastPublicType;
// Offset to a ResStringPool_header defining the resource
// key symbol table. If zero, this package is inheriting from
// another base package (overriding specific values in it).
uint32_t keyStrings;
// Last index into keyStrings that is for public use by others.
uint32_t lastPublicKey;
uint32_t typeIdOffset;
};
前面我们计算知道ResTable_header的大小为12,紧接着后面就是全局字符串chunk。大小为 59452。所以package前有59452+12=59464=0xE848。我们跳转到resources.arsc的这个地址处。
-
00 02
type=0x0200,RES_TABLE_PACKAGE_TYPE = 0x0200
-
120 01
head_size=288 12+4+128*2+4+4+4+4+4=288。 -
30 8C 02 00
size=166960。我们看到166960+59452=226412正好是等于文件的大小 -
7F 00 00 00
是id - 后面128*2个字节就是包名,每个字符占用两个字节,从图中我们可以看到包名 com.wangheart.resdemo
-
00 00 00 00
typeStrings 类型字符串资源池相对头部的偏移位置。 -
00 00 00 00
lastPublicType 等于最后一个导出的Public类型字符串在类型字符串资源池中的索引,目前这个值设置为类型字符串资源池的大小。 -
20 01 00 00
keyStrings。资源项名称字符串池keyStrings相对于package header起始位置的偏移是0x0120 -
0C 00 00 00
lastPublicKey -
D0 01 00 00
typeIdOffset