本篇解析resource.arsc文件,参考文章:Reference
项目源码:ApkParser
arsc文件的结构图,所有的resource资源类型都定义在AOSP的frameworks\base\include\androidfw\ResourceTypes.h
头文件中。
Resources.arsc文件格式是由一系列的chunk构成,每一个chunk均包含如下结构的ResChunk_header,用来描述这个chunk的基本信息:
字段名 | 含义 | 长度 |
---|---|---|
type | 当前这个chunk的类型 | 2字节 |
headerSize | 当前这个chunk的头部大小 | 2字节 |
size | 当前这个chunk的大小 | 4字节 |
字段名 | 含义 | 长度 |
---|---|---|
header | 标准的Chunk头部信息格式 | 8字节:0x0002 |
packageCount | 被编译的资源包的个数,Apk中可以包含多个资源包,默认就1个 | 4字节 |
紧跟着资源索引表头部的是资源项的值字符串资源池,这个字符串资源池包含了所有的在资源包里面所定义的资源项的值字符串,字符串资源池头部的结构如下:
字段名 | 含义 | 长度 |
---|---|---|
header | 标准的Chunk头部信息格式 | 8字节:0x0001 |
stringCount | 字符串的个数 | 4字节 |
styleCount | 字符串样式的个数 | 4字节 |
flags | 字符串的属性,可取值包括0x000(UTF-16),0x001(字符串经过排序)、0X100(UTF-8)和他们的组合值 | 4字节 |
stringsStart | 字符串内容块相对于当前Chunk头部的距离 | 4字节*stringCount |
stylesStart | 字符串样式块相对于当前Chunk头部的距离 | 4字节*styleCount |
stringOffsetArray | 每个字符串相对于stringsStart位置的偏移 | 4字节*stringCount |
styleOffsetArray | 每个style串相对于stylesStart位置的偏移 | 4字节*stylesCount |
strings | 字符串内容池 | 每个串的前2个字节标识字符串长度,utf8的字符串以0x00结尾,长度不包含结束符 |
styles | 样式串内容池 |
字符串的长度计算比较特殊:length = byte[1] & 0x7f
,并且长只包含有效字符的长度,不包含结束符0x00。
接着资源项的值字符串资源池后面的部分就是Package数据块,这个数据块记录编译包的元数据,头部结构如下:
字段名 | 含义 | 长度 |
---|---|---|
header | 标准的Chunk头部信息格式 | 8字节:0x0200 |
pkgId | 用户包的值Package Id为0X7F,系统资源包的Package Id为0X01 | 4字节 |
packageName | 包名 | 128*2字节 |
typeString | 类型字符串资源池 相对头部的偏移 | 4字节 |
lastPublicType | 最后一个导出的Public类型字符串在类型字符串资源池中的索引,目前这个值设置为类型字符串资源池的元素个数 | 4字节 |
keyStrings | 资源项名称字符串相对头部的偏移 | 4字节 |
lastPublicKey | 最后一个导出的Public资源项名称字符串在资源项名称字符串资源池中的索引,资源项名称字符串资源池的元素个数 | 4字节 |
PackageChunk数据块头部0x0120字节 (8+4+128*2+16+4 = 0120 最后4字节无用),数据块部分包含:
DataBlock | 含义 | |
---|---|---|
TypeStringPool | 类型字符串 资源池 | attr、drawable、mipmap、layout、anim string、dimen、style、bool、color、id、interger |
KeyStringPool | 资源项名称字符串 资源池 | |
ResTableTypeSpec | 类型规范数据块 | RES_TABLE_TYPE_SPEC_TYPE |
ResTableTypeInfo | 类型资源项数据块 | RES_TABLE_TYPE_TYPE |
类型规范数据块用来描述资源项的配置差异性。通过这个差异性描述,我们就可以知道每一个资源项的配置状况。知道了一个资源项的配置状况之后,Android资源管理框架在检测到设备的配置信息发生变化之后,就可以知道是否需要重新加载该资源项。类型规范数据块是按照类型来组织的,也就是说,每一种类型都对应有一个类型规范数据块
。其数据块头部结构如下。
DataBlock | 含义 | |
---|---|---|
header | 标准的Chunk头部信息格式 | 8字节:0x0202 |
typeId | 标识资源的Type ID,Type ID是指资源的类型ID,资源的类型有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干种,每一种都会被赋予一个ID | 1字节 |
res0 | 保留,始终为0 | 1字节 |
res1 | 保留,始终为0 | 2字节 |
entryCount | 等于本类型的资源项个数,指名称相同的资源项的个数。 | 4字节 |
entryConfigs | 配置项数组 | entryConfigs * 4字节 |
类型资源项数据块用来描述资源项的具体信息, 这样我们就可以知道每一个资源项的名称
、值
和配置
等信息。类型资源项数据同样是按照类型和配置来组织的,也就是说,一个具有n个配置的类型一共对应有n个类型资源项数据块。其数据块头部结构如下
DataBlock | 含义 | |
---|---|---|
header | 标准的Chunk头部信息格式 | 8字节:0x0201 |
typeId | 标识资源的Type ID | 1字节 |
res0 | 保留,始终为0 | 1字节 |
res1 | 保留,始终为0 | 2字节 |
entryCount | 等于本类型的资源项个数,指名称相同的资源项的个数 | 4字节 |
entriesStart | 等于资源项数据块相对头部的偏移值 | 4字节 |
resConfig | 指向一个ResTable_config,用来描述配置信息,地区,语言,分辨率等,是union类型 | 大小由ResTableConfig#size字段指定,正常是56个字节 |
entryOffsets | 属于当前InfoChunk的Entry偏移数组 | entryCount * 4字节 |
tableEntries | 具体的Entry定义,根据ResTableEntry#flags区分类型:=FLAG_COMPLEX表示ResTableMapEntry,否则为ResTableValueEntry | 根据实际的类型确定 |
如果检查到 entryOffsets[i] == 0xffffffffL
表示这个位置没有entry,应该跳过继续读取下一个。这个地方还要明确一下 RES_TABLE_TYPE_SPEC_TYPE
和 RES_TABLE_TYPE_INFO_TYPE
这两种TypeChunk的关系,下面的表格是按文件流的顺序解析得到结果,可以清楚地说明问题:
SequenceId | ChunkType | TypeID | TypeName | EntryCount |
---|---|---|---|---|
0 | RES_TABLE_TYPE_SPEC_TYPE |
0x01 | attr | 228 |
1 | RES_TABLE_TYPE_INFO_TYPE | 0x01 | attr | 228 |
2 | RES_TABLE_TYPE_SPEC_TYPE |
0x02 | drawable | 95 |
3 | RES_TABLE_TYPE_INFO_TYPE | 0x02 | drawable | 95 |
4 | RES_TABLE_TYPE_SPEC_TYPE | 0x02 | drawable | 95 |
5 | RES_TABLE_TYPE_INFO_TYPE | 0x02 | drawable | 95 |
6 | RES_TABLE_TYPE_SPEC_TYPE | 0x02 | drawable | 95 |
7 | RES_TABLE_TYPE_INFO_TYPE | 0x02 | drawable | 95 |
8 | RES_TABLE_TYPE_SPEC_TYPE |
0x03 | mipmap | 1 |
9 | RES_TABLE_TYPE_INFO_TYPE | 0x03 | mipmap | 1 |
10 | RES_TABLE_TYPE_SPEC_TYPE | 0x03 | mipmap | 1 |
11 | RES_TABLE_TYPE_INFO_TYPE | 0x03 | mipmap | 1 |
12 | RES_TABLE_TYPE_SPEC_TYPE | 0x03 | mipmap | 1 |
13 | RES_TABLE_TYPE_INFO_TYPE | 0x03 | mipmap | 1 |
… | … | 0x04 0x05 0x06 0x07 0x08 0x09 0x0a 0x0b 0x0c |
layout anim string dimen style bool color id interger |
… |
RES_TABLE_TYPE_SPEC_TYPE
的数量和当前APP使用到的资源类型数相同(attr、drawable、mipmap、layout、anim、string、dimen、style、bool、color、id、interger、xml、raw、array、menu),每个RES_TABLE_TYPE_SPEC_TYPE
块之后都会跟着若干个RES_TABLE_TYPE_INFO_TYPE
块,而具体的数量实际上对应的就是这种类型的资源有几种配置。
最后再看一下最后一列的EntryCount
,如果把每种RES_TABLE_TYPE_SPEC_TYPE的EntryCount加起来,总数正好就是public.xml中
的条目数量。
我们写代码的时候不能保证每个资源都正好提供N种类型,比如说mipmap类型的图片资源,a图片提供了全部配置hdpi/mdpi/xhdpi/xxhdpi/xxxhdpi,b图片只提供了mdpi/xxhdpi,那这种差异就由entryOffsets[i]数组的具体位置来标识了,如果 entryOffsets[i]==0xffffffffL 就意味着这种类型的资源在当前这种配置下没有提供,读取时应该忽略。因为具体的资源Entry在文件中都是紧密排列的,所以同一个资源在不同配置之间的偏移下标不一定相同,完全取决于其他资源配置缺失的情况。
配置 | Offset[] for A | Offset[] for B |
---|---|---|
hdpi | 0x111111 | 0xffffffffL 缺失 |
mhdpi | 0x111111 | 0x111222 |
xhdpi | 0x120000 | 0x111333 |
xxmhdpi | 0x110000 | 0xffffffffL 缺失 |
xxxmhdpi | 0x120000 | 0xffffffffL 缺失 |
这个具体运行一下Demo代码就知道了,RES_TABLE_TYPE_SPEC_TYPE 和 RES_TABLE_TYPE_INFO_TYPE 这两个Chunk应该算是最复杂的了,搞清楚后下一步生成public.xml就方便了。
构造public.xml要提供下面几个Entry属性:
属性 | 含义 | 保存字段 |
---|---|---|
属性类型名称 | 在ResTableTypeInfoChunk中根据typeId索引从typeStringPool获取:atrr/drawble/mipmap… | |
属性名称 | 在ResTableEntry中根据key.index索引从keyStringPool获取,具体在子类实现 | ResTableEntry#key#index |
资源包ID | 0x01系统资源,0x7f用户资源,由PackageChunk保存 | ResTablePackageChunk#pkgId |
资源类型ID | [0x01, 0x10]下标从1开始,共16种,保存在对应的TypeChunk中 | ResTableTypeInfoChunk#typeId |
资源ID | Entry所在的数组下标,顺序从文件读取Entry的时候记录在具体的ResTableEntry中 | ResTableEntry#entryId |
上面提到同一种类型的资源对应的 RES_TABLE_TYPE_INFO_TYPE_CHUNK 会有多个,个数等于配置最多的资源类型数,同一种资源的ID是相同的,所以要避免产生重复的Entry,同时对于配置缺失的情况,要使用其他有配置的Chunk来补充。所以格式化输出时对于同一种资源,需要从多个同类别的 RES_TABLE_TYPE_INFO_TYPE_CHUNK 中找到一个非空的,顺序构造publix.xml。
public.xml文件中的id是4字节的16进制数,由pkgId, typeId, entryId合成:id = pkgId << 24 | typeIdId << 16 | (entryId & 0xffff)
,最后就是构造类似
这样的Entry项了,可以和apktool反编译出来的对比,应该是完全相同的。
.