Android 编译打包的那些疑问

栏目: Android · 发布时间: 5年前

内容简介:我们平时都是用 AS 进行打包,这就造成了很多盲点,我们就来看看究竟是咋回事,提前声明这篇文章讲的不全,只讲一些疑惑盲点,需要全面学习的,看老罗的吧,详细的令人发指。我们从结果入手,看看打包完毕的apk里面是啥模样,把.apk 修改成 .zip 解压缩。按图索骥哈哈。一共五个文件,第一个 AndroidManifest.xml 我们很熟悉了。打开看一下。

我们平时都是用 AS 进行打包,这就造成了很多盲点,我们就来看看究竟是咋回事,提前声明这篇文章讲的不全,只讲一些疑惑盲点,需要全面学习的,看老罗的吧,详细的令人发指。

我们从结果入手,看看打包完毕的apk里面是啥模样,把.apk 修改成 .zip 解压缩。按图索骥哈哈。

Android 编译打包的那些疑问

一共五个文件,第一个 AndroidManifest.xml 我们很熟悉了。打开看一下。

Android 编译打包的那些疑问

我的个乖乖,咋回事。 然后在打开 res 文件,很多维度的 drawable 文件夹啊。

Android 编译打包的那些疑问

随便打开一个吧,看看里面的 xml 文件,nm,咋也都是二进制数据了呢?

Android 编译打包的那些疑问

好吧,这些貌似是和 资源编译打包有关系哈,印象里好像是这么回事,赶紧百度,谷歌。看到了如下的神图貌似和我们遇到的类似啊。

Android 编译打包的那些疑问
Android 编译打包的那些疑问

aapt 这家伙,把我们的资源搞了一下。他想干什么呢?带着疑问我们去学习。

1、为啥要把文本格式的xml 转换成 二进制格式的

为啥呢?如果猜测的话,应该不是闲的没事搞得,我们知道安卓面临的问题一直是空间和时间的问题,这么搞,无非就是省空间,提升速度。

2、怎么做呢?

把所有 xml 元素的标签,属性,内容等字符串统一放到一个 字符串资源池里面去,然后在用到这个字符串的地方用索引来代替,这是偷懒的行家啊,也是计算机里的局部思想的发扬光大。这样就可以解决空间占用的大小了。 那么怎么就速度快了呢?因为这里的字符串用的是索引,所以就不必每次都解析字符串了,这还不快吗?重复利用多开心啊。

ok,这个 xml 二进制的问题也就解决完毕。但是一个问题的结束往往伴随着另一个问题的开始。那个字符串资源池在哪里呢?

这就引出了我们的上面 五大部分的 Resources.arsc。先容我百度,谷歌下哈。 blog.csdn.net/beyond702/a… 很显然我不是要解析 Resources.arsc,反正我知道了,这个字符串资源池就在 Resources.arsc 中,名字叫 Global String Pool,这样就可以了。

好了回到 aapt 这个家伙。

据网上总结他有以下几个重要的工作。

1、 assert 和 res/raw 目录下的所有资源原封不动打包到 apk 里。 2、对 res/ 目录下的文件进行编译处理 比如 xml 编译成二进制文件,png 等图片也会进行优化处理。 3、除了 assert 资源之外的所有资源都会赋予一个资源ID常量,并且声称一个资源索引表 Resources.arsc。 4、把 AndroidManifest.xml 也进行二进制处理。 5、把上面四步骤中声称的结果保存到一个*.ap_ 文件,把各个资源 ID 常量定义在 R.java 文件中。

这么一来解答了不少疑惑,但是 *.ap_ 是个啥玩意呢?下图是网上的,说实话,我反正没看到,我实验了下没有 .ap 文件。咋办呢?继续搜吧,可能是文章有点老了。

Android 编译打包的那些疑问

