26
2017
09

Apk解析之 —— resource.arsc

本篇解析resource.arsc文件,参考文章:Reference
项目源码:ApkParser

arsc文件结构

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数据块

接着资源项的值字符串资源池后面的部分就是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

五、类型规范数据块 RES_TABLE_TYPE_SPEC_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字节

六、资源类型项数据块 RES_TABLE_TYPE_INFO_TYPE

类型资源项数据块用来描述资源项的具体信息, 这样我们就可以知道每一个资源项的名称配置等信息。类型资源项数据同样是按照类型和配置来组织的,也就是说,一个具有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_TYPERES_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中<public .../>的条目数量。

我们写代码的时候不能保证每个资源都正好提供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

构造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),最后就是构造类似<public type="attr" name="drawerArrowStyle" id="0x7f010000" />这样的Entry项了,可以和apktool反编译出来的对比,应该是完全相同的。

八、由public.xml的key找到value值

.

上一篇:Genymotion错误:An error occured while deploying the file. 下一篇:Fresco解析 三