resource.arsc文件结构
概述
resource.arsc
文件是Apk打包过程中的产生的一个资源索引文件。在对apk进行解压或者使用Android Studio对apk进行分析时便可以看到resource.arsc
文件。
通过学习resource.arsc
文件结构,可以帮助我们深入了解apk包体积优化中使用到的 重复资源删除、资源文件名混淆 技术。
arsc文件作用
在java中访问一个文件是需要提供文件的文件名,例如:
new File("./res/drawable-xxhdpi/img.png");
然而在Android中,却可以通过drawable Id获得资源文件:
getDrawable(R.drawable.img);
这里凭一个id就能获取资源文件内容,省去了文件路径的手动输入,其背后就是通过读取arsc
文件实现的。
这些R.drawable.xxx
、R.layout.xxx
、R.string.xxx
等的值(存储在R.jar
或者R.java
文件中)称之为 资源索引 ,通过这些资源索引可以在arsc
文件中查取实际的资源路径或资源值;
例如:
getDrawable(R.drawable.img)
在编译后成了getDrawable(2131099964)
,再将id
转为十六进制:
2131099964 = 0x7f06013c
这时的资源索引为0x7f06013c
。
资源索引具有固定的格式:0xPPTTEEEE
PackageId(2位) + TypeId(2位) + EntryId(4位)
PP:Package ID,包的命名空间,取值范围为[0x01, 0x7f],第三方应用均为7f。
TT:资源类型,有anim、layout、mipmap、string、style等资源类型。
EEEE:代表某一类资源在偏移数组中的值
所以,0x7f06013c
中 PackageId = 0x7f、TypeId = 0x06、EntryId = 0x013c
最简单的我们可以将arsc
函数想象成一个含有多个Pair
数组的文件,且每个资源类型(TypeId)对应一个Pair[]
(或多个,为了便于理解先只认为是一个)。因此在arsc
中查找0x7f06013c
元素的值,就是去设法找到TypeId=0x06
所对应的数组,然后找到其中的第0X013c
号元素。这个元素恰好就是"img => ./res/drawable-xxhdpi/img.png"
,左边是资源名称,右边是资源的文件路径,有了这个字符串程序便可以访问到对应的资源文件了。
当然实际的arsc
文件在结构上要稍微复杂一点,下面开始分析arsc
文件结构。
chunk
为了便于理解,在正式介绍resource.arsc
(以下简称arsc
)文件前,需要对chunk
进行解释一下,在其他文章中也多次使用了“chunk”这个词。
chunk
翻译为中文就是“块、部分(尤指大部分,一大块)”的意思,例如:一棵树,可以分为三个chunk
(部分):树冠、树茎、树根。也可以将一棵树视为一个chunk
,这个chunk
就是这棵树。
arsc文件结构
resources.arsc
是一个二进制文件,其内部结构的定义在ResourceTypes.h,不喜欢这个文件的同学,可以先看这张描述arsc
文件结构的网络图片。
图片整体描述了arsc
文件中各个chunk
的关系(注意结合图片左右两侧内容):
- 整个
arsc
文件是一个RES_TABLE_TYPE
类型的chunk
;RES_TABLE_TYPE
可分为三个部分:文件头部和两个子chunk
(RES_STRING_POOL_TYPE
、RES_TABLE_PACKAGE_TYPE
);RES_TABLE_PACKAGE_TYPE
中包含了:头部、资源类型字符串常量池、资源项名称字符串常量池、多个子chunk
(RES_TABLE_TYPE_SPEC_TYPE
和RES_TABLE_TYPE_TYPE
);- 每种类型的
chunk
都含有一个头结构
arsc
文件的结构大致可以用如下的伪代码表示:
//---------------------------------------------------------------------------
//: arsc文件是一个 RES_TABLE_TYPE 类型的chunk
RES_TABLE_TYPE {
table_header//文件头部
RES_STRING_POOL_TYPE //常量池chunk
RES_TABLE_PACKAGE_TYPE//内容chunk
}
//---------------------------------------------------------------------------
//:字符串常量池chunk
RES_STRING_POOL_TYPE {
pool_header//字符串常量池头部
string[] //常量池
}
//---------------------------------------------------------------------------
//: 内容chunk
RES_TABLE_PACKAGE_TYPE {
package_header//chunk头部
RES_STRING_POOL_TYPE//资源类型字符串常量池,类型为:RES_STRING_POOL_TYPE,内容为:[anim,attr,bool,color,dimen,drawable,id,integer,interpolator,layout,mipmap,string,style]
RES_STRING_POOL_TYPE//资源项名称字符串常量池
//资源类型chunk:在上述的ResTypeName_StringPool(资源类型常量池)中的每一个类型都有一个资源类型的chunk。这里以drawable为例
//drawable资源类型chunk
RES_TABLE_TYPE_SPEC_TYPE{
spec_header//spec头部
//drawable-mdpi
RES_TABLE_TYPE_TYPE
//drawable-hdpi
RES_TABLE_TYPE_TYPE
...
}
//attr资源类型chunk
RES_TABLE_TYPE_SPEC_TYPE{
RES_TABLE_TYPE_TYPE
RES_TABLE_TYPE_TYPE{
type_header//type头部
//具体的资源项池:资源名:资源值
ResName:ResValue
ResName:ResValue
ResName:ResValue
ResName:ResTableMapEntry->[Res_value1, Res_value2]
ResName:ResTableMapEntry->->[Res_value1, Res_value2,Res_value3]
}
...
}
...
...
}
//---------------------------------------------------------------------------
Chunk头结构
上述说到每一种chunk
均由一个头结构开始,在ResourceTypes.h中,这个头结构被定义为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.
uint16_t type;
// Size of the chunk header (in bytes). Adding this value to
// the address of the chunk allows