手把手教你解析Resources.arsc

http://blog.csdn.net/beyond702/article/details/51744082

一、前言

对于APK里面的Resources.arsc文件大家应该都知道是干什么的(不知道的请看我的另一篇文章Android应用程序资源文件的编译和打包原理),它实际上就是App的资源索引表。下面我会结合实例对它的格式做一下剖析,读完这篇文章应该能够知道Resources.arsc的格式,并可以从二进制的文件中查找到资源的相关信息,或者根据资源的id可以定位到二进制文件中的位置。不过本人对Android资源文件的有一些相关概念并不是特别熟悉,所以文章中有很多地方也并不明白,如有错误欢迎指正!

二、R.java文件及资源ID

首先先介绍一下我们在Android应用开发过程中程序中用的资源的id,相信大家都知道R.java文件,这个是通过aapt对资源文件进行编译生成的资源id文件,这样我们程序中使用资源文件更加方便。举例我们先看一下原始的资源文件res/values/strings.xml内容如下:

[html] view plain copy

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <string name="app_name">Cert</string>
  4. <string name="hello_world">Hello world!</string>
  5. <string name="action_settings">Settings</string>
  6. </resources>

代码段1

这里先介绍几个概念,上面的app_name和hello_world这些叫做资源项名称(其它的还有windowActionBar、ActionBarTabStyle类似这种),而它们对应的资源项类型就是string(其它的还有attr、drawable类似这些),资源项的值就是Cert和Hello world!这些。

下面是对应R.java文件的内容:

[java] view plain copy

  1. public final class R {
  2. ...
  3. public static final class string {
  4. ...
  5. /**  Description of the choose target button in a ShareActionProvider (share UI). [CHAR LIMIT=NONE]
  6. */
  7. public static final int abc_shareactionprovider_share_with=0x7f0a000c;
  8. /**  Description of a share target (both in the list of such or the default share button) in a ShareActionProvider (share UI). [CHAR LIMIT=NONE]
  9. */
  10. public static final int abc_shareactionprovider_share_with_application=0x7f0a000b;
  11. public static final int action_settings=0x7f0a000f;
  12. public static final int app_name=0x7f0a000d;
  13. public static final int hello_world=0x7f0a000e;
  14. }
  15. ...
  16. }

代码段2

可以看到每个资源文件在R中都是一个class,每个资源项名称都分配了一个id,id值是一个四字节无符号整数,格式是这样的:0xpptteeee,(p代表的是package,t代表的是type,e代表的是entry),最高字节代表Package ID,次高字节代表Type ID,后面两个字节代表Entry ID。

Package ID相当于是一个命名空间,限定资源的来源。Android系统当前定义了两个资源命令空间,其中一个系统资源命令空间,它的Package ID等于0x01,另外一个是应用程序资源命令空间,它的Package ID等于0x7f。所有位于[0x01, 0x7f]之间的Package ID都是合法的,而在这个范围之外的都是非法的Package ID。前面提到的系统资源包package-export.apk的Package ID就等于0x01,而我们在应用程序中定义的资源的Package ID的值都等于0x7f,这一点可以通过生成的R.java文件来验证。

Type ID是指资源的类型ID。资源的类型有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干种,每一种都会被赋予一个ID。

Entry ID是指每一个资源在其所属的资源类型中所出现的次序。注意,不同类型的资源的Entry ID有可能是相同的,但是由于它们的类型不同,我们仍然可以通过其资源ID来区别开来。

三、解析Resources.arsc

1. Resources.arsc文件格式

下面我们开始看Resources.arsc(后面截图给出的resources.arsc文件的二进制内容都是与上面代码段1和代码段2相对应的),首先看一下文件的格式,如下面两个图:

图1

图2

以上两个图都是Resources.arsc文件的格式,图1是从网上找的,其中很多项都展开了,不了解对应的数据结构肯定看不懂,所以我自己画了图2(画图好蛋疼的说~),相对来说更容易接受一点,这里都放出来做个对照吧。Resources.arsc对应的数据结构的定义在Android源码/frameworks/base/include/androidfw/ResourceType.h中,大家可以自己去看一下。

2. chunk

