aapt工具在编译资源会将一些资源或者资源索引打包成resources.arsc。这个文件以二进制数据的形式记录数据,c/c++加载起来特别方便。
了解resources.arsc的结构对理解安卓的资源加载原理有很重要的帮助。
这几天写resources.arsc解析工具时候在网上搜到了不少的资料、博客,但是它们写的都不是特别的详细,都会漏掉一些东西没有提。导致在实现的时候遇到了很多的坑。这里我希望尽量把自己总结出来的东西一步步都列出来,尽量做到只需要看这篇博客就能自己实现一个resources.arsc的解析器。
这个工具已经在github上开源(使用C++11,已经在mac和ubuntu上报make编译通过正常运行,Windows的同学就只好说声抱歉了),感兴趣的同学也可以直接下载下来玩玩。
总体结构
resources.arsc是以一个个Chunk块的形式组织的,Chunk的头部信息记录了这个Chunk的类型、长度等数据。
从整体上来看,其结构为:资源索引表头部+字符串资源池+N个Package数据块:
1.png
头部数据解析
整个resources.arsc就是一个Chunk块,所以文件的开头就是这个Chunk的头部信息.不过需要注意的是resources.arsc文件采用小端编码方式.所以数据应该按字节从后往前读。
头部的结构如下:
头部类型(两个字节),头部大小(两个字节),Chunk块大小(四个字节)
我们找一个apk文件,将它后缀改成.zip直接解压,就可以得到resources.arsc,这里实际举个例子,用编辑工具查看到resources.arsc的前8个字节的数据,这里以16进制显示:
0200 0c00 acb6 0300
首先头部类型的两个字节是0200(每两个16进制数字代表了一个字节),但是这里是小端序,所以要从后往前读,得到实际的值0x0002。
02 00 -> 00 02
resources.arsc内部记录的数据类型在ResourceTypes.h里面定义,我们找到头部类型的枚举,可以查到0x0002对应的类型是RES_TABLE_TYPE。返回去看上面的图,可以看到类型的确是RES_TABLE_TYPE。
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
};
接着是头部大小的两个字节0c00(每两个16进制数字代表了一个字节),从后往前读得到实际值0x000c,也就是说这个头部大小有12个字节
0c 00 -> 00 0c
再接着的是Chunk块大小的四个字节acb6 0300(每两个16进制数字代表了一个字节),从后往前读得到实际值0x0003b6ac,转换回十进制是243372,也就是说resources.arsc文件的大小是243372字节。
ac b6 03 00 -> 00 03 b6 ac
我们可以用wc命令查看这个文件的大小:
wc resources.arsc
277 2932 243372 resources.arsc
resources.arsc文件这样组织的原因是可以很方便的使用c/c++去加载。
我们在源码里面找到Chunk头部信息的ResChunk_header结构体:
/**
* 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.
<