还是看官方文档吧,我擦,appt2 了啊,好吧,看英文文档使我快乐()。文档上说 appt 已经弃用,开始使用 appt2 了,虽然老项目也在用,但是你懂得。

developer.android.com/studio/comm…

Android 编译打包的那些疑问

终于找到了一篇文章,讲述 appt2 编译的。

www.colabug.com/1787983.htm…

原来是 appt2 将原来的资源编译打包过程拆分成了两部分,编译和链接,提高了资源的编译性能。当只有一个资源发上改变的时候,只需要重新编译改变的文件就行了。其他不变的进行链接就行了。之前 appt 是将所有的资源进行merge ,merge 完毕重新对所有资源进行编译,产生一个 资源 ap_ 文件。这个也就是一个压缩包。

Android 编译打包的那些疑问

具体细节大家有兴趣可以搞一搞。我心里还是有点不明白这些个过程,所有又找了一篇文章来看看。

www.jianshu.com/p/d487f0aa9…

这个的图真是详细的很啊。

Android 编译打包的那些疑问

我们要编译的应用程序的资源结构目录。图文结合一下,不然不知道说的什么。

Android 编译打包的那些疑问

第一步:解析AndroidManifest.xml

获得包名,根据包名创建资源表 ResourceTable 。 那什么是 ResourceTable 呢?Android资源打包 工具 在编译应用资源之前,会创建一个资源表,当编译完成后,就可以拿着这个资源表,去生成资源索引文件 resources.arsc。

第二步:添加被引用资源包

不光我们自己的应用拥有资源包,android 系统也定义了一套通用资源。所以需要把这个也添加上。最重要的的 资源ID 的命名规则是这样的。一共四位,Package ID,次高字节表示Type ID,最低两字节表示Entry ID。

Package ID:比如系统的就是0×01 ,我们自己的就是0x7f。

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

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

总结就是 先按包名来分,然后看类型,最后看顺序。

第三步:收集资源文件

在编译应用程序之前,aapt 会创建一个 AaptAsserts 对象。用来收集当前需要编译的资源文件。保存到 AaptAsserts 的成员变量 mRes。

KeyedVector<String8, sp<ResourceTypeSet> >* mRes;

收集资源是按照类型来保存的 分别是 drawable、layout、values。所以对应了三种 ResourceTypeSet。

举个例子说明下吧(其实我就是抄的)

1、类型是 drawalbe 的ResourceTypeSet 只有一个AaptGroup,它的名称是 icon.png。这个AaptGroup 里包含了三个文件 res/drawable-ldpi/icon.png、res/drawable-mdpi/icon.png和res/drawable-hdpi/icon.png。 每一个文件都用一个 AaptFile 来描述,并且都对应一个 AaptGroupEntry。每个 AaptGroupEntry 描述的都是不同的资源配置信息,即他们所描述的屏幕密度是ldpi、mdpi和hdpi。

2、类型是 layout 的的ResourceTypeSet 有两个AaptGroup,分别是 main.xml 和 sub.xml。都只包含了一个 AaptFile ,分别是res/layout/main.xml和res/layout/sub.xml。同样分别对应一个AaptGroupEntry。这两个AaptGroupEntry描述的资源配置信息都是属于default的。

3、类型为 values 的ResourceTypeSet 只有一个 AaptGroup,为 string.xml。包含了一个 AaptFile 即 res/values/strings.xml。同样对应一个AaptGroupEntry,这个AaptGroupEntry描述的资源配置信息也是属于default的。

第四步:将收集到的资源添加到资源表

上一步只是保存到 AaptAsserts 对象里,我们需要将这些资源同时增加到 ResourceTable 对象中,为啥子呢?因为我们要用 ResourceTable 来生成 resources.arsc。这样看来思路就有点清晰了。

需要注意的是: 收集的资源不包括 values 类型的资源,它比较特殊,要经过编译才会添加到资源表中。(ps:又增加了一个问题)

举个例子:

在这个名称为“shy.luo.activity”的Package中,分别包含有drawable和layout两种类型的资源,每一种类型使用一个Type对象来描述,其中:

1、类型是 drawable 的Type,包含一个 ConfigList。名称为 icon.png。包含了三个 Entry,分别是res/drawable-ldpi/icon.png、res/drawable-mdpi/icon.png和res/drawable-hdpi/icon.png。每一个 Entry 对应一个 ConfigDescription,用来描述不同的资源配置信息。

Android 编译打包的那些疑问
Android 编译打包的那些疑问
Android 编译打包的那些疑问

第五步:编译 values 类资源

类型为 values 终于开始要编译了,之前的疑问看来要在这里进行解答了。我们通常用 values 来描述一些简单的值,比如 颜色,大小,尺寸等等。这些资源是在编译的过程中收集的。具体怎么收集看下边。

strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Activity</string>
    <string name="sub_activity">Sub Activity</string>
    <string name="start_in_process">Start sub-activity in process</string>
    <string name="start_in_new_process">Start sub-activity in new process</string>
    <string name="finish">Finish activity</string>
</resources>

这个文件经过编译后,资源表中会多了一个名为 string 的 Type。这个 Tpye 还有五个 ConfigList 。这五个 ConfigList 的名称分别为 “app_name”、“sub_activity”、“start_in_process”、“start_in_new_process”和“finish”,每一个ConfigList又分别含有一个Entry。

Android 编译打包的那些疑问

第六步、给Bag资源分配ID

类型 values的资源除了 string 之外,还会有 bag,style,array 等。统一称为 Bag 资源。比如 Android 系统提供的android:orientation属性的取值范围为{“vertical”、“horizontal”},就相当于是定义了vertical和horizontal两个Bag。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <attr name="custom_orientation">
        <enum name="custom_vertical" value="0" />
        <enum name="custom_horizontal" value="1" />
    </attr>
</resources>
Android 编译打包的那些疑问

看完了 Bag 的解释。我们看看是如何分配的ID的。上面是三个Entry均为 Bag 资源。其中 custom_vertical(id类型资源)和custom_horizontal( id类型资源)是custom_orientation(attr类型资源)的两个bag。我们可以将custom_vertical和custom_horizontal看成是custom_orientation的两个元数据,用来描述custom_orientation的取值范围。实际上,custom_orientation 还有一个内部元数据,用来描述它的类型。这个内部元数据也是通过一个 bag 来表示的,这个 bag 的名称和值,分别是“^type”和TPYE_ENUM ,用来表示他描述的一个枚举类型的属性。注意:所有“^”开头的bag都是表示一个内部元数据。

对于 Bag 资源来说,这一步需要给他们的元数据项分配资源ID,也就是给他们的bag分配资源ID,例如上述的 custom_orientation 来说,我们需要给它的 ^type 、custom_horizontal 和 custom_vertical 分配资源ID。其中 ^type 分配到的是 attr 类型的资源ID,而custom_vertical和custom_horizontal分配的是 id 类型的资源ID。

第七步:编译xml资源文件

前六步都是为了编译xml资源文件做准备。不容易啊。

开始编译:

除了 values 类型的资源文件,其他所有xml资源文件都需要编译。以 main.xml 为例。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" 
    android:gravity="center">
    <Button 
        android:id="@+id/button_start_in_process"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="@string/start_in_process" >
    </Button>
    <Button 
        android:id="@+id/button_start_in_new_process"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="@string/start_in_new_process" >
    </Button>
</LinearLayout>
Android 编译打包的那些疑问

1、解析xml 文件 这个就不多说了,最后得到根节点的 XMLNode。 2、赋予属性名称资源ID。 比如根节点 LinearLayout 里面有“android:orientation”、“android:layout_width”、“android:layout_height”和“android:gravity”都需要赋予一个资源ID。这就给是在系统资源包里定义的。所以AAPT会从系统资源包里找到这些名称对应的资源ID,然后才能赋给main.xml 的根节点LinearLayout。

