知识屋:更实用的电脑技术知识网站
所在位置:首页 > 科技  > 软件

Android6.0之App中的资源Rsources.arsc详解

发表时间:2022-03-26来源:网络

Apk中的resources.arsc是aapt工具编译资源时生成的一个重要文件。App资源能根据配置的变化,索引到相应的资源都要依赖它。例如Android设备语言,屏幕设备尺寸不同时,app通过同样的ID但却能找到不同的资源进行显示。

资源打包过程简述

开发app时,需要代码和资源。最终生成的apk中代码转换为了dex文件,那么apk文件中的资源是否还是app开发时那些资源文件呢?或者说这些资源文件是否发生了什么变化?

引用老罗一张关于资源打包过程以及查找的图:

资源打包.jpg

从上图可以看出:

除了assets和res/raw资源被原装不动地打包进APK之外,其它的资源都会被编译或者处理.xml文件会被编译为二进制的xml,所以解压apk后,无法直接打开xml文件。

除了assets资源之外,其它的资源都会被赋予一个资源ID。

打包工具负责编译和打包资源,编译完成之后,会生成一个resources.arsc文件和一个R.java,前者保存的是一个资源索引表,后者定义了各个资源ID常量,供在代码中索引资源。

应用程序配置文件AndroidManifest.xml同样会被编译成二进制的XML文件,然后再打包到APK里面去。

应用程序在运行时最终是通过AssetManager来访问资源,或通过资源ID来访问,或通过文件名来访问。

在生成的apk中,只有assets和res/raw资源被原装不动地打包进apk。其它的资源都会被编译或者处理。可以使用如下命令查看apk中的文件列表:

aapt l -v apkfile

将apk直接解压后,会发现xml都打不开,提示格式不对,因为其已经变为二进制xml了。另外PNG等图片也会进行相应的优化。还有就是多了一个resources.arsc文件。

需要准备的东西

分析resources.arsc文件,肯定要现有它了。利用Android studio创建一个ResourceDemo的工程,

资源从取值上来分,可分为两类:bag类型资源和非bag类型的资源。

bag资源:通俗的说,就是这类资源在赋值的时候,不能随便赋值,只能从事先定义好的值中选取一个赋值。很像枚举。

类型为values的资源除了是string之外,还有其它很多类型的资源,其中有一些比较特殊,如bag、style、plurals和array类的资源。这些资源会给自己定义一些专用的值,这些带有专用值的资源就统称为Bag资源。

例如,Android系统提供的android:orientation属性的取值范围为{“vertical”、“horizontal”},就相当于是定义了vertical和horizontal两个Bag。

在res/values中创建attrs.xml文件,在其中自定一个bag类型的属性资源。

这个文件定义了一个名称为“custom_orientation”的属性资源,它是一个枚举格式(也可理解为枚举类型)的属性,可以取值为“custom_vertical”或者“custom_horizontal”。

custom_vertical和custom_horizontal是custom_orientation的两个bag,我们可以将custom_vertical和custom_horizontal看成是custom_orientation的两个元数据,用来描述custom_orientation的取值集合。

“custom_orientation”是一个枚举类型的attr属性资源,也要使用一个内部元数据来描述其属性类型,这个元数据也使用一个bag来表示。

也就是说custom_orientation是由三个bag构成的:

第一个bag:名称是“^type”,值是TYPE_ENUM(TYPE_ENUM = 1header.headerSize); printf("res chunk size = %p\n",resHd->header.size); printf("res packages count = %p\n",resHd->packageCount);

结果:

################# res 文件头部信息 ################# res type = 0x2 res chunk hd size = 0xc res chunk size = 0x2f268 res packages count = 0x1

0x2与RES_TABLE_TYPE相等,packages count为1都与预期相一致。

字符串资源值池

这一部分的存储的字符串,都是资源的值,而且值是字符串类型。

以res/values/strings.xml为例:

ResourceDemo

该文件中定义了一个名字为“app_name”的string类型的资源,资源值为ResourceDemo。

ResourceDemo就存在这一部分,而"app_name"与"string"并没有存储在这里。

这一字符串池也包含一个头部:

struct ResStringPool_header { struct Resheader header; // 字符串个数 uint32_t stringCount; //字符串样式个数 uint32_t styleCount; // Flags enum { // If set, the string index is sorted by the string values (based // on strcmp16()). SORTED_FLAG = 1styleCount); printf("flags = %p\n",resStrPoolHd->flags); printf("stringsStart = %p\n",resStrPoolHd->stringsStart); printf("stylesStart = %p\n",resStrPoolHd->stylesStart); // header后面紧接着是两个偏移数组,之后才是数据 // 字符串偏移数组,数组元素个数是stringCount const uint32_t* mEntries = (const uint32_t*)(data+sizeof(ResTable_header)+resStrPoolHd->header.headerSize); const uint32_t* mEntryStyles; uint32_t mStylePoolSize; // 字符串池地址 const void * mString = (const void *)((const uint8_t*)resStrPoolHd+resStrPoolHd->stringsStart); const void * mStyleString; if(resStrPoolHd->styleCount>0){ // 字符串样式偏移数组,数组个数是styleCount,样式数组和字符串数组一一对应 // 也就是说在字符串偏移数组中所以为N的字符串,其样式在样式数组中的索引也为N mEntryStyles = mEntries + resStrPoolHd->stringCount; mStyleString = (void *)((const uint8_t*)resStrPoolHd+resStrPoolHd->stylesStart); mStylePoolSize = (resStrPoolHd->header.size-resStrPoolHd->stylesStart)/sizeof(uint32_t); } for(int i=0;istringCount;i++){ // 加2是跳过长度 if(strcmp(((char*)mString)+mEntries[i]+2,"ResourceDemo")==0){ const uint8_t * str = (((uint8_t*)mString)+mEntries[i]); // 字符串长度 int len = decodeLength(&str); printf("-->len = %p\n",len); printf("-->idx = %p\n",i); // 前两字节是长度 printf("-->%s\n",(((uint8_t*)mString)+mEntries[i])+2); } }

测试结果:

################# resStringPool(资源项的值字符串资源池)头部信息 ################# type = 0x1 chunk hd size = 0x1c chunk size = 0xcfb8 stringCount = 0x610 styleCount = (nil) flags = 0x100 stringsStart = 0x185c stylesStart = (nil) -->len = 0xc -->idx = 0x148 -->ResourceDemo

type为RES_STRING_POOL_TYPE正确。

flags为0x100,表明是string8,也就是utf-8字符串。

len为0xc,即12,而ResourceDemo长度为12,也正确。也找到了ResourceDemo。

package数据部分

这一部分最为复杂。索引表头部中的packageCount记录了索引表中有多少各package数据部分。通常只有一个。

同样这一部分的开头也是一个头部,结构如下:

struct ResTable_package { struct Resheader header; // 包ID uint32_t id; //package名字,string16形式存储 uint16_t name[128]; // 类型字符串池,相对package头部的偏移 uint32_t typeStrings; // 包中共有资源类型的种数 uint32_t lastPublicType; // 资源项名称字符串池,相对package头部的偏移 uint32_t keyStrings; // 资源项的数量 uint32_t lastPublicKey; uint32_t typeIdOffset; };

其中ID是由命名规则的,系统资源包id为0x1,而app的资源包ID为0x7f,0x1-0x7f的都是合法的。

从神图中可以看到紧跟着这个头部的是两个字符串池,都和前面介绍的资源项字符串池结构一样。

那么这两个字符串池用来存储什么东东呢?

仍以res/values/strings.xml为例:

ResourceDemo

该文件中定义了一个名字为“app_name”的string类型的资源项,资源值为ResourceDemo。

ResourceDemo就存在在前面介绍的资源项字符串池中,"string"存储在类型字符串池中,“app_name”存储在资源项名称字符串池中。

代码验证下:

printf("################# ResTablePackage头部信息 #################\n"); ResTablePackage_header *resTablePackageHd = (ResTablePackage_header *)(data+sizeof(ResTable_header)+resStrPoolHd->header.size); printf("resTablePackage type = %p\n",resTablePackageHd->header.type); printf("resTablePackage chunk hd size = %p\n",resTablePackageHd->header.headerSize); printf("resTablePackage chunk size = %p\n",resTablePackageHd->header.size); printf("resTablePackage id = %p\n",resTablePackageHd->id); char *name = allocFromUTF16(resTablePackageHd->name,128); printf("resTablePackage name = %s\n",name); free(name); name = NULL; printf("resTablePackage typeStrings = %p\n",resTablePackageHd->typeStrings); //目前这个值设置为类型字符串资源池的元素个数 printf("resTablePackage lastPublicType = %p\n",resTablePackageHd->lastPublicType); printf("resTablePackage keyStrings = %p\n",resTablePackageHd->keyStrings); //目前这个值设置为资源项名称字符串资源池的元素个数 printf("resTablePackage lastPublicKey = %p\n",resTablePackageHd->lastPublicKey); printf("resTablePackage typeIdOffset = %p\n",resTablePackageHd->typeIdOffset); printf("################# Type String pool信息 #################\n"); ResStringPool_header *typeStringPoolHd = (ResStringPool_header *)((uint8_t*)resTablePackageHd+resTablePackageHd->header.headerSize); printf("typeStringPoolHd type = %p\n",typeStringPoolHd->header.type); printf("typeStringPoolHd chunk hd size = %p\n",typeStringPoolHd->header.headerSize); printf("typeStringPoolHd chunk size = %p\n",typeStringPoolHd->header.size); printf("stringCount = %p\n",typeStringPoolHd->stringCount); printf("styleCount = %p\n",typeStringPoolHd->styleCount); printf("flags = %p\n",typeStringPoolHd->flags); printf("stringsStart = %p\n",typeStringPoolHd->stringsStart); printf("stylesStart = %p\n",typeStringPoolHd->stylesStart); // header后面紧接着是两个偏移数组,之后才是数据 // 字符串偏移数组,数组元素个数是stringCount const uint32_t* mTypeEntries = (const uint32_t*)((uint8_t*)typeStringPoolHd+typeStringPoolHd->header.headerSize); const uint32_t* mTypeEntryStyles; uint32_t mTypeStylePoolSize; // 字符串池地址 const void * mTypeString = (const void *)((const uint8_t*)typeStringPoolHd+typeStringPoolHd->stringsStart); const void * mTypeStyleString; if(typeStringPoolHd->styleCount>0){ // 字符串样式偏移数组,数组个数是styleCount,样式数组和字符串数组一一对应 // 也就是说在字符串偏移数组中所以为N的字符串,其样式在样式数组中的索引也为N mTypeEntryStyles = mTypeEntries + typeStringPoolHd->stringCount; mTypeStyleString = (void *)((const uint8_t*)typeStringPoolHd+typeStringPoolHd->stylesStart); mTypeStylePoolSize = (typeStringPoolHd->header.size-typeStringPoolHd->stylesStart)/sizeof(uint32_t); } printf("---->res type: \n"); for(int i=0;istringCount;i++){ const uint8_t * str = (((uint8_t*)mTypeString)+mTypeEntries[i]); int len = decodeLength(&str); //printf("-->len = %p\n",decodeLength(&str)); //printf("-->idx = %p\n",i); printf("-->%s\n",(((uint8_t*)mTypeString)+mTypeEntries[i])+2); } printf("################# key String pool信息 #################\n"); ResStringPool_header *keyStringPoolHd = (ResStringPool_header *)((uint8_t*)typeStringPoolHd+typeStringPoolHd->header.size); printf("keyStringPoolHd type = %p\n",keyStringPoolHd->header.type); printf("keyStringPoolHd chunk hd size = %p\n",keyStringPoolHd->header.headerSize); printf("keyStringPoolHd chunk size = %p\n",keyStringPoolHd->header.size); printf("stringCount = %p\n",keyStringPoolHd->stringCount); printf("styleCount = %p\n",keyStringPoolHd->styleCount); printf("flags = %p\n",keyStringPoolHd->flags); printf("stringsStart = %p\n",keyStringPoolHd->stringsStart); printf("stylesStart = %p\n",keyStringPoolHd->stylesStart); // header后面紧接着是两个偏移数组,之后才是数据 // 字符串偏移数组,数组元素个数是stringCount const uint32_t* mKeyEntries = (const uint32_t*)((uint8_t*)keyStringPoolHd+keyStringPoolHd->header.headerSize); const uint32_t* mKeyEntryStyles; uint32_t mKeyStylePoolSize; // 字符串池地址 const void * mKeyString = (const void *)((const uint8_t*)keyStringPoolHd+keyStringPoolHd->stringsStart); const void * mKeyStyleString; if(keyStringPoolHd->styleCount>0){ // 字符串样式偏移数组,数组个数是styleCount,样式数组和字符串数组一一对应 // 也就是说在字符串偏移数组中所以为N的字符串,其样式在样式数组中的索引也为N mKeyEntryStyles = mKeyEntries + keyStringPoolHd->stringCount; mKeyStyleString = (void *)((const uint8_t*)keyStringPoolHd+keyStringPoolHd->stylesStart); mKeyStylePoolSize = (keyStringPoolHd->header.size-keyStringPoolHd->stylesStart)/sizeof(uint32_t); } printf("---->res key: \n"); for(int i=0;istringCount;i++){ if(strcmp(((char*)mKeyString)+mKeyEntries[i]+2,"app_name")==0){ const uint8_t * str = (((uint8_t*)mKeyString)+mKeyEntries[i]); int len = decodeLength(&str); printf("-->%s\n",(((uint8_t*)mKeyString)+mKeyEntries[i])+2); } }

结果:

################# ResTablePackage头部信息 ################# resTablePackage type = 0x200 resTablePackage chunk hd size = 0x120 resTablePackage chunk size = 0x222a4 resTablePackage id = 0x7f resTablePackage name = com.godin.resourcedemo resTablePackage typeStrings = 0x120 resTablePackage lastPublicType = 0xc resTablePackage keyStrings = 0x1d0 resTablePackage lastPublicKey = 0x3a8 resTablePackage typeIdOffset = (nil) ################# Type String pool信息 ################# typeStringPoolHd type = 0x1 typeStringPoolHd chunk hd size = 0x1c typeStringPoolHd chunk size = 0xb0 stringCount = 0xc styleCount = (nil) flags = 0x100 stringsStart = 0x4c stylesStart = (nil) ---->res type: -->attr -->drawable -->mipmap -->layout -->anim -->string -->bool -->dimen -->style -->integer -->color -->id ################# key String pool信息 ################# keyStringPoolHd type = 0x1 keyStringPoolHd chunk hd size = 0x1c keyStringPoolHd chunk size = 0x7f38 stringCount = 0x3a8 styleCount = (nil) flags = 0x100 stringsStart = 0xebc stylesStart = (nil) ---->res key: -->app_name

resTablePackage type 是RES_TABLE_PACKAGE_TYPE 正确。

也打印出该资源包中的所有资源类型和找到了"app_name"这个资源项。

Type Spec与Config List

神图中的Type Spec 和 Config List 仍然归属在package数据部分.

这块内容是资源索引表中最重要的部分,但也是神图没能表达清楚的地方。这一部分也是同一个资源ID在不同配置下,找到不同资源文件的关键。所以这里先对这部分结构进行补充。

该部分的整体结构以资源类型Type分段,每段的数据结构相似,都是以ResTable_typeSpec开头,后面紧跟着一个spec数组,若干ResTable_type,每个ResTable_type之后紧跟着ResTable_entry偏移数组和若干ResTable_entry。

要注意:ResTable_typeSpec中的chunk header的size包括了其后面紧跟的spec数组所占空间大小,ResTable_type中的chunk header的size同样包括了跟随在其后面的数据大小。

然后又以一个资源type的ResTable_typeSpec开头,后面还跟这上面说的那些结构。直到所有的资源Type都存放完毕。

也就是说一个resources.arsc中的资源type有多少,就会有多少个。ResTable_typeSpec结构。

Type Spec数结构定义如下:

struct ResTable_typeSpec { struct Resheader header; // 类型ID uint8_t id; // Must be 0. uint8_t res0; // Must be 0. uint16_t res1; // 该类型资源项的数量 uint32_t entryCount; enum { // Additional flag indicating an entry is public. SPEC_PUBLIC = 0x40000000 }; };

紧跟着这个结构后面的是一个uint32_t类型的数组,该数组元素数量为entryCount。

数组中的uint32_t数据位图表示资源的配置。而且如果这个资源是可以导出的资源,那么其SPEC_PUBLICbit位置1.其余的配置的bit位如下:

ACONFIGURATION_MCC = 0x0001, ACONFIGURATION_MNC = 0x0002, ACONFIGURATION_LOCALE = 0x0004, ACONFIGURATION_TOUCHSCREEN = 0x0008, ACONFIGURATION_KEYBOARD = 0x0010, ACONFIGURATION_KEYBOARD_HIDDEN = 0x0020, ACONFIGURATION_NAVIGATION = 0x0040, ACONFIGURATION_ORIENTATION = 0x0080, ACONFIGURATION_DENSITY = 0x0100, ACONFIGURATION_SCREEN_SIZE = 0x0200, ACONFIGURATION_VERSION = 0x0400, ACONFIGURATION_SCREEN_LAYOUT = 0x0800, ACONFIGURATION_UI_MODE = 0x1000, ACONFIGURATION_SMALLEST_SCREEN_SIZE = 0x2000, ACONFIGURATION_LAYOUTDIR = 0x4000, ACONFIGURATION_SCREEN_ROUND = 0x8000, enum { CONFIG_MCC = ACONFIGURATION_MCC, CONFIG_MNC = ACONFIGURATION_MNC, CONFIG_LOCALE = ACONFIGURATION_LOCALE, CONFIG_TOUCHSCREEN = ACONFIGURATION_TOUCHSCREEN, CONFIG_KEYBOARD = ACONFIGURATION_KEYBOARD, CONFIG_KEYBOARD_HIDDEN = ACONFIGURATION_KEYBOARD_HIDDEN, CONFIG_NAVIGATION = ACONFIGURATION_NAVIGATION, CONFIG_ORIENTATION = ACONFIGURATION_ORIENTATION, CONFIG_DENSITY = ACONFIGURATION_DENSITY, CONFIG_SCREEN_SIZE = ACONFIGURATION_SCREEN_SIZE, CONFIG_SMALLEST_SCREEN_SIZE = ACONFIGURATION_SMALLEST_SCREEN_SIZE, CONFIG_VERSION = ACONFIGURATION_VERSION, CONFIG_SCREEN_LAYOUT = ACONFIGURATION_SCREEN_LAYOUT, CONFIG_UI_MODE = ACONFIGURATION_UI_MODE, CONFIG_LAYOUTDIR = ACONFIGURATION_LAYOUTDIR, CONFIG_SCREEN_ROUND = ACONFIGURATION_SCREEN_ROUND, };

以mipmap类型的资源(存储app icon)为例:

├── mipmap-hdpi-v4 │ └── ic_launcher.png ├── mipmap-mdpi-v4 │ └── ic_launcher.png ├── mipmap-xhdpi-v4 │ └── ic_launcher.png ├── mipmap-xxhdpi-v4 │ └── ic_launcher.png └── mipmap-xxxhdpi-v4 └── ic_launcher.png

mipmap类型的资源,提供了五种配置,以适应mdpi,hdpi,xhdpi,xxhdpi,xxxhpdi等不同屏幕尺寸。

mipmap类型的资源项名称为ic_launcher(注意不包括文件的后缀),对应着五个文件,app运行时,会根据当时的系统配置选择最佳的文件来显示。

以此为例的话,entryCount为1.配置数组spec元素数量也是为1了。

每种资源类型,在resources.arsc中只会存在一个ResTable_typeSpec数据结构,用来规范这个资源类型,比如这个资源类型中的资源项是否有配置(即可选资源),有哪些配置等。

紧跟在配置数组后面的是Config list,其对于的数据结构是:

struct ResTable_type { struct Resheader header; enum { NO_ENTRY = 0xFFFFFFFF }; // 类型ID uint8_t id; // Must be 0. uint8_t res0; // Must be 0. uint16_t res1; // 该类型资源项的数量 uint32_t entryCount; // 该资源项值在 ResTable_entry数据部分的中的偏移 uint32_t entriesStart; // 该资源的配置. ResTable_config config; };

ResTable_type这个数据结构的数量,与配置的种类有关,比如这个例子中的ic_launcher有五中配置,那么就会存在五个这样的数据结构。

假设res/mipmap-hdpi-v4中除了ic_launcher.png外还有一个名为test.png,而其他配置的mipmap文件夹中没有这个文件,那么也还是只有五中配置,也就是五个ResTable_type数据结构。

也就是说ResTable_type数据结构的数量,由某一种类型资源中配置最多的资源项来决定,其数量等于该资源项的配置数量。

接下来希望在代码中验证mipmap类型的entryCount是1,有五个ResTable_type数据结构。

printf("#################### type spec信息 ####################\n"); ResTable_typeSpec *resTableTypeSpecHd = NULL; ResTable_type *resTableTypeHd = NULL; // 得到第一个type的ResTable_typeSpec结构 Resheader *chunk_hd = (Resheader *)((uint8_t*)keyStringPoolHd+keyStringPoolHd->header.size); int num = 0; // 资源包中有几个资源类型,就有几个ResTable_typeSpec结构 for(int i=0;ilastPublicType;i++){ // ResTable_typeSpec后面有若干ResTable_type, // 通过 chunk header的type来区分 // 说明是TYPE_TYPE while(chunk_hd->type == RES_TABLE_TYPE_TYPE){ resTableTypeHd = (ResTable_type *)chunk_hd; // 统计mipmap类型的的ResTable_type结构有多少 if(strcmp((((uint8_t*)mTypeString)+mTypeEntries[resTableTypeHd->id-1]+2),"mipmap")==0){ num +=1; } chunk_hd = (Resheader *)((uint8_t*)chunk_hd+chunk_hd->size); } //这是一个新的ResTable_typeSpec resTableTypeSpecHd = (ResTable_typeSpec*)chunk_hd; printf("resTableTypeSpecHd type = %p\n",resTableTypeSpecHd->header.type); printf("resTableTypeSpecHd chunk hd size = %p\n",resTableTypeSpecHd->header.headerSize); printf("resTableTypeSpecHd chunk size = %p\n",resTableTypeSpecHd->header.size); printf("resTableTypeSpecHd id = %p\n",resTableTypeSpecHd->id); // 头部后面紧跟着数组元素个数为entryCount的uint32_t数组,每一个数组元素都用来描述一个资源项的配置差异性的。 printf("resTableTypeSpecHd entryCount = %p\n",resTableTypeSpecHd->entryCount); chunk_hd = (Resheader *)((uint8_t*)chunk_hd+chunk_hd->size); } printf("-mipmap ResTable_type count is:%d\n",num);

结果:

#################### type spec信息 #################### resTableTypeSpecHd type = 0x202 resTableTypeSpecHd chunk hd size = 0x10 resTableTypeSpecHd chunk size = 0x368 resTableTypeSpecHd id = 0x1 resTableTypeSpecHd entryCount = 0xd6 resTableTypeSpecHd type = 0x202 resTableTypeSpecHd chunk hd size = 0x10 resTableTypeSpecHd chunk size = 0x144 resTableTypeSpecHd id = 0x2 resTableTypeSpecHd entryCount = 0x4d resTableTypeSpecHd type = 0x202 resTableTypeSpecHd chunk hd size = 0x10 resTableTypeSpecHd chunk size = 0x14 resTableTypeSpecHd id = 0x3 resTableTypeSpecHd entryCount = 0x1 ................. -mipmap ResTable_type count is:5

从结果中看出entryCount为1,mipmap类型的ResTable_type数量也为5,与其配置种类一样。

然后按照前面假设的做法,在res/mipmap-hdpi-v4放置一个名为test.png的文件。重新编译生成新的resources.arsc文件,再次运行测试(加入了ResTable_type.entryCount):

if(strcmp((((uint8_t*)mTypeString)+mTypeEntries[resTableTypeHd->id-1]+2),"mipmap")==0){ num +=1; printf("mipmap ResTable_type.entryCount = %d\n",resTableTypeHd->entryCount); .......... ........ resTableTypeSpecHd type = 0x202 resTableTypeSpecHd chunk hd size = 0x10 resTableTypeSpecHd chunk size = 0x18 resTableTypeSpecHd id = 0x3 resTableTypeSpecHd entryCount = 0x2 mipmap ResTable_type.entryCount = 2 mipmap ResTable_type.entryCount = 2 mipmap ResTable_type.entryCount = 2 mipmap ResTable_type.entryCount = 2 mipmap ResTable_type.entryCount = 2 .......... -mipmap ResTable_type count is:5

看到了吧,虽然只是在在res/mipmap-hdpi-v4放置一个名为test.png的文件,没有在其他mipmap中放置,但是每个ResTable_type.entryCount同样由1变为了2。

也就是说ResTable_type.entryCount和ResTable_typeSpec.entryCount应该保持一致,都为该类型资源项的个数,如果一个类型的某个资源项只存在某一个配置文件夹下,那也算一个资源项。

ResTable_entry

到这里就只剩下ResTable_type结构中的ResTable_entry了。这里面存储了资源项的值和资源项的资源ID,可以理解为资源项的数据块。

每一个ResTable_type结构后面都会有若干ResTable_entry,至于ResTable_entry的数量,每个ResTable_type可能回有所不同。

还以前面res/mipmap-hdpi-v4放置一个名为test.png的文件,其它mipmap文件夹中不放为例,那么与mipmap-hdpi-v4对应的ResTable_type结构后面的ResTable_entry就有两个,其他mipmap对应的ResTable_type后面只有一个ResTable_entry。但是所有的ResTable_type结构后面跟着的ResTable_entry偏移数组元素数都是一样的,还是为2.

再来看一次ResTable_type:

struct ResTable_type { struct Resheader header; enum { NO_ENTRY = 0xFFFFFFFF }; // 类型ID uint8_t id; // Must be 0. uint8_t res0; // Must be 0. uint16_t res1; // 该类型资源项的数量 uint32_t entryCount; // 该资源项值在 ResTable_entry数据部分的中的偏移 uint32_t entriesStart; // 该资源的配置. ResTable_config config; };

ResTable_type结构后面紧跟一个大小为entryCount的uint32_t数组ResTable_entry偏移数组,每一个数组元素都用来描述一个资源项数据块的偏移位置。

ResTable_type结构中的entriesStart指明了ResTable_type后面的一系列的ResTable_entry的起始位置。

这里还有一个十分重要的的地方,前面说了资源项值分为两大类:bag类值和非bag类值。

非bag类值的资源项数据块是ResTable_entry,而bag类值资源项数据块由ResTable_map_entry描述。

struct ResTable_entry { // Number of bytes in this structure. uint16_t size; enum { FLAG_COMPLEX = 0x0001, FLAG_PUBLIC = 0x0002, FLAG_WEAK = 0x0004 }; //根据flags的不同,后面跟随的数据也不相同:bag资源和非bag资源 //flags为1,则ResTable_entry是ResTable_map_entry //资源项标志位。如果是一个Bag资源项,那么FLAG_COMPLEX位就等于1,并且在ResTable_entry后面跟有一个ResTable_map数组, //否则的话,在ResTable_entry后面跟的是一个Res_value。如果是一个可以被引用的资源项,那么FLAG_PUBLIC位就等于1。/ uint16_t flags; //对应的资源项名称 资源项名称字符串池中的偏移数组的索引 struct ResStringPool_ref key; }; struct ResTable_map_entry : public ResTable_entry { ResTable_ref parent; //bag类可取值的数量 uint32_t count; }; struct ResStringPool_ref { uint32_t index; };

对应mipmap类型的ic_launcher来说,其值也不是事先就定义好的,所以是一个非bag类值。可以通过flags来验证。

对于非bag类值来说,ResTable_entry后面紧跟着一个Res_value结构:

/** * Representation of a value in a resource, supplying type * information. */ struct Res_value { // Number of bytes in this structure. uint16_t size; // Always set to 0. uint8_t res0; //数据的类型,可以从上面的枚举类型中获取 uint8_t dataType; // The data for this item, as interpreted according to dataType. // 对于bag类值来说,data就是其值 // 对于非bag类值来说,其值是在资源项值字符串池中偏移数组的索引 typedef uint32_t data_type; data_type data; void copyFrom_dtoh(const Res_value& src); };

现在以代码获取ic_launcher的值来验证:

printf("#################### type spec信息 ####################\n"); ResTable_typeSpec *resTableTypeSpecHd = NULL; ResTable_type *resTableTypeHd = NULL; Resheader *chunk_hd = (Resheader *)((uint8_t*)keyStringPoolHd+keyStringPoolHd->header.size); int num = 0; for(int i=0;ilastPublicType;i++){ // 说明是TYPE_TYPE while(chunk_hd->type == RES_TABLE_TYPE_TYPE){ resTableTypeHd = (ResTable_type *)chunk_hd; if(strcmp((((uint8_t*)mTypeString)+mTypeEntries[resTableTypeHd->id-1]+2),"mipmap")==0){ num +=1; printf("mipmap ResTable_type.entryCount = %d\n",resTableTypeHd->entryCount); // 得到数组 uint32_t * su =(uint32_t *)(resTableTypeHd->header.headerSize+(uint8_t*)resTableTypeHd); //ResTable_entry data starts // 得到ResTable_entry起始位置 uint8_t * addr = (uint8_t*)((uint8_t*)resTableTypeHd+resTableTypeHd->header.headerSize+resTableTypeHd->entryCount*sizeof(uint32_t)); for(int i=0;ientryCount;i++){ // 因为偏移数组中元素数量可能比其后面的ResTable_entry数量多,对于没有对应ResTable_entry结构的偏移数组中元素,其值为0xffffffff. if(su[i]!=0xffffffff){ // 依次加上偏移得到对应的ResTable_entry ResTable_entry * entry = (ResTable_entry *)(addr+su[i]); // 如果flags是bag值类型的话,flags最低bit位为1 printf("entry flags:0x%x\n",entry->flags); if(strstr(((char*)mKeyString)+mKeyEntries[entry->key.index],"ic_launcher")){ //ResTable_entry后面紧跟着Res_Value Res_value* value = (Res_value*)((uint8_t*)entry+entry->size); printf("value is:%s\n",((char*)mString)+mEntries[value->data]+2); } } } } chunk_hd = (Resheader *)((uint8_t*)chunk_hd+chunk_hd->size); } resTableTypeSpecHd = (ResTable_typeSpec*)chunk_hd; printf("resTableTypeSpecHd type = %p\n",resTableTypeSpecHd->header.type); printf("resTableTypeSpecHd chunk hd size = %p\n",resTableTypeSpecHd->header.headerSize); printf("resTableTypeSpecHd chunk size = %p\n",resTableTypeSpecHd->header.size); printf("resTableTypeSpecHd id = %p\n",resTableTypeSpecHd->id); // 头部后面紧跟着数组元素个数为entryCount的uint32_t数组,每一个数组元素都用来描述一个资源项的配置差异性的。 printf("resTableTypeSpecHd entryCount = %p\n",resTableTypeSpecHd->entryCount); chunk_hd = (Resheader *)((uint8_t*)chunk_hd+chunk_hd->size); } printf("-mipmap ResTable_type count is:%d\n",num);

结果:

........... resTableTypeSpecHd type = 0x202 resTableTypeSpecHd chunk hd size = 0x10 resTableTypeSpecHd chunk size = 0x18 resTableTypeSpecHd id = 0x3 resTableTypeSpecHd entryCount = 0x2 mipmap ResTable_type.entryCount = 2 entry flags:0 value is:res/mipmap-mdpi-v4/ic_launcher.png mipmap ResTable_type.entryCount = 2 entry flags:0 value is:res/mipmap-hdpi-v4/ic_launcher.png entry flags:0 mipmap ResTable_type.entryCount = 2 entry flags:0 value is:res/mipmap-xhdpi-v4/ic_launcher.png mipmap ResTable_type.entryCount = 2 entry flags:0 value is:res/mipmap-xxhdpi-v4/ic_launcher.png mipmap ResTable_type.entryCount = 2 entry flags:0 value is:res/mipmap-xxxhdpi-v4/ic_launcher.png ......... -mipmap ResTable_type count is:5

那么对于bag值类型,以前面介绍bag时列举的类型:

在res/values中创建attrs.xml文件,在其中自定一个bag类型的属性资源。

前面已经知道了custom_orientation是由三个bag构成的:

第一个bag:名称是“^type”,值是TYPE_ENUM(TYPE_ENUM = 1type == RES_TABLE_TYPE_TYPE){ resTableTypeHd = (ResTable_type *)chunk_hd; if(strcmp((((uint8_t*)mTypeString)+mTypeEntries[resTableTypeHd->id-1]+2),"mipmap")==0){ num +=1; printf("mipmap ResTable_type.entryCount = %d\n",resTableTypeHd->entryCount); // 得到数组 uint32_t * su =(uint32_t *)(resTableTypeHd->chunk_header.headerSize+(uint8_t*)resTableTypeHd); //ResTable_entry data starts uint8_t * addr = (uint8_t*)((uint8_t*)resTableTypeHd+resTableTypeHd->chunk_header.headerSize+resTableTypeHd->entryCount*sizeof(uint32_t)); for(int i=0;ientryCount;i++){ if(su[i]!=0xffffffff){ ResTable_entry * entry = (ResTable_entry *)(addr+su[i]); printf("entry flags:0x%x\n",entry->flags); if(strstr(((char*)mKeyString)+mKeyEntries[entry->key.index],"ic_launcher")){ //ResTable_entry后面紧跟着Res_Value Res_value* value = (Res_value*)((uint8_t*)entry+entry->size); printf("value is:%s\n",((char*)mString)+mEntries[value->data]+2); } } } } // 因为custom_orientation本质上是一个属性资源,所以以attr来判断 if(strcmp((((uint8_t*)mTypeString)+mTypeEntries[resTableTypeHd->id-1]+2),"attr")==0){ uint32_t * su =(uint32_t *)(resTableTypeHd->chunk_header.headerSize+(uint8_t*)resTableTypeHd); //ResTable_entry data starts uint8_t * addr = (uint8_t*)((uint8_t*)resTableTypeHd+resTableTypeHd->chunk_header.headerSize+resTableTypeHd->entryCount*sizeof(uint32_t)); for(int i=0;ientryCount;i++){ if(su[i]!=0xffffffff){ ResTable_map_entry * map_entry = (ResTable_map_entry *)(addr+su[i]); if(strstr(((char*)mKeyString)+mKeyEntries[map_entry->key.index],"custom_orientation")){ printf("entry flags:0x%x\n",map_entry->flags); // 等于本bag资源的可取值数量,也预示着后面紧跟着count个ResTable_map结构 printf("bag count: %d\n",map_entry->count); for(int j=0;jcount;j++){ ResTable_map* map_value = (ResTable_map*)((uint8_t*)map_entry+map_entry->size); ResTable_map* map_value1 = map_value+j; printf("bag value-->%p\n",map_value1->value.data); printf("bag id-->%p\n" , map_value1->name); } } } } } chunk_hd = (ResChunk_header *)((uint8_t*)chunk_hd+chunk_hd->size); } resTableTypeSpecHd = (ResTable_typeSpec*)chunk_hd; printf("resTableTypeSpecHd type = %p\n",resTableTypeSpecHd->chunk_header.type); printf("resTableTypeSpecHd chunk hd size = %p\n",resTableTypeSpecHd->chunk_header.headerSize); printf("resTableTypeSpecHd chunk size = %p\n",resTableTypeSpecHd->chunk_header.size); printf("resTableTypeSpecHd id = %p\n",resTableTypeSpecHd->id); // 头部后面紧跟着数组元素个数为entryCount的uint32_t数组,每一个数组元素都用来描述一个资源项的配置差异性的。 printf("resTableTypeSpecHd entryCount = %p\n",resTableTypeSpecHd->entryCount); chunk_hd = (ResChunk_header *)((uint8_t*)chunk_hd+chunk_hd->size); } printf("-mipmap ResTable_type count is:%d\n",num);

结果:

#################### type spec信息 #################### resTableTypeSpecHd type = 0x202 resTableTypeSpecHd chunk hd size = 0x10 resTableTypeSpecHd chunk size = 0x368 resTableTypeSpecHd id = 0x1 resTableTypeSpecHd entryCount = 0xd6 entry flags:0x1 //预示着是一个bag值类型 bag count: 3 bag value-->0x10000 //TYPE_ENUM = 10xc8 //200 bag id-->0x7f0c0009 bag value-->0x64 //100 bag id-->0x7f0c000a

结果和预期都是符合的,否则使无法解析的。

这里特别指出一点,ResTable_type后面的资源项数据块可能既有ResTable_entry又有ResTable_map_entry,例如本例中资源类型attr的ResTable_type就是这种情况。

但ResTable_map_entry继承自ResTable_entry。

ResTable_entry后面跟这个的是一个Res_value,而ResTable_map_entry后面跟着的是若干ResTable_map,数量是ResTable_map_entry.count决定。每个ResTable_map中都有一个Res_value。

好了到这里位置就彻底搞清楚resources.arsc的格式了。

 

收藏
  • 人气文章
  • 最新文章
  • 下载排行榜
  • 热门排行榜