龙空技术网

一文读懂resource.arsc文件结构

闪念基因 1102

前言:

此时看官们对“定义一个数组类型为字符串类型求数组中所有数的和”可能比较关怀,你们都需要了解一些“定义一个数组类型为字符串类型求数组中所有数的和”的相关知识。那么小编在网络上网罗了一些关于“定义一个数组类型为字符串类型求数组中所有数的和””的相关内容,希望咱们能喜欢,兄弟们一起来学习一下吧!

概述

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

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

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

//---------------------------------------------------------------------------//: arsc文件是一个 RES_TABLE_TYPE 类型的chunkRES_TABLE_TYPE {    table_header//文件头部    RES_STRING_POOL_TYPE //常量池chunk    RES_TABLE_PACKAGE_TYPE//内容chunk}//---------------------------------------------------------------------------//:字符串常量池chunkRES_STRING_POOL_TYPE {    pool_header//字符串常量池头部    string[] //常量池}//---------------------------------------------------------------------------//: 内容chunkRES_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 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_TYPE   RES_TABLE_PACKAGE_TYPE      = 0x0200,   RES_TABLE_TYPE_TYPE         = 0x0201,   RES_TABLE_TYPE_SPEC_TYPE    = 0x0202,   RES_TABLE_LIBRARY_TYPE      = 0x0203};
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_TYPEpackageCount : 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-8        UTF8_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_TYPEstringCount : 常量池中的字符串个数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_TYPEid : 包的 ID , 等于 Package Id,一般用户包的 Package Id0X7F , 系统资源包的 Package Id0X01name : 包名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 的结构为例,可以表示成如下结构:

//drawableRES_TABLE_TYPE_SPEC_TYPE{    //drawable-mdpi    RES_TABLE_TYPE_TYPE    //drawable-hdpi    RES_TABLE_TYPE_TYPE{        ResChunk_header//type头部        //具体的资源项数组:资源名->资源值        ResName->ResValue        ResName->ResValue        ResName->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_TYPEid : 表示资源类型 id ,通过这个 id 可以在资源类型常量池中获取资源类型,这个 id 就是 0xPPTTEEEE 中的 TTres0res1 :保留字段,值为0entryCount : 本类型的资源项个数,注意,这里是指名称相同的资源项的个数

资源类型的分析完成后,我们再看看适配类型所用的 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_TYPEreserved : 保留字段,值为0entryCount :本类型的资源项个数,注意,这里是指名称相同的资源项的个数。entriesStart :资源项数据块相对本 chunk 头部的偏移值。config :指向一个ResTable_config,用来描述配置信息(用以区别 Type 是何种适配类型)

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

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

普通资源 : ResTable_entry + Res_valuebag 资源 : 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 : 保留字段,值为0dataType : 当前数据的类型,这个为枚举类型(string、dimension等),具体可以查看 ResourceTypes.hdata : 数据。根据上面的数据类型定,如果类型为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,则等于0    ResTable_ref parent;    // Number of name/value pairs that follow for FLAG_COMPLEX.    //bag项的个数    uint32_t count;};struct ResTable_map{    //bag的资源项ID    ResTable_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 文件内容,完成资源文件名混淆与重复资源优化等。

标签: #定义一个数组类型为字符串类型求数组中所有数的和