注意:对于系统资源包来说“android:orientation”、“android:layout_width”、“android:layout_height”等这些属性名称都是它定义的一系列 Bag资源。在被编译的时候就分配好资源ID了。如第六步。

每一个xml文件都是从根节点开始给属性名称赋予资源ID的,然后在递归给每一个子节点的属性名称赋予资源ID。直到都获得为止。 3、解析属性值。 上一步只是对属性的解析,这一步是对属性值的解析。比如 main.xml 文件的根节点 LinearLayout 来说,我们已经给他的属性 android:orientation 赋值了一个资源 ID,这里就是要给他的值 vertical 进行解析。上面说到了,android:orientation 是 Bag 资源,这个 Bag 资源分配有资源 ID,也会有元数据,也就是它的取值。对于 android:orientation 的合法取值就是 horizontal 或者 vertical 。而这两个也是 Bag 资源。他们的值分别被定义为 0 和 1。

AAPT 是如何找到 main.xml ->LinearLayout-> android:orientation->vertical 等于 1 的呢?假设上一步从系统资源找到资源 ID 0x010100c4,那么 AAPT 会找到它的元数据,也就是 名为 horizontal 和 vertical 的 Bag。接着根据字符串匹配到 vertical 的 Bag,最后就可以将这个 Bag 解析了。

讲个很基础的一个东西,平时经常用,但是基本不会注意的一个点,对于引用类型的属性值,比如 android:id属性值“@+id/button_start_in_process”,其中 @ 表示后面描述的属性是因引用类型的。+ 表示如果该引用不存在那么就新建一个。id 表示引用的资源类型是 id。button_start_in_process 是个名称。实际上在 id 之前还可以加 包名,@+[package:]id/button_start_in_process 就是这样的,如果不指定那就从当前的包里查找。

再举个例子,比如 android:text属性值“@string/start_in_process”,在第五步的时候已经编译过了,所以在这里可以直接获取他们的资源 ID。

注意:一个资源项一旦建立之后,要获得它的 ID 是很容易的,因为它的 package id、tpye id和 entry id都是已知的。

4、压平xml 这个词很新鲜啊,第一次听说这么个东西,就是对xml文件的内容进行扁平化处理,实际就是将xml文件格式转换成二进制格式。过程如下图。

Android 编译打包的那些疑问

一共分六步,(mmp好复杂) step 1、收集有资源 ID 的属性的名称字符串 这一步除了会收集 资源 ID的属性的名称字符串之外,还会将对应的资源ID收集到一个数组中,这里收集到的属性名称字符串保存在一个字符串资源池中。他们与收集到的资源ID是一一对应的。也就是下图这样子滴。

Android 编译打包的那些疑问

step 2、收集其他字符串 看到第一步我还在纳闷,怎么收集的字符串就只有属性的呢?原来还有其他的也放入字符串资源池里,不过对于字符不会重复收集,毕竟是字典嘛。

Android 编译打包的那些疑问

step 3、写入XML文件头 最终编译出来的XML二进制文件是一系列的chunk组成,每一个chunk都有一个头部,用来描述chunk的元信息,同时整个xml文件又可以看成一个总的chunk。它有一个类型为 ResXMLTree_header的头部。

struct ResChunk_header
{
    uint16_t type;
    uint16_t headerSize;
    uint32_t size;
};

struct ResXMLTree_header
{
    struct ResChunk_header header;
};

–type:等于RES_XML_TYPE,描述这是一个Xml文件头部。

–headerSize:等于sizeof(ResXMLTree_header),表示头部的大小。

–size:等于整个二进制Xml文件的大小,包括头部headerSize的大小。

