resource.arsc文件结构

  • 概述
  • arsc文件作用
  • chunk
  • arsc文件结构
    • Chunk头结构
    • ResTable_header
    • StringPool
    • Package
      • `ResTable_typeSpec` 和 `ResTable_type`
  • 结尾
  • 推荐阅读

概述

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.xxxR.layout.xxxR.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的关系(注意结合图片左右两侧内容):

  1. 整个arsc文件是一个 RES_TABLE_TYPE 类型的chunk
  2. RES_TABLE_TYPE 可分为三个部分:文件头部和两个子chunk( RES_STRING_POOL_TYPERES_TABLE_PACKAGE_TYPE );
  3. RES_TABLE_PACKAGE_TYPE 中包含了:头部、资源类型字符串常量池、资源项名称字符串常量池、多个子chunk(RES_TABLE_TYPE_SPEC_TYPERES_TABLE_TYPE_TYPE );
  4. 每种类型的chunk都含有一个头结构

arsc文件的结构大致可以用如下的伪代码表示:

//---------------------------------------------------------------------------
//: arsc文件是一个 RES_TABLE_TYPE 类型的chunk
RES_TABLE_TYPE {table_header//文件头部RES_STRING_POOL_TYPE //常量池chunkRES_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资源类型chunkRES_TABLE_TYPE_SPEC_TYPE{spec_header//spec头部//drawable-mdpiRES_TABLE_TYPE_TYPE//drawable-hdpiRES_TABLE_TYPE_TYPE...}//attr资源类型chunkRES_TABLE_TYPE_SPEC_TYPE{RES_TABLE_TYPE_TYPERES_TABLE_TYPE_TYPE{type_header//type头部//具体的资源项池:资源名:资源值ResName:ResValueResName:ResValueResName:ResValueResName: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 you to find its associated data// (if any).uint16_t headerSize;// Total size of this chunk (in bytes).  This is the chunkSize plus// the size of any data associated with the chunk.  Adding this value// to the chunk allows you to completely skip its contents (including// any child chunks).  If this value is the same as chunkSize, there is// no data associated with the chunk.uint32_t size;
};

uint16_t: 16位无符号整形(2字节)、uint32_t:32位无符号整形(4字节)

结构分析

  • type : chunk块的类型,部分定义如下:
enum {RES_NULL_TYPE               = 0x0000,RES_STRING_POOL_TYPE        = 0x0001,RES_TABLE_TYPE              = 0x0002,// Chunk types in RES_TABLE_TYPERES_TABLE_PACKAGE_TYPE      = 0x0200,RES_TABLE_TYPE_TYPE         = 0x0201,RES_TABLE_TYPE_SPEC_TYPE    = 0x0202,RES_TABLE_LIBRARY_TYPE      = 0x0203
};
  • headerSize : chunk头部大小
  • size : 所在chunk块的大小

ResTable_header

首先,文件头部是一个ResTable_header结构:

struct ResTable_header
{struct ResChunk_header header;// The number of ResTable_package structures.uint32_t packageCount;
};

结构分析:

  • header : ResChunk_header类型,其中typeRES_TABLE_TYPE
  • packageCount : arsc文件中ResTablePackage的个数,通常是 1。

所以头部结构如下:

StringPool

接着是字符串资源池chunk,它的结构如下图:
    字符串常量池存放了APK中所有的字符串资源的内容,这个chunk由图中的五个部分组成:

  • ResStringPool_header : 字符串常量池常量头部
  • String Offset Array : 字符串偏移数组,数组中的每个元素记录一条字符串在此常量池中的起始位置的偏移量,没个偏移量大小为4字节,所以此区域的大小为(4 x stringCount)字节
  • Style Offset Array : 字符串样式偏移数组
  • String Content : 字符串常量池内容区域,池中的每个字符串元素末尾含有一个字符串结束符
  • Style Content : 字符串样式内容区域

我们主要关心:ResStringPool_headerString Offset ArrayString Content

首先分析字符串常量池的头部,这个头部是一个ResStringPool_header结构:

struct ResStringPool_header
{struct ResChunk_header header;// Number of strings in this pool (number of uint32_t indices that follow// in the data).uint32_t stringCount;// Number of style span arrays in the pool (number of uint32_t indices// follow the string indices).uint32_t styleCount;// Flags.enum {// If set, the string index is sorted by the string values (based// on strcmp16()).SORTED_FLAG = 1<<0,// String pool is encoded in UTF-8UTF8_FLAG = 1<<8};uint32_t flags;// Index from header of the string data.uint32_t stringsStart;// Index from header of the style data.uint32_t stylesStart;
};

结构分析:

  • header : ResChunkHeader,其中typeRES_STRING_POOL_TYPE
  • stringCount : 常量池中的字符串个数
  • styleCount : 常量池中字符串样式个数
  • flags : 等于0、SORTED_FLAGUTF8_FLAG或者它们的组合值,用来描述字符串资源串的属性,例如,SORTED_FLAG位等于1表示字符串是经过排序的,而UTF8_FLAG位等于1表示字符串是使用UTF8编码的,否则就是UTF16编码的
  • stringsStart : 字符串内容与常量池头部起始点之间的偏移距离
  • stylesStart : 字符串样式内容与常量池头部起始点之间的偏移距离

Package

最后,分析Package,这个chunk以一个ResTable_package结构开始:

/*** A collection of resource data types within a package.  Followed by* one or more ResTable_type and ResTable_typeSpec structures containing the* entry values for each resource type.*/
struct ResTable_package
{struct ResChunk_header header;// If this is a base package, its ID.  Package IDs start// at 1 (corresponding to the value of the package bits in a// resource identifier).  0 means this is not a base package.uint32_t id;// Actual name of this package, \0-terminated.uint16_t name[128];// Offset to a ResStringPool_header defining the resource// type symbol table.  If zero, this package is inheriting from// another base package (overriding specific values in it).uint32_t typeStrings;// Last index into typeStrings that is for public use by others.uint32_t lastPublicType;// Offset to a ResStringPool_header defining the resource// key symbol table.  If zero, this package is inheriting from// another base package (overriding specific values in it).uint32_t keyStrings;// Last index into keyStrings that is for public use by others.uint32_t lastPublicKey;uint32_t typeIdOffset;
};

结构分析:

  • header : 类型为ResChunk_header , 其typeRES_TABLE_PACKAGE_TYPE
  • id : 包的ID, 等于 Package Id,一般用户包的Package Id0X7F, 系统资源包的 Package Id0X01
  • name : 包名
  • typeStrings :资源类型字符串资源池相对头部的偏移位置
  • lastPublicType : 类型字符串资源池的大小
  • keyStrings : 资源项字符串相对头部的偏移位置
  • lastPublicKey : 一资源项名称字符串资源池的大小
  • typeIdOffset : 未知,值为 0

上述结构中的typeStringskeyStrings中,提到了资源类型字符串常量池与资源项名称常量池,这两个字符串常量池的结构也是ResStringPool,他们的位置紧随ResTable_package之后,分别是Type String PoolType String Pool。通过下图可以看到ResTable_package与这两个字符串常量池的位置关系:

    加上之前的字符串常量池,在整个arsc文件中一共有三个字符串常量池:字符串资源常量池、资源类型字符串常量池、资源项名称字符串常量池。

比如:

<string name="tip">hello world</string>

表示一个资源类型为string,名字为tip,值为hello world的资源。

  • hello world字符串资源,存储在 字符串资源 常量池中;
  • string资源类型 ,存储在 资源类型 字符串常量池中;
  • tip资源项名称 ,存储在 资源项名称 字符串常量池中;

当资源为R.drawable.img时,资源类型为drawable、资源项名称为imgR.drawable.img资源所对应的文件路径存储则在 字符串资源 中。

ResTable_typeSpecResTable_type