下面我来从上到下介绍一下文件的格式,首先是chunk概念,整个文件是由一系列的chunk构成的,算是整个文件划分的基本单位吧,实际上就是把整个文件无差别的划分成多个模块,每个模块就是一个chunk,结构更加清晰。每个chunk是最前面是一个ResChunk_header的结构体,描述这个chunk的信息,ResChunk_header如下:

[cpp] view plain copy

  1. struct ResChunk_header
  2. {
  3. enum
  4. {
  5. RES_NULL_TYPE               = 0x0000,
  6. RES_STRING_POOL_TYPE        = 0x0001,
  7. RES_TABLE_TYPE              = 0x0002,
  8. RES_XML_TYPE                = 0x0003,
  9. RES_XML_FIRST_CHUNK_TYPE    = 0x0100,
  10. RES_XML_START_NAMESPACE_TYPE= 0x0100,
  11. RES_XML_END_NAMESPACE_TYPE  = 0x0101,
  12. RES_XML_START_ELEMENT_TYPE  = 0x0102,
  13. RES_XML_END_ELEMENT_TYPE    = 0x0103,
  14. RES_XML_CDATA_TYPE          = 0x0104,
  15. RES_XML_LAST_CHUNK_TYPE     = 0x017f,
  16. RES_XML_RESOURCE_MAP_TYPE   = 0x0180,
  17. RES_TABLE_PACKAGE_TYPE      = 0x0200,
  18. RES_TABLE_TYPE_TYPE         = 0x0201,
  19. RES_TABLE_TYPE_SPEC_TYPE    = 0x0202
  20. };
  21. //当前这个chunk的类型
  22. uint16_t type;
  23. //当前这个chunk的头部大小
  24. uint16_t headerSize;
  25. //当前这个chunk的大小
  26. uint32_t size;
  27. };

代码段3

3. 文件header

Resources.arsc文件的最开始是整个文件的header,结构是ResTable_header:

