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

resources.arsc是Android应用资源的关键文件,它在Apk打包过程中由aapt生成,用于根据配置变化索引相应资源。本文详细介绍了资源打包过程、resources.arsc的结构和作用,以及如何分析它。资源被编译为二进制XML,通过资源ID和AssetManager访问。resources.arsc包含资源索引表和字符串池,以及包数据,如bag和非bag类型的资源,解析其格式对于理解Android资源管理至关重要。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

资源打包过程简述

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

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

资源打包.jpg

从上图可以看出:

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

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

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

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

  5. 应用程序在运行时最终是通过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类型的属性资源。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="custom_orientation">
        <enum name="custom_vertical" value="100" />
        <enum name="custom_horizontal" value="200" />
    </attr>
</resources>

这个文件定义了一个名称为“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 = 1<<16)

第二个bag:名称是“custom_vertical”,值是100

第三个bag: 名称是“custom_horizontal”,值是200

另外还要给这个bag分配资源ID,因为这些枚举值是通过名称,例如custom_vertical被引用使用的,所以也要给其分配资源ID,

资源ID的格式是PPTTEEEE,其中TT代表资源类型。那么:

名称是“^type”的bag其分配的资源ID是attr类型的,而“custom_vertical”和“custom_horizontal”被分配到的资源ID是id类型的,所以在代码中可以通过下面的形式引用其值:

     R.attr.custom_orientation;
     R.id.custom_horizontal;
     R.id.custom_vertical;

非bag资源:通俗的说,就是这类资源赋值的时候,很随意,可以任意指定。

以res/values/strings.xml为例:

<resources>
    <string name="app_name">ResourceDemo</string>
</resources>

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

将这个ResourceDemo工程编译之后,解压APK,就可以得到resources.arsc文件了。

要做什么

接下来就是分析resources.arsc,看看前面我们指出的那三个bag资源和一个非bag资源是以什么样的形式存储在resources.arsc的什么位置的。

只要搞清楚了这个,那么就沉底搞清楚resources.arsc文件的格式了。

resources.arsc

resources.arsc文件的作用就是通过一样的ID,根据不同的配置索引到最佳的资源显示在UI中。

从整体上来看,其结构为:资源索引表头部+字符串资源池+N个Package数据块。文件格式:

AMS-33.png

这张神图在网上广为流传,但是其下半部分很容易让人产生误解,因为实际上Type Spec和 Config List是交替出现的,而且一个Type Spec通常有不止一个config list.

不想在画图了,所以仍旧以此神图为模板分析resources.arsc文件吧。

这里不去纠结这个文件是如何生成的,咱们逆其道而行,从文件本身窥探它是有什么组成的。

resources.arsc文件的结构分割符

从上面所示的神图中可以看到arsc文件是由若干种chunk组成的,而每一种chunk都是由一个头部来记录一些相关信息,例如该部分是什么,占多大空间等。

而每种chunk的头部又是在一个基础头部上扩展而来的的,这个基础头部是strcut Resheader:

源码路径:

 AOSP-6.0/frameworks/base/include/androidfw/ResourceTypes.h
struct Resheader
{
    //表示这是一个什么chunk
    uint16_t type;
    //chunk header 大小
    uint16_t headerSize;
    // chunk headr + chunk data,也就是 chunk的总大小
    uint32_t size;
};

每一部分的头部也是一个结构体,这个结构体继承自Resheader(按照C语言来理解,就是结构体的首元素是Resheader)。

在resources.arsc中type的取值有:

RES_NULL_TYPE               = 0x0000,
RES_STRING_POOL_TYPE        = 0x0001,
RES_TABLE_TYPE              = 0x0002,
RES_XML_TYPE                = 0x0003,
// 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

resources.arsc头部

resources.arsc头部,即索引表头部,其结构如下:

struct ResTable_header
{
    struct Resheader header;
    // 该resources.arsc文件中包含几个package资源包,
    // 通常一个app只会包一个package资源包,就是自己
    uint32_t packageCount;
};

这里header.headerSize就是这个struct ResTable_header的大小,header.size是这个resources.arsc文件的大小。而pacakageCount为1.

resources.arsc的文件是一个索引表,是RES_TABLE_TYPE,也就是说header.type为RES_TABLE_TYPE。

代码验证:

struct stat buf;
stat("./resources.arsc", &buf);
int fd = open("./resources.arsc",0644);
uint8_t *data = (uint8_t*)mmap(NULL,buf.st_size,PROT_READ,MAP_PRIVATE,fd,0);
printf("################# res 文件头部信息 #################\n");
ResTable_header *resHd = (ResTable_header*)data;
printf("res type           = %p\n",resHd->header.type);
printf("res chunk hd size  = %p\n",resHd->header.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为例:

<resources>
    <string name="app_name">ResourceDemo</string>
</resources>

该文件中定义了一个名字为“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 = 1<<0,

        // String pool is encoded in UTF-8
        UTF8_FLAG = 1<<8
    };
    // 该字符串是string16还是string8类型
    uint32_t flags;

    //字符数组相对头部的位置
    uint32_t stringsStart;

    //样式数组相对头部的位置
    uint32_t stylesStart;
}

简单介绍这个字符串池如何存储和索引字符串。

从神图中可以看出紧跟着头部的后面是两个uint32类型的数组:字符串偏移数组和样式偏移数组,数组元素个数分别为stringCount和styleCount.

这两个数组后面之后还有两个字符数组:字符串字符数组和与样式字符数组。这两个字符数组都很大很大。。。。

字符串偏移数组中的元素,就是一个字符串在字符串字符数组中的索引,而且根据索引得到的字符串的前面两个字节表示其长度,而且是以NULL结尾的,所以不会索引到其他内容。

样式偏移数组中的元素,就是一个样式在样式字符数组中的索引。这里不考虑样式的情况。有兴趣的可以参考老罗的博

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值