文章开头说讲arsc是一个由多个Pair[]组成的文件,每种资源类型(animattrdrawablestring等)对应一个Pair[],这个Pair[]就是接下来要讲到的ResTable_typeSpecResTable_type

实际上在arsc文件中,每种资源类型对应一个ResTable_typeSpec,它用来描述资源项的配置差异性,每个ResTable_typeSpec头部、一个或多个 ResTable_type 组成,ResTable_type的数量由适配类型数目决定,例如:drawable、drawable-mdpi、drawable-hdpi等每种适配类型对应一个ResTable_type。而每个ResTable_type则由一个 头部 和一个 资源项数组 构成,这个资源项数组就是上面提到的Pair[]

drawableResTable_typeSpecResTable_type的结构为例,可以表示成如下结构:

//drawable
RES_TABLE_TYPE_SPEC_TYPE{//drawable-mdpiRES_TABLE_TYPE_TYPE//drawable-hdpiRES_TABLE_TYPE_TYPE{ResChunk_header//type头部//具体的资源项数组:资源名->资源值ResName->ResValueResName->ResValueResName->ResValue//ResName->ResTableMapEntry//ResName->ResTableMapEntry...}...
}

arsc文件中ResTable_typeSpecResTable_type具体是怎么表示的呢?

首先看ResTable_typeSpec类型:

struct ResTable_typeSpec
{struct ResChunk_header header;// The type identifier this chunk is holding.  Type IDs start// at 1 (corresponding to the value of the type bits in a// resource identifier).  0 is invalid.uint8_t id;// Must be 0.uint8_t res0;// Must be 0.uint16_t res1;// Number of uint32_t entry configuration masks that follow.uint32_t entryCount;enum : uint32_t {// Additional flag indicating an entry is public.SPEC_PUBLIC = 0x40000000u,// Additional flag indicating an entry is overlayable at runtime.// Added in Android-P.SPEC_OVERLAYABLE = 0x80000000u,};
};

结构分析:

  • header: 头部,type等于RES_TABLE_TYPE_SPEC_TYPE
  • id : 表示资源类型id,通过这个id可以在资源类型常量池中获取资源类型,这个id就是0xPPTTEEEE中的TT
  • res0res1:保留字段,值为0
  • entryCount : 本类型的资源项个数,注意,这里是指名称相同的资源项的个数

资源类型的分析完成后,我们再看看适配类型所用的ResTable_type以及具体的资源项。

依然是从其头部开始分析:

struct ResTable_type
{struct ResChunk_header header;enum {NO_ENTRY = 0xFFFFFFFF};// The type identifier this chunk is holding.  Type IDs start// at 1 (corresponding to the value of the type bits in a// resource identifier).  0 is invalid.uint8_t id;enum {// If set, the entry is sparse, and encodes both the entry ID and offset into each entry,// and a binary search is used to find the key. Only available on platforms >= O.// Mark any types that use this with a v26 qualifier to prevent runtime issues on older// platforms.FLAG_SPARSE = 0x01,};uint8_t flags;// Must be 0.uint16_t reserved;// Number of uint32_t entry indices that follow.uint32_t entryCount;// Offset from header where ResTable_entry data starts.uint32_t entriesStart;// Configuration this collection of entries is designed for. This must always be last.ResTable_config config;
};

结构分析:

  • header : ResChunk_header类型,其中type等于RES_TABLE_TYPE_TYPE
  • reserved : 保留字段,值为0
  • entryCount :本类型的资源项个数,注意,这里是指名称相同的资源项的个数。
  • entriesStart:资源项数据块相对本chunk头部的偏移值。
  • config:指向一个ResTable_config,用来描述配置信息(用以区别Type是何种适配类型)

紧随其后的是资源项池(一个资源项数组)到底是如何存储具体的资源的.

资源项池中的资源项的存储方式有两种,分别如下:

  • 普通资源 : ResTable_entry + Res_value
  • bag资源 :ResTable_entry + ResTable_map_entry + Res_Table_map * n

其中, ResTable_entry指向资源项名称,并标识此资源是否为一个bag资源; Res_valueRes_Table_map指向具体的资源,两种资源类型的具体存储方式如下图所示:

最后再一起了解一下ResTable_entryRes_valueResTable_map_entry的内部结构。

先看ResTable_entry

struct ResTable_entry
{// Number of bytes in this structure.uint16_t size;enum {// If set, this is a complex entry, holding a set of name/value// mappings.  It is followed by an array of ResTable_map structures.FLAG_COMPLEX = 0x0001,// If set, this resource has been declared public, so libraries// are allowed to reference it.FLAG_PUBLIC = 0x0002,// If set, this is a weak resource and may be overriden by strong// resources of the same name/type. This is only useful during// linking with other resource tables.FLAG_WEAK = 0x0004};uint16_t flags;// Reference into ResTable_package::keyStrings identifying this entry.struct ResStringPool_ref key;
};

结构分析:

  • size:资源项头部大小。
  • flags:资源项标志位。flags = FLAG_COMPLEX表示此资源为Bag资源项,并且在ResTable_entry后紧随ResTable_map数组表示资源项内容,否则的话,在ResTable_entry后紧随Res_value : 资源项内容。如果是一个可以被引用的资源项,那么FLAG_PUBLIC位就等于1。
  • key:__资源项名称__在资源项名称字符串资源池的索引。

资源项名称在ResTable_entry中已经找到了,接着看资源值Res_Value:

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.typedef uint32_t data_type;data_type data;
};

结构分析:

  • size: Res_value的大小
  • res0: 保留字段,值为0
  • dataType : 当前数据的类型,这个为枚举类型(string、dimension等),具体可以查看ResourceTypes.h
  • data : 数据。根据上面的数据类型定,如果类型为string,则当前的值为字符串资源池中的索引

最后看看bag资源的存储结构的具体内容(显然bag资源的存储结构已经不满足我们上述说的 Pair对象,Pair对象的引入只是帮助我们理解非bag资源的存储结构),ResTable_map_entryResTable_ref

struct ResTable_map_entry : public ResTable_entry
{// Resource identifier of the parent mapping, or 0 if there is none.//父ResTable_map_entry的资源ID,如果没有父ResTable_map_entry,则等于0ResTable_ref parent;// Number of name/value pairs that follow for FLAG_COMPLEX.//bag项的个数uint32_t count;
};struct ResTable_map
{//bag的资源项IDResTable_ref name;// This mapping's value.//bag的资源项值Res_value value;
};struct ResTable_ref
{uint32_t ident;
};

至此,已完成resource.arsc问价的分析。

结尾

arsc文件的结构总体并不算非常复杂,android-chunk-utils是一个用java编写的arsc文件解析工具,通过该工具可以帮助理解arsc文件的结构,同时通过该工具也可以更改arsc文件内容,完成资源文件名混淆与重复资源优化等。

推荐阅读

1 、ResourceTypes.h

2、Android应用程序资源的编译和打包过程分析

3、Android 手把手分析resources.arsc

4、Android 逆向笔记 —— ARSC 文件格式解析

一文读懂resource.arsc文件结构相关推荐

  1. 一文读懂Java中File类、字节流、字符流、转换流

    一文读懂Java中File类.字节流.字符流.转换流 第一章 递归:File类: 1.1:概述 java.io.File 类是文件和目录路径名的抽象表示,主要用于文件和目录的创建.查找和删除等操作. ...

  2. 从实验室走向大众,一文读懂Nanopore测序技术的发展及应用

    关键词/Nanopore测序技术    文/基因慧 随着基因测序技术不断突破,二代测序的发展也将基因检测成本大幅降低.理想的测序方法,是对原始DNA模板进行直接.准确的测序,消除PCR扩增带来的偏差, ...

  3. 一文读懂Faster RCNN

    来源:信息网络工程研究中心本文约7500字,建议阅读10+分钟 本文从四个切入点为你介绍Faster R-CNN网络. 经过R-CNN和Fast RCNN的积淀,Ross B. Girshick在20 ...

  4. 福利 | 一文读懂系列文章精选集发布啦!

    大数据时代已经悄然到来,越来越多的人希望学习一定的数据思维和技能来武装自己,虽然各种介绍大数据技术的文章每天都扑面而来,但纷繁又零散的知识常常让我们不知该从何入手:同时,为了感谢和回馈读者朋友对数据派 ...

  5. ​一文读懂EfficientDet

    一文读懂EfficientDet. 今年年初Google Brain团队在 CVPR 2020 上发布了 EfficientDet目标检测模型, EfficientDet是一系列可扩展的高效的目标检测 ...

  6. 一文读懂序列建模(deeplearning.ai)之序列模型与注意力机制

    https://www.toutiao.com/a6663809864260649485/ 作者:Pulkit Sharma,2019年1月21日 翻译:陈之炎 校对:丁楠雅 本文约11000字,建议 ...

  7. AI洞观 | 一文读懂英特尔的AI之路

    AI洞观 | 一文读懂英特尔的AI之路 https://mp.weixin.qq.com/s/E9NqeywzQ4H2XCFFOFcKXw 11月13日-14日,英特尔人工智能大会(AIDC)在北京召 ...

  8. 一文读懂机器学习中的模型偏差

    一文读懂机器学习中的模型偏差 http://blog.sina.com.cn/s/blog_cfa68e330102yz2c.html 在人工智能(AI)和机器学习(ML)领域,将预测模型参与决策过程 ...

  9. 一文读懂AI简史:当年各国烧钱许下的愿,有些至今仍未实现

    一文读懂AI简史:当年各国烧钱许下的愿,有些至今仍未实现 导读:近日,马云.马化腾.李彦宏等互联网大佬纷纷亮相2018世界人工智能大会,并登台演讲.关于人工智能的现状与未来,他们提出了各自的观点,也引 ...

最新文章

  1. 淘宝宣布改名,网友:改了个寂寞?
  2. 微软服务器离线补丁工具包,wsus offline update
  3. 大数据之-Hadoop伪分布式_Log日志查看和NN格式化前强调---大数据之hadoop工作笔记0024
  4. 20145234黄斐《信息安全系统设计基础》第十周
  5. 反射注解知识点复习(第一次)
  6. .NET 常用ORM之SubSonic
  7. 超赞!设计师完全自学指南
  8. 数字化背景下的经济社会发展的新特征 新趋势
  9. 纳米壳聚糖骨形成蛋白水凝胶/壳聚糖/蒙脱土纳米复合水凝胶/甘草多糖壳聚糖水凝胶的制备
  10. 商业银行vh是哪个银行的简称_各个银行缩写是什么
  11. 人一生要读的60本书(经典读书计划)
  12. 文件管理,文件判断,时间戳,通配符类命令
  13. 南卡A2降噪耳机开箱测评:降噪实力派
  14. 制作使用天空盒(Skyboxes)
  15. AtCoder Beginner Contest 165 D Floor Function 公式推导
  16. “牛市”惊涛骇浪中的股友们
  17. 电动车进入电梯自动监控识别系统-楚纳
  18. Ext4 Project Quota磁盘配额使用介绍
  19. Ubuntu基本操作命令
  20. 如何将Android应用发布到Google Play(Android Market)官方市场

热门文章

  1. 【老生谈算法】MATLAB实现车间作业调度问题(JSP)遗传算法通用源码——JSP
  2. vue+css3动画开发发牌、翻牌效果
  3. 人工智能时代下的数据安全治理
  4. 第二章 定义和构建索引(二)
  5. 网络地址192.168.10.0;子网掩码255.255.255.128 计算
  6. 基于单片机太阳能自动双轴追光电路仿真系统(毕设课设)
  7. 服务器 ts250是什么系统,ts250服务器恢复系统
  8. 3分钟带你了解分时图的7种基本形态!
  9. 致诸弟·劝弟谨记进德修业
  10. 量化投资学习-15:散户与庄家共赢策略之价值长线策略