[cpp] view plain copy

  1. struct ResTable_header
  2. {
  3. struct ResChunk_header header;
  4. // The number of ResTable_package structures.
  5. uint32_t packageCount;
  6. ;

代码段4

可以看到header就是一个chunk,以ResChunk_header结构开头来描述这个chunk。resources.arsc文件的header内容如下图中选中部分:

图3

图中选中的部分就是header,可以看到类型是0x0002,对应类型是RES_TABLE_TYPE,headerSize是0x0c,整个chunk的大小也就是文件的大小是0x019584,package的数量是1个。

4. 全局字符串池

紧接着是Global String Pool,全局字符串池,这也是Resources.arsc存在最重要的一个原因之一,就是把所有字符串放到这个池子里,大家复用这些字符串,可以很大的减小APK包的尺寸。从图1和图2可以看到后面还有两个字符串池,那么什么字符串会放到这个全局字符串池中呢?所有的资源文件的路径名,以及资源文件中所定义的资源的值,比如代码段1中的Cert和Hello world!都存在这里。

字符串池的结构体如下:

[cpp] view plain copy

  1. struct ResStringPool_header
  2. {
  3. struct ResChunk_header header;
  4. // Number of strings in this pool (number of uint32_t indices that follow in the data).
  5. uint32_t stringCount;
  6. // Number of style span arrays in the pool (number of uint32_t indices follow the string indices).
  7. uint32_t styleCount;
  8. // Flags.
  9. enum {
  10. // If set, the string index is sorted by the string values (based on strcmp16()).
  11. SORTED_FLAG = 1<<0,
  12. // String pool is encoded in UTF-8
  13. UTF8_FLAG = 1<<8
  14. };
  15. uint32_t flags;<span style="white-space:pre"> </span>//If flags is 0x0, string pool is encoded in UTF-16
  16. // Index from header of the string data.
  17. uint32_t stringsStart;
  18. // Index from header of the style data.
  19. uint32_t stylesStart;

代码段5

对应的二进制内容如下图选中部分:

图4

从图中可以看到类型是0x0001,对应代码段3中RES_STRING_POOL_TYPE,整个chunk的大小是0x919C,stringCount是0x03E1,styleCount是0,flags是0x0100即UTF8格式,stringsStart即字符串相对头部起始位置的偏移是0x0FA0。

从图2中可以看到紧接着header的是stringCount个字符串偏移数组,数组每一个元素记录着每个字符串的起始位置相对于stringsStart的偏移。字符串池中每个UTF8格式字符串都是以字符串结束符0x00结束的,UTF16是0x0000。

style偏移数组与string是一样的就不多说了,但这个style是干什么的现在我还不清楚,以后知道了再更新。

5. Package解析

下面要介绍重头戏Package了。首先是一个package的header,结构体如下:

[cpp] view plain copy

  1. struct ResTable_package
  2. {
  3. struct ResChunk_header header;
  4. //包的ID,等于Package Id,一般用户包的值Package Id为0X7F,系统资源包的Package Id为0X01。
  5. uint32_t id;
  6. //包名称
  7. char16_t name[128];
  8. //类型字符串资源池相对头部的偏移
  9. uint32_t typeStrings;
  10. //最后一个导出的Public类型字符串在类型字符串资源池中的索引,目前这个值设置为类型字符串资源池的元素个数。
  11. uint32_t lastPublicType;
  12. //资源项名称字符串相对头部的偏移
  13. uint32_t keyStrings;
  14. //最后一个导出的Public资源项名称字符串在资源项名称字符串资源池中的索引,目前这个值设置为资源项名称字符串资源池的元素个数。
  15. uint32_t lastPublicKey;
  16. };

代码段6

图4中全局字符串池的起始位置是0xC,而整个chunk的大小是0x919C,那么package的起始位置就是两者相加得到0x91A8,对应二进制内容如下图选中部分:

图5

从上图可以看到chunk类型是0x0200,对应代码段3中的RES_TABLE_PACKAGE_TYPE,id是0x7F(这与R.java中的每个资源id的最高字节是一样的),这个package的名字是com.example.cert,类型字符串池typeStrings相对于package header起始位置的偏移是0x011C,类型字符串的个数是0x0C,资源项名称字符串池keyStrings相对于package header起始位置的偏移是0x01C8,个数是0x01E1。

5.1 类型字符串池和资源项名称字符串池

对于类型字符串池(图2中的Type String Pool)和资源项名称字符串池(图2中的Key String Pool)的结构和内容我这里就不贴出来了,结构和全局字符串池是一样的。类型字符串池中存储的是所有类型相关的字符串,比如attr,drawable,layout这些;而资源项名称字符串池中存储的是应用所有资源文件中的资源项名称相关的字符串,比如代码段1中的app_name,hello_world,action_settings。

5.2 类型规范数据块(Type Spec)

类型规范数据块用来描述资源项的配置差异性。通过这个差异性描述,我们就可以知道每一个资源项的配置状况。知道了一个资源项的配置状况之后,Android资源管理框架在检测到设备的配置信息发生变化之后,就可以知道是否需要重新加载该资源项。类型规范数据块是按照类型来组织的,也就是说,每一种类型都对应有一个类型规范数据块。

上面是从参考文章里copy过来的,可能有些人不太了解这个Type Spec是什么东西,我个人的理解它实际上就是类型。说到这里需要提几句Android资源文件的配置问题,大家都知道Android设备众多,为了使得一个应用程序能够在运行时同时支持不同的大小和密度的屏幕,以及支持国际化,即支持不同的国家地区和语言,Android应用程序资源的组织方式有18个维度,每一个维度都代表一个配置信息,从而可以使得应用程序能够根据设备的当前配置信息来找到最匹配的资源来展现在UI上,从而提高用户体验。也就是说,每一个资源类,都会有一个配置列表,配置着这个资源类的不同维度的信息,那么Type Spec就是这个资源类的代表。比如前面看到的attr,drawable,string这种都是资源类,Type Spec就是描述这些的结构,前面说到过R.java中每个资源id的格式是0xpptteeee,里面那个次高字节的tt就是Type Spec的id,同时这个id值也是这个Type Spec的类型名称在Type String Pool类型字符串池中索引数组的索引值,根据id值就可以找到其名称。

下面是Type Spec的结构:

[cpp] view plain copy

  1. struct ResTable_typeSpec
  2. {
  3. struct ResChunk_header header;
  4. //标识资源的Type ID,Type ID是指资源的类型ID,从1开始。资源的类型有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干种,每一种都会被赋予一个ID。
  5. uint8_t id;
  6. //保留,始终为0
  7. uint8_t res0;
  8. //保留,始终为0
  9. uint16_t res1;
  10. //等于本类型的资源项个数,指名称相同的资源项的个数。
  11. uint32_t entryCount;
  12. };

代码段7

下图是其对应的二进制数据:

图6

上图可以看出该chunk的类型是0x0202,这个Type Spec的id是1,entryCount是6E,在这个ResTable_typeSpec结构后面紧跟着entryCount个资源spec数组,entryCount指的是这个类型有多少资源项,在后面我们会讲到aapt解码resources.arsc,输出中每个Type Spec的资源项后面会有一个flags,它的值就是这个数组中对应的值,但是这个flag代表什么我还不清楚。

5.3 Config List

上面讲到每个Type Spec是对一个类型的描述,每个类型会有多个维度,那就是接下来的Config List了,这个Config List是由多个ResTable_type结构来描述的,每个ResTable_type描述的是一个维度,下面是这个结构体的定义:

[cpp] view plain copy

  1. struct ResTable_type
  2. {
  3. struct ResChunk_header header;
  4. enum {
  5. NO_ENTRY = 0xFFFFFFFF
  6. };
  7. //标识资源的Type ID
  8. uint8_t id;
  9. //保留,始终为0
  10. uint8_t res0;
  11. //保留,始终为0
  12. uint16_t res1;
  13. //等于本类型的资源项个数,指名称相同的资源项的个数。
  14. uint32_t entryCount;
  15. //等于资源项数据块相对头部的偏移值。
  16. uint32_t entriesStart;
  17. //指向一个ResTable_config,用来描述配置信息,地区,语言,分辨率等
  18. ResTable_config config;
  19. };

代码段7

 

其中的id与ResTable_typeSpec中的id值是一样的。其中的ResTable_config就是这个维度的具体描述了,如下:

[cpp] view plain copy

  1. struct ResTable_config
  2. {
  3. // Number of bytes in this structure.
  4. uint32_t size;
  5. union {
  6. struct {
  7. // Mobile country code (from SIM).  0 means "any".
  8. uint16_t mcc;
  9. // Mobile network code (from SIM).  0 means "any".
  10. uint16_t mnc;
  11. };
  12. uint32_t imsi;
  13. };
  14. union {
  15. struct {
  16. // \0\0 means "any".  Otherwise, en, fr, etc.
  17. char language[2];
  18. // \0\0 means "any".  Otherwise, US, CA, etc.
  19. char country[2];
  20. };
  21. uint32_t locale;
  22. };
  23. union {
  24. struct {
  25. uint8_t orientation;
  26. uint8_t touchscreen;
  27. uint16_t density;
  28. };
  29. uint32_t screenType;
  30. };
  31. union {
  32. struct {
  33. uint8_t keyboard;
  34. uint8_t navigation;
  35. uint8_t inputFlags;
  36. uint8_t inputPad0;
  37. };
  38. uint32_t input;
  39. };
  40. union {
  41. struct {
  42. uint16_t screenWidth;
  43. uint16_t screenHeight;
  44. };
  45. uint32_t screenSize;
  46. };
  47. union {
  48. struct {
  49. uint16_t sdkVersion;
  50. // For now minorVersion must always be 0!!!  Its meaning
  51. // is currently undefined.
  52. uint16_t minorVersion;
  53. };
  54. uint32_t version;
  55. };
  56. union {
  57. struct {
  58. uint8_t screenLayout;
  59. uint8_t uiMode;
  60. uint16_t smallestScreenWidthDp;
  61. };
  62. uint32_t screenConfig;
  63. };
  64. union {
  65. struct {
  66. uint16_t screenWidthDp;
  67. uint16_t screenHeightDp;
  68. };
  69. uint32_t screenSizeDp;
  70. };
  71. }

代码段8

下图是示例中ResTable_type的二进制内容:

图7

上图可以看到这个chunk的类型是0x0201,id是1与上面的Type Spec是对应的,entryCount是0x6E与上面的Type Spec也是一样的,entriesStart是0x01F0表示entry列表相对于此头部起始位置的偏移,后面0x24表示ResTable_config的大小,后面其它的字节全是0说明配置信息全是any,也就是default默认的配置。

5.4 Entries

紧接着ResTable_type后面是entryCount个entry的索引数组,每个索引数组的值表示该entry相对于entriesStart的偏移。那么这个entry代表什么呢?就是一个资源项!R.java中每个id的结构是0xpptteeee,低位两个字节的eeee就是这个资源项在索引数组中的索引值。entry的数据结构定义如下:

[cpp] view plain copy

  1. struct ResTable_entry
  2. {
  3. //表示资源项头部大小。
  4. uint16_t size;
  5. enum {
  6. //如果flags此位为1,则ResTable_entry后跟随ResTable_map数组,为0则跟随一个Res_value。
  7. FLAG_COMPLEX = 0x0001,
  8. //如果此位为1,这个一个被引用的资源项
  9. FLAG_PUBLIC = 0x0002
  10. };
  11. //资源项标志位
  12. uint16_t flags;
  13. //资源项名称在资源项名称字符串资源池的索引
  14. struct ResStringPool_ref key;
  15. };

代码段9

上面结构中的key字段的值是这个资源项的名字在资源项字符串资源池的索引数组中的索引值,通过这个值就可以查到这个资源项的名称。另一个很重要的字段是flags,如果是0x0001位为0的话,那么这个结构后面跟一个ResTable_value;若该位为1的话,就表示这个结构是个ResTable_map_entry(继承自ResTable_entry),并且后面会跟一个或多个ResTable_map结构。这些结构的定义如下:

[cpp] view plain copy

  1. struct ResTable_map_entry : public ResTable_entry
  2. {
  3. //指向父ResTable_map_entry的资源ID,如果没有父ResTable_map_entry,则等于0。
  4. ResTable_ref parent;
  5. //等于后面ResTable_map的数量
  6. uint32_t count;
  7. };
  8. struct ResTable_map
  9. {
  10. //bag资源项ID
  11. ResTable_ref name;
  12. //bag资源项值
  13. Res_value value;
  14. };
  15. struct Res_value
  16. {
  17. //Res_value头部大小
  18. uint16_t size;
  19. //保留,始终为0
  20. uint8_t res0;
  21. enum {
  22. TYPE_NULL = 0x00,
  23. TYPE_REFERENCE = 0x01,
  24. TYPE_ATTRIBUTE = 0x02,
  25. TYPE_STRING = 0x03,
  26. TYPE_FLOAT = 0x04,
  27. TYPE_DIMENSION = 0x05,
  28. TYPE_FRACTION = 0x06,
  29. TYPE_FIRST_INT = 0x10,
  30. TYPE_INT_DEC = 0x10,
  31. TYPE_INT_HEX = 0x11,
  32. TYPE_INT_BOOLEAN = 0x12,
  33. TYPE_FIRST_COLOR_INT = 0x1c,
  34. TYPE_INT_COLOR_ARGB8 = 0x1c,
  35. TYPE_INT_COLOR_ARGB8 = 0x1c,
  36. TYPE_INT_COLOR_RGB8 = 0x1d,
  37. TYPE_INT_COLOR_ARGB4 = 0x1e,
  38. TYPE_INT_COLOR_RGB4 = 0x1f,
  39. TYPE_LAST_COLOR_INT = 0x1f,
  40. TYPE_LAST_INT = 0x1f
  41. };
  42. //数据的类型,可以从上面的枚举类型中获取
  43. uint8_t dataType;
  44. //数据对应的索引
  45. uint32_t data;
  46. };

代码段10

ResTable_map_entry结构后面跟多少个ResTable_map由其结构中的count字段来决定。

下面看示例中对应ResTable_entry的二进制代码:

图8

可以看到size是0x10,flags是0x0001,也就是说它是一个ResTable_map_entry结构而不是ResTable_entry,key是0表示其名字在Key String Pool的索引数组中的0号元素,然后看到count是1,那么后面跟一个ResTable_map,其中name的值是0x01000000,具体含义查看系统源码文件中该结构的定义,这里就不多说了,后面ResTable_value的size是0x08,dataType是0x10即TYPE_FIRST_INT即后面data数据是int类型。

到这里整个文件的结构解析大概就介绍完了,下面我们会从另一个角度来介绍,我们根据资源的id值来找resources.arsc中的数据。

四、根据资源的id值定位resources.arsc中的数据

我们在用Eclipse或者Android Studio来写Android应用的时候,IDE直接帮我们生成了R.java文件,我们可以在这里面看到某个资源的id值,其实IDE也是使用aapt来编译资源文件生成的R.java。如果我们拿到一个APK怎么看资源的id呢?当然也是用aapt来反编译就好了,命令如下:

[cpp] view plain copy

  1. aapt d resources XXX.apk

对应我们之前的示例输出如下:

[html] view plain copy

  1. Package Groups (1)
  2. Package Group 0 id=127 packageCount=1 name=com.example.cert
  3. Package 0 id=127 name=com.example.cert typeCount=12
  4. ......
  5. type 9 configCount=56 entryCount=16
  6. spec resource 0x7f0a000b com.example.cert:string/abc_shareactionprovider_share_with_application: flags=0x00000004
  7. spec resource 0x7f0a000c com.example.cert:string/abc_shareactionprovider_share_with: flags=0x00000004
  8. spec resource 0x7f0a000d com.example.cert:string/app_name: flags=0x00000000
  9. spec resource 0x7f0a000e com.example.cert:string/hello_world: flags=0x00000000
  10. spec resource 0x7f0a000f com.example.cert:string/action_settings: flags=0x00000000
  11. config (default):
  12. resource 0x7f0a000b com.example.cert:string/abc_shareactionprovider_share_with_application: t=0x03 d=0x00000162 (s=0x0008 r=0x00)
  13. resource 0x7f0a000c com.example.cert:string/abc_shareactionprovider_share_with: t=0x03 d=0x0000015b (s=0x0008 r=0x00)
  14. resource 0x7f0a000d com.example.cert:string/app_name: t=0x03 d=0x00000154 (s=0x0008 r=0x00)
  15. resource 0x7f0a000e com.example.cert:string/hello_world: t=0x03 d=0x00000152 (s=0x0008 r=0x00)
  16. resource 0x7f0a000f com.example.cert:string/action_settings: t=0x03 d=0x00000155 (s=0x0008 r=0x00)
  17. config ca:
  18. resource 0x7f0a000b com.example.cert:string/abc_shareactionprovider_share_with_application: t=0x03 d=0x00000197 (s=0x0008 r=0x00)
  19. resource 0x7f0a000c com.example.cert:string/abc_shareactionprovider_share_with: t=0x03 d=0x0000019f (s=0x0008 r=0x00)
  20. config da:
  21. resource 0x7f0a000b com.example.cert:string/abc_shareactionprovider_share_with_application: t=0x03 d=0x000001bb (s=0x0008 r=0x00)
  22. resource 0x7f0a000c com.example.cert:string/abc_shareactionprovider_share_with: t=0x03 d=0x000001bc (s=0x0008 r=0x00)
  23. config fa:
  24. resource 0x7f0a000b com.example.cert:string/abc_shareactionprovider_share_with_application: t=0x03 d=0x000001fe (s=0x0008 r=0x00)
  25. resource 0x7f0a000c com.example.cert:string/abc_shareactionprovider_share_with: t=0x03 d=0x000001fb (s=0x0008 r=0x00)
  26. config ja:
  27. resource 0x7f0a000b com.example.cert:string/abc_shareactionprovider_share_with_application: t=0x03 d=0x00000287 (s=0x0008 r=0x00)
  28. resource 0x7f0a000c com.example.cert:string/abc_shareactionprovider_share_with: t=0x03 d=0x00000280 (s=0x0008 r=0x00)
  29. ......

代码段11

从上面的输出可以看到,只有一个package,id是127即0x7F,name是com.example.cert,与之前看到的一样,typeCount有12个即一共有12个ResTable_typeSpec结构体。字符串string类型的资源类型id是0x0A(上面那个type 9是因为aapt工具从0开始数的,而ResTable_typeSpec里的id是从1开始数的)。

后面我们看到spec resource的字样,这个spec就和我们前面介绍的Type Spec是一样的了,这几行就代表它是类型规范数据块了,然后后面是Config List,有很多config,每个config是一个维度,里面对每个资源项都有自己的配置信息,aapt输出config的格式如下:

[html] view plain copy

  1. resource <Resource ID> <Package Name>:<Type>/<Name>: t=<DataType> d=<Data> (s=<Size> r=<Res0>)
  2. Resource ID R.java中的资源ID
  3. Package Name 资源所在的的包
  4. Type 资源的类型
  5. Name 资源名称
  6. DataType 数据类型,按照以下枚举类型取值
  7. Data 资源的值,根据dataType进行解释
  8. Size 一直为0x0008
  9. Res0 固定为0x00

因为hellow_world和app_name这些字符串是我自己demo用的,没有配置多维度,所以只有在config default里面才有,其它维度没有。下面我们以hello_world为例,来定位它在resources.arsc文件中的位置。hello_world的id值是0x7F0A000E,分解以后package id是0x7F,Type Spec的id是0x0A,entry id是0x0E。

这里面因为只有一个Package,所以就不需要去定位Package了。

从图2中我们没找到对Type Spec有索引数组,所以我们需要去一个一个的找Type Spec,从第一个ResTable_typeSpec开始,在ResChunk_header中有整个chunk的大小,一个一个的将收地址加上这个大小就可以了,直到我们找到chunk的类型是0x0202,id是0x0A的ResTable_typeSpec结构体就可以了,下图是示例对应的二进制数据:

图9

可以看到ResTable_typeSpec后面是0x00000004,这个就是代码段11中每行后面的flags,具体含义还不清楚。entryCount是0x10,有16个string类型的资源项。然后下面我们去找第0x0E个ResTable_entry结构体,当然不一定Config List中每个维度都会有,但是default默认的config中肯定是有的。首先找到第一个ResTable_type结构体,方法就是ResTable_typeSpec的起始地址0x11C7C加上这个chunk的大小0x50,位置在0x11CCC,数据如下图:

图10

可以看到entryCount是0x10,entriesStart是0x78,将起始地址与这个偏移相加就可以得到ResTable_entry的开始地址是0x11D44。

hello_world的entry id是0x0E,那么我们从紧接着ResTable_type的entry数组中找第0x0E个元素,它的值是0xE0,加上ResTable_entry的起始地址,得到我们要找的hello_world对应的ResTable_entry的地址是0x11E24,对应内容如下图:

图11

可以看到size是0x08,flags是0,也就是说它是ResTable_entry结构,后面跟一个ResTable_value结构,再看key的值是0x0152,即资源项名称在Key String Pool的字符串偏移数组的索引是第0x0152个,找到以后如下图,具体方法我就不啰嗦了,就是偏移数组收地址加上那个0x0152的时候别忘了这个0x0152要先乘以4,因为数组元素是4字节大小嘛。

图12

然后再看图11中ResTable_value的内容,size是0x08,type是0x03对应TYPE_STRING类型,data值是0x0152,这个就是全局字符串池的字符串偏移数组的索引了,找到以后如下图:

图13

可以看到对应的字符串值就找到了!!

五、总结

啰嗦了这么多,好累。。。不过大概搞明白了,算是自己的笔记吧,同时也分享给大家,我相信这个够详细了!只不过里面很多16进制的地址,还有乱七八糟的数据结构,看着肯定头大,所以不能光看,还是要动手去做,这样才能明白的透彻,否则肯定看不下来。resources.arsc文件的格式还是很简单的,说白了就是一个索引文件。稍后我会写一个解析这个文件的C++程序练手,加深印象,写完以后我会放到github上并把链接放出来。

参考文章:

1. http://www.freebuf.com/articles/terminal/75944.html

2. http://blog.csdn.net/jiangwei0910410003/article/details/50628894

3. http://blog.csdn.net/luoshengyang/article/details/8744683

4. http://blog.csdn.net/mldxs/article/details/44956911

时间: 2024-10-04 05:39:55

手把手教你解析Resources.arsc的相关文章

Android开发之手把手教你写ButterKnife框架(二)

欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52664112 本文出自:[余志强的博客] 上一篇博客Android开发之手把手教你写ButterKnife框架(一)我们讲了ButterKnife是什么.ButterKnife的作用和功能介绍以及ButterKnife的实现原理. 本篇博客主要讲在android studio中如何使用apt. 一.新建个项目, 然后创建一个module名叫processor 新建m

Android消息推送:手把手教你集成小米推送

前言 在Android开发中,消息推送功能的使用非常常见. 为了降低开发成本,使用第三方推送是现今较为流行的解决方案. 今天,我将手把手教大家如何在你的应用里集成小米推送 该文档基于小米推送官方Demo,并给出简易推送Demo 看该文档前,请先阅读我写的另外两篇文章: 史上最全解析Android消息推送解决方案 Android推送:第三方消息推送平台详细解析 目录 1. 官方Demo解析 首先,我们先对小米官方的推送Demo进行解析. 请先到官网下载官方Demo和SDK说明文档 1.1 Demo

手把手教你做关键词匹配项目(搜索引擎)---- 第三天

第三天 小王(运营总监)看到小丁丁整天都在淘宝.百度.魔方.拍拍上面淘关键词,每天花费的时间好长,工作效率又低,拿着这个借口来找到我. 说到:小帅帅,你看小丁丁每天都在淘宝.百度.魔方.拍拍上面淘关键词花费的时间好长,你能不能帮帮忙,看看能不能让系统自己做啦,这样可以节省好多人力,带来的效益多高.(0 其实就是为了掩饰他们懒惰 0) 小帅帅一听到可以带来的效益好高,王总还求着我呢 ,马上 两眼冒着星光,是该好好体现, 解决这个问题就可以体现出我的价值. 小帅帅拍着胸膛保证到:王总,这个小KS啦,

手把手教你安装Hbase,一次成功!

安装环境: OS: Centos 6.5 JDK: jdk1.6.0_18 Hadoop: hadoop-0.20.2 Hbase: hbase-0.90.5 安装准备: 1.       Jdk环境已安装:版本为1.6以上 2.       hadoop环境已安装:完全分布模式安装如下 http://blog.csdn.net/lichangzai/article/details/8206834 3.       hbase版本选择 Hbase 版本必需与 Hadoop版本匹配,否则会安装失败

手把手教你怎样免费注冊国际顶级域名

手把手教你怎样申请国际顶级域名(新奇的,快到快强,过时无效不负责)申请域名地址:https://www.domaindiscover.com/第一步:敲入 https://www.domaindiscover.com/ 网址,在页面内输入你想要申请的域名地址.我这里以注冊wzsoft.info为样例教大家怎样申请到自己的顶级域名.第二步:输入wzsoft后系统自己主动搜索到左右能够能注冊的wzsoft的顶级域名列表,假设列表中哪一个没有,就表示已经被人注冊了,比方wzsoft.com就不再列表中

手把手教你nginx基础安装配置

手把手教你nginx基础安装配置! 一.Nginx安装及简单配置: 安装环境和依赖的包: #yum groupinstall "developmenttools" "server platform development" # yum -yinstall pcre-devel #yum install openssl-devel 下载相关nginx源码包: #wgethttp://nginx.org/download/nginx-1.6.2.tar.gz 解压缩源码

Android 自定义ViewGroup手把手教你实现ArcMenu

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37567907 逛eoe发现这样的UI效果,感觉很不错,后来知道github上有这么个开源项目~~~~当然本篇不是教你如何使用这个开源项目,而是教你如何自己通过自定义ViewGroup写这样的效果,自定义ViewGroup也是我的痛楚,嘿嘿,希望以此可以抛砖引玉~~ 效果图: 1.实现思路 通过效果图,会有几个问题: a.动画效果如何实现 可以看出动画是从顶点外外发射的,可能有人

手把手教你调试Linux C++ 代码

手把手教你调试Linux C++ 代码 软件调试本身就是一项相对复杂的活动,他不仅要求调试者有着清晰的思路,而且对调试者本身的技能也有很高的要求.Windows下Visual Studio为我们做了很多的工作,使初学者基本上可以获得一个所见即所得的调试体验,相对来说也比较容易上手.然而在linux平台下,一切都显得有些不同,倒不是说GDB有多难,只是对于习惯了visual studio的人来说刚开始会有些不适应.然而对于那些在windows 平台下使用windbg调试代码的人来说,情况会好很多,

手把手教你:解决python UnicodeDecodeError: &#39;gb2312&#39; codec can&#39;t decode问题

问题:UnicodeDecodeError: 'gb2312' codec can't decode bytes in position 2-3: illegal multibyte sequence 原因:python在做将普通字符串转换为unicode对象时, 例如:u_string = unicode(string , "gb2312"),如果你的字符串string中有诸如某些繁体字,例如"河滘小学" 中的滘,那么gb2312作为简体中文编码是不能进行解析的,