step 4、写入字符串资源池 原来定义在xml文件中的字符串已经在 1、2步收集完毕,因此,我们可以将它们写入最终收集到二进制格式的xml文件中。写入的顺序必须严格按照在字符串资源池中的写入顺序。例如,对于main.xml来说,依次写入的字符串为“orientation”、“layout_width”、“layout_height”、“gravity”、“id”、”text”、”android”、“ schemas.android.com/apk/res/and… step 1 收集到的资源 ID 数组也要写入二进制格式的xml中,保持这个资源ID 和字符串资源池对应字符串的对应关系。 写入的字符串池chunk同样也是具有一个头部的,这个头部的类型为ResStringPool_header,它定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:

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;
};

–type:等于RES_STRING_POOL_TYPE,描述这是一个字符串资源池。

–headerSize:等于sizeof(ResStringPool_header),表示头部的大小。

–size:整个字符串chunk的大小,包括头部headerSize的大小。

ResStringPool_header的其余成员变量的值如下所示:

–stringCount:等于字符串的数量。

–styleCount:等于字符串的样式的数量。

–flags:等于0、SORTED_FLAG、UTF8_FLAG或者它们的组合值,用来描述字符串资源串的属性,例如,SORTED_FLAG位等于1表示字符串是经过 排序 的,而UTF8_FLAG位等于1表示字符串是使用UTF8编码的,否则就是UTF16编码的。

–stringsStart:等于字符串内容块相对于其头部的距离。

–stylesStart:等于字符串样式块相对于其头部的距离。

step 6、写入资源ID 这些收集到的资源ID会作为一个单独的chunk写入到最终的xml二进制文件中。这个chunk位于字符串资源池的后面。它的头部使用ResChunk_header来描述。这个ResChunk_header的各个成员变量的取值如下所示:

–type:等于RES_XML_RESOURCE_MAP_TYPE,表示这是一个从字符串资源池到资源ID的映射头部。

–headerSize:等于sizeof(ResChunk_header),表示头部大小。

–size:等于headerSize的大小再加上sizeof(uint32_t) * count,其中,count为收集到的资源ID的个数。

以main.xml为例,字符串资源池的第一个字符串为“orientation”,而在资源ID这个chunk中记录的第一个数据为0x010100c4,那么就表示属性名称字符串“orientation”对应的资源ID为0x010100c4。 step 6、压平xml 压平xml就是将各个xml元素中的字符串都替换掉。要么被替换成字符串资源池的一个索引,要么是被替换成一个具有类型的其他值。我们以main.xml 为例。

首先压平的是一个表示命名空间的xml node。这个Xml Node用两个ResXMLTree_node和两个ResXMLTree_namespaceExt来表示,如图所示:

Android 编译打包的那些疑问

反正就是哔哩哔哩一大堆的约定协议参数,我就不多说了,感兴趣大家就看老罗的文章研究下。反正就是xml各种稀里糊涂的定规矩,解析什么的。

第八步、生成资源符号

这些生成的资源符号为后面生成R.java 做准备。所有的资源项按照类型保存在 ResourceTable对象中,因此 AAPT需要遍历每一个package中的每个tpye,取出每一个 entry。根据这些entry在 type 中的顺序计算他们资源ID。那么就可以生成一个资源符号了。这个资源符号由名称和资源ID组成。

对于strings.xml文件中名称为“start_in_process”的Entry来说,它是一个类型为string的资源项,假设它出现的次序为第3,那么它的资源符号就等于R.string.start_in_process,对应的资源ID就为0x7f050002,其中,高字节0x7f表示Package ID,次高字节0×05表示string的Type ID,而低两字节0×02就表示“start_in_process”是第三个出现的字符串。

第九步、生成资源索引表

经过上面八个步骤,终于获得了资源列表,有了这个 aapt 就可以按照下面的流程生成 资源索引表 resources.arsc。

Android 编译打包的那些疑问

感觉又是很多步骤,真实心累的一批哈,看老罗的文章真的是想睡啊~~

Android 编译打包的那些疑问

上面我们压平了xml,基本已经完成了一半的任务了,剩下一般就是生成这个resources.arsc 文件,估计又是一大堆的规则。知道大概意思就行了,到了需要的时候再仔细研究就好了。

step 1、收集类型字符串

一共有四种类型分别是 drawable 、layout、string 和 id。对应的类型字符串也是drawable 、layout、string 和 id。注意这些字符串是按照报名 package 来收集的。有几个报名就有几组对应的类型字符串。

step 2、收集资源项 名称 字符串 比如上面的例子中有 12 个资源项,“icon”、“icon”、“icon”、“main”、“sub”、“app_name”、“sub_activity”、“start_in_process”、“start_in_new_process”、“finish”、“button_start_in_process”和“button_start_in_new_process” 对应的名称字符串也是它们。对的这个也按照 package 来分组。

step 3、收集资源项 值 字符串 上一步是 名称 这一回是 值。一共有12个资源项,但是只有10项是具有值字符串的,它们分别是“res/drawable-ldpi/icon.png”、“res/drawable-mdpi/icon.png”、“res/drawable-hdpi/icon.png”、“res/layout/main.xml”、“res/layout/sub.xml”、“Activity”、“Sub Activity”、“Start sub-activity in process”、“Start sub-activity in new process”和“Finish activity”。需要注意的是这些字符串不是按照包 package 来区分的,会被统一收集起来。

step 4、生成package数据块 参与编译的每一个 package 的资源项 元信息 都写在一个独立的数据上,这个数据块使用和一个类型为 ResTable_package 的头部来描述。最后是下图这样的形式来的。

Android 编译打包的那些疑问

说一个比较不注意的东西点,但是感觉挺重要的点吧,在Android资源中,有一种资源类型成为public,他们一般是定义在 res/values/public.xml 。比如下面这样的,这个public.xml是用来告诉aapt,将类型为string的资源string3的ID 固定为0x7f040001,为什么要固定呢?当我们自己自定义的资源导出来给第三方程序使用的时候,为了保证以后修改这些导出资源时,仍然能保证第三方应用程序的兼容性,就需要给这些导出资源一个固定的资源ID。举个栗子,不然不好理解。那就对比一下有什么区别吧,我们首先建立一个public.xml 里面放一个string3.然后新建两个String,一个string3,一个string1。我们看一下public和没有public的区别吧。

<code lang="bash">res/values/public.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <public type="string" name="string3" id="0x7f040001" />
</resources>
复制代码</code>
<code lang="bash">
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="string1">String 1</string>
    <string name="string3">String 3</string>
</resources>
</code>

假设 资源ID是下图这样的,那么第三方引用string3的资源ID永远是0x7f040001。

public final class R {
    // ...
    public static final class string {
        public static final int string1=0x7f040000;
        public static final int string3=0x7f040001;
    }
}

当有一天,我们增加一个string,按照我们之前所说的他们是按顺序来收集以及分配资源ID的。所以会有所改变。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="string1">String 1</string>
    <string name="string2">String 2</string>
    <string name="string3">String 3</string>
</resources>

假设string3 没有引入public.xml 中,我们应该猜到是下图这样的

public final class R {
    // ...
    public static final class string {
        public static final int string1=0x7f040000;
        public static final int string2=0x7f040001;
        public static final int string3=0x7f040002; // New ID! Was 0x7f040001
    }
}

但是我们放入 public中了,所以它的资源ID是固定的,就是下图这样的。应该挺好理解的。

public final class R {
    // ...
    public static final class string {
        public static final int string1=0x7f040000;
        public static final int string2=0x7f040002;
        public static final int string3=0x7f040001; // Resource ID from public.xml
    }
}

需要注意的是我们自己开发应用程序是不需要pubic.xml 这么搞一下的,基本都是内部使用。不会导出来给第三方app使用,只在内部使用的资源,不管它的资源ID怎么变化,我们都能通过R.java 文件定义的常量来引用他们。只有系统定义的资源包才会使用到public.xml 文件。因为它定义的资源需要提供给第三方应用程序使用的。

其实有一个更常见的情景,我们在反编译的时候,就会看到有这个public.xml 发现R.java的id好像都跑到public.xml里面去了,这是为什么呢?因为我们反编译之后再重新打包,编译,对资源ID会重新编排这是一个随机的过程,但是我们的代码里面还是之前的资源id,那么就乱套了,所以生成一个public.xml保持这些资源ID的固定,感觉又学到知识了,啊哈哈,以前没怎么关心过这个东西,不知道这个还有这一层含义。

继续我们的 生成package数据块

  1. 写入Package资源项元信息数据块头部
  2. 写入类型字符串资源池 在上面步骤中我们将每一个package 用到的类型字符串手机起来了,因此直接把他写到package资源项元信息块头部后面的数据块中去。

3.写入资源名称字符串资源池 我们已经把资源项名称字符串收集了。因此可以将他们直接写到类型字符串资源池后面的那个数据块中。 4.写入类型规范数据块 每一个类型都对应了一个类型规范数据块 5.写入类型资源项数据块

step 5、写入资源索引表头部 step 6、写入资源项的值字符串资源池 在前三个步骤中已经收集了这些,我们按照相关规则写入就可以了。 step 7、写入package数据块 在第四步的时候我们已经收集到了它的信息,同样按规则写入就可以了。

十、编译AndroidManifest文件 经过前面九个步骤,终于把应用程序的所有资源项都编译完成了。这个时候就开始讲应用程序的配置文件 AndroidManifest.xml也编译成二进制的xml文件。和之前的道理是一样的。当然aapt也会验证这个文件的完整性和正确性什么的。

十一、生成R.java 在第八步的时候,我们已经收集到这些资源ID,这里只是写入到R.java 文件中就好了。

public final class R {
    ......

    public static final class layout {
        public static final int main=0x7f030000;
        public static final int sub=0x7f030001;
    }

    ......
}

十二、打包APK文件 所有资源文件都编译以及生成完毕之后就可以打包到apk文件中去了。包括以下文件: 1、assert目录 2、res目录,但是不包括 res/values 目录,这是因为 res/values 目录下的文件经过编译后,直接写入到了资源项索引表去了。 3、资源项索引文件 resources.arsc 当然除了这些资源文件,应用的配置文件 AndroidManifest.xml 以及应用代码文件 class.dex,还有用来描述程序的签名信息的文件,也会被一并打包到 APK中去,这个APK文件可以直接安装了。

终于看完了,其中最重要的四个要点:

1、xml 资源文件从文本格式编译成二进制格式的过程

2、xml 资源文件的二进制格式

3、项目资源索引文件 resources.arsc 的生成过程

4、项目资源索引文件 resources.arsc的二进制格式

好了,这么长的文章,我感觉我是记不住的,那么就提炼一下吧,简化流程。

Android 编译打包的那些疑问

闭上眼睛不去看上面这张图,脑子还记的什么呢?突然想起了倚天屠龙记张三丰传授张无忌的桥段。啊~全忘记了~~呸,咱们这个可不行。既然忘记了,那我们就想想我们要做什么?貌似有两个点

1、xml 二进制话 更节省空间更快速 2、resources.arsc 和 R.java 也就是各种收集资源ID

我们知道这两点就够了,知道了要做什么,然后发散:如何做。

xml二进制话:其实就是从resources.arsc这个小字典里找到字符串相应的序列号,用0×0011来表示。这个相对来说就简单了一点。

难点是如何生成resources.arsc,那么我们就想啊,既然要生成这个资源集合文件,那么我们用包名这个唯一标识来创建 资源表吧,那么如何找到包名呢?解析 AndroidManifest.xml,找到了包名,根据包名,创建这个 资源表。

so:总结一下就是:1、创建资源表

有了资源表这个文件,那么该怎么往里边填写呢?那么我们就要知道我们收集的是啥?我们收集的资源文件,可以分为 drawable、layout、color、menu、raw、string和xml等,对了还有系统的资源文件,怎么区分系统和我们应用呢?根据包名package啊,然后根据类型type,根据Entry就各种资源ID了。这道怎么区分了,我们就该收集资源了。需要说的是我们不能直接往表里提交啊,万一错了咋办,就需要想用一个临时的容器,然后再往资源表里复制过去。

首先既然是收集资源,就要有一定的顺序和规矩,不能东一棒槌,西一锥子的,那么我们就以资源的类型来收集。比如 layout 、values、drawable。我们先用drawable来举例子,比如 xh,xxh,xxxh 都有一个icon.png。我们就想啊,怎么来区分呢?都是 icon 啊,那么我们就用组来分吧,比如drawable的容器是set,然后下一个粒度就是组group了,一组icon.png,然后里面的icon.png就是一个单个的文件file,那么怎么怎么知道它的一些特征呢?这个文件就需要一个它的描述文件就是entry,这样就知道这个drawable在xh等其他维度信息。当然别的类型也是如此。

收集到了临时容器,那么就需要往资源表放,需要注意的是values就先别放了,放其他类型的资源文件,values资源需要编译后再放。

放的时候和临时容器稍有差异,不过大同小异,每一个资源也是用 entry 表示,根据 package,tpye,configList 来分类。

至于values资源为什么那么特殊呢?它分为string 和 bag资源,这些bag资源会自定义一些自己的专用值,所以需要编译得到最终的结果才行。对了这个bag资源分为 attr类型资源和id资源类型,怎么区分呢,attr有个内部元数据“^type”。

好了收集了资源,因为前面values是处于编译阶段,所以也就连带着开始编译xml资源文件。

解析xml,把里面bag或者其他Values的资源转换成真实的值。就是根据资源的名字,类型,所在包找到对应的资源,根据资源找到它的元数据,就是需要的值了。

然后就开始压扁xml,也就是二进制他,那么就需要我们首先先收集这个xml里面的资源的属性名称字符串,然后收集其他String字符串。把收集到的这些字符串放到字符串资源池里面。然后根据资源池里作为字典,按照资源序号来重新生成这个xml,也就是二进制xml。就是因为values资源中的bag资源是在values编译的时候就被赋予资源ID的,但是其他资源目前是没有资源ID,只有对应路径的资源名称和资源数据。所以我们需要给资源赋值ID。也就是生成资源符号。就是资源名称+资源ID,为R.java做准备。

当然有了之前的所有东西,就可以根据规则整成 resources.arsc。各种添添补补什么的。最后生成R.java 文件。最终把生成的resources.arsc、AndroidManifest.xml、res目录(没有values 早被打包到resources.arsc了)、assert还有class.dex 和 签名信息文件 一同打进apk包里。

好了完工。连粘贴带复制,加上自己手敲终于稍微理清楚了这个关系,不过还需要回头再看一下这个过程才好,明确目的,一步一步根据思路去实现,不纠结具体实现,理清楚核心思想就可以了。

copy from 以下博客:

www.jianshu.com/p/d487f0aa9…

blog.csdn.net/luoshengyan…

juejin.im/entry/58b78…

www.10tiao.com/html/597/20…

www.jianshu.com/p/3cc131db2…


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

用UML构建Web应用

用UML构建Web应用

科纳尔伦 (Conallen Jim) / 陈起 / 中国电力出版社 / 2003-11 / 39.0

用UML构建Web应用(第2版),ISBN:9787508315577,作者:(美)Jim Conallen著;陈起,英宇译;陈起译一起来看看 《用UML构建Web应用》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具