Android应用程序资源的编译和打包过程分析 (转自老罗的博客)
图1 Android应用程序资源的编译和打包过程
? ? ? ? 这些XML资源文件之所要从文本格式编译成二进制格式,是因为:
? ? ? ? 1. 二进制格式的XML文件占用空间更小。这是由于所有XML元素的标签、属性名称、属性值和内容所涉及到的字符串都会被统一收集到一个字符串资源池中去,并 且会去重。有了这个字符串资源池,原来使用字符串的地方就会被替换成一个索引到字符串资源池的整数值,从而可以减少文件的大小。
? ? ? ? 2. 二进制格式的XML文件解析速度更快。这是由于二进制格式的XML元素里面不再包含有字符串值,因此就避免了进行字符串解析,从而提高速度。
? ? ? ? 将XML资源文件从文本格式编译成二进制格式解决了空间占用以及解析效率的问题,但是对于Android资源管理框架来说,这只是完成了其中的一部分工作。Android资源管理框架的另外一个重要任务就是要根据资源ID来快速找到对应的资源。
? ? ? ? 在前面Android资源管理框架(Asset Manager)简要介绍和学习计划一 文中提到,为了使得一个应用程序能够在运行时同时支持不同的大小和密度的屏幕,以及支持国际化,即支持不同的国家地区和语言,Android应用程序资源 的组织方式有18个维度,每一个维度都代表一个配置信息,从而可以使得应用程序能够根据设备的当前配置信息来找到最匹配的资源来展现在UI上,从而提高用 户体验。
? ? ? ? 由于Android应用程序资源的组织方式可以达到18个维度,因此就要求Android资源管理框架能够快速定位最匹配设备当前配置信息的资源来展现在 UI上,否则的话,就会影响用户体验。为了支持Android资源管理框架快速定位最匹配资源,Android资源打包工具aapt在编译和打包资源的过 程中,会执行以下两个额外的操作:
? ? ? ? 1. 赋予每一个非assets资源一个ID值,这些ID值以常量的形式定义在一个R.java文件中。
? ? ? ? 2. 生成一个resources.arsc文件,用来描述那些具有ID值的资源的配置信息,它的内容就相当于是一个资源索引表。
? ? ? ? 有了资源ID以及资源索引表之后,Android资源管理框架就可以迅速将根据设备当前配置信息来定位最匹配的资源了。接下来我们在分析Android应 用程序资源的编译和打包过程中,就主要关注XML资源的编译过程、资源ID文件R.java的生成过程以及资源索引表文件resources.arsc的 生成过程。
? ? ? ??Android资源打包工具在编译应用程序资源之前,会创建一个资源表。这个资源表使用一个ResourceTable对象来描述,当应用程序资源编 译完成之后,它就会包含所有资源的信息。有了这个资源表之后,?Android资源打包工具就可以根据它的内容来生成资源索引表文件 resources.arsc了。
? ? ? ? 接下来,我们就通过ResourceTable类的实现来先大概了解资源表里面都有些什么东西,如图2所示:

图2 ResourceTable的实现
? ? ? ? ResourceTable类用来总体描述一个资源表,它的重要成员变量的含义如下所示:
? ? ? ? --mAssetsPackage:表示当前正在编译的资源的包名称。
? ? ? ? --mPackages:表示当前正在编译的资源包,每一个包都用一个Package对象来描述。例如,一般我们在编译应用程序资源时,都会引用系统预先编译好的资源包,这样当前正在编译的资源包除了目标应用程序资源包之外,就还有预先编译好的系统资源包。
? ? ? ? --mOrderedPackages:和mPackages一样,也是表示当前正在编译的资源包,不过它们是以Package ID从小到大的顺序保存在一个Vector里面的,而mPackages是一个以Package Name为Key的DefaultKeyedVector。
? ? ? ? --mAssets:表示当前编译的资源目录,它指向的是一个AaptAssets对象。
? ? ? ? Package类用来描述一个包,这个包可以是一个被引用的包,即一个预先编译好的包,也可以是一个正在编译的包,它的重要成员变量的含义如下所示:
? ? ? ? --mName:表示包的名称。
? ? ? ? --mTypes:表示包含的资源的类型,每一个类型都用一个Type对象来描述。资源的类型就是指animimator、anim、color、drawable、layout、menu和values等。
? ? ? ? --mOrderedTypes:和mTypes一样,也是表示包含的资源的类型,不过它们是Type ID从小到大的顺序保存在一个Vector里面的,而mTypes是一个以Type Name为Key的DefaultKeyedVector。
? ? ? ? Type类用来描述一个资源类型,它的重要成员变量的含义如下所示:
? ? ? ? ?--mName:表示资源类型名称。
? ? ? ? ?--mConfigs:表示包含的资源配置项列表,每一个配置项列表都包含了一系列同名的资源,使用一个ConfigList来描述。例如,假设有main.xml和sub.xml两个layout类型的资源,那么main.xml和sub.xml都分别对应有一个ConfigList。
? ? ? ? ?--mOrderedConfigs:和mConfigs一样,也是表示包含的资源配置项,不过它们是以Entry ID从小到大的顺序保存在一个Vector里面的,而mConfigs是以Entry Name来Key的DefaultKeyedVector。
? ? ? ? ?--mUniqueConfigs:表示包含的不同资源配置信息的个数。我们可以将mConfigs和mOrderedConfigs看作是按照名称的不同来划分资源项,而将mUniqueConfigs看作是按照配置信息的不同来划分资源项。
? ? ? ? ConfigList用来描述一个资源配置项列表,它的重要成员变量的含义如下所示:
? ? ? ? --mName:表示资源项名称,也称为Entry Name。
? ? ? ? --mEntries: 表示包含的资源项,每一个资源项都用一个Entry对象来描述,并且以一个对应的ConfigDescription为Key保存在一个 DefaultKeyedVector中。例如,假设有一个名称为icon.png的drawable资源,有三种不同的配置,分别是ldpi、mdpi 和hdpi,那么以icon.png为名称的资源就对应有三个项。
? ? ? ??Entry类用来描述一个资源项,它的重要成员变量的含义如下所示:
? ? ? ? --mName:表示资源名称。
? ? ? ? --mItem:表示资源数据,用一个Item对象来描述。
? ? ? ? Item类用来描述一个资源项数据,它的重要成员变量的含义如下所示:
? ? ? ? --value:表示资源项的原始值,它是一个字符串。
? ? ? ? --parsedValue:表示资源项原始值经过解析后得到的结构化的资源值,使用一个Res_Value对象来描述。例如,一个整数类型的资源项的原始值为“12345”,经过解析后,就得到一个大小为12345的整数类型的资源项。
? ? ? ??ConfigDescription类是从ResTable_config类继承下来的,用来描述一个资源配置信息。ResTable_config 类的成员变量imsi、locale、screenType、input、screenSize、version和screenConfig对应的实际上 就是在前面Android资源管理框架(Asset Manager)简要介绍和学习计划一文提到的18个资源维度。
? ? ? ? 前面提到,当前正在编译的资源目录是使用一个AaptAssets对象来描述的,它的实现如图3所示:

图3 AaptAssets类的实现
? ? ? ? AaptAssets类的重要成员变量的含义如下所示:
? ? ? ? --mPackage:表示当前正在编译的资源的包名称。
? ? ? ? --mRes:表示所包含的资源类型集,每一个资源类型都使用一个ResourceTypeSet来描述,并且以Type Name为Key保存在一个KeyedVector中。
? ? ? ? --mHaveIncludedAssets:表示是否有引用包。
? ? ? ? --mIncludedAssets:指向的是一个AssetManager,用来解析引用包。引用包都是一些预编译好的资源包,它们需要通过AssetManager来解析。事实上,Android应用程序在运行的过程中,也是通过AssetManager来解析资源的。
? ? ? ? --mOverlay: 表示当前正在编译的资源的重叠包。重叠包是什么概念呢?假设我们正在编译的是Package-1,这时候我们可以设置另外一个Package-2,用来告 诉aapt,如果Package-2定义有和Package-1一样的资源,那么就用定义在Package-2的资源来替换掉定义在Package-1的 资源。通过这种Overlay机制,我们就可以对资源进行定制,而又不失一般性。
? ? ? ? ResourceTypeSet类实际上描述的是一个类型为AaptGroup的KeyedVector,并且这个KeyedVector是以 AaptGroup Name为Key的。AaptGroup类描述的是一组同名的资源,类似于前面所描述的ConfigList,它有一个重要的成员变量mFiles,里面 保存的就是一系列同名的资源文件。每一个资源文件都是用一个AaptFile对象来描述的,并且以一个AaptGroupEntry为Key保存在一个 DefaultKeyedVector中。
? ? ? ??AaptFile类的重要成员变量的含义如下所示:
? ? ? ? --mPath:表示资源文件路径。
? ? ? ? --mGroupEntry:表示资源文件对应的配置信息,使用一个AaptGroupEntry对象来描述。
? ? ? ? --mResourceType:表示资源类型名称。
? ? ? ? --mData:表示资源文件编译后得到的二进制数据。
? ? ? ? --mDataSize:表示资源文件编译后得到的二进制数据的大小。
? ? ? ??AaptGroupEntry类的作用类似前面所描述的ResTable_config,它的成员变量mcc、mnc、locale、vendor、 screenLayoutSize、screenLayoutLong、orientation、uiModeType、uiModeNight、 density、tounscreen、keysHidden、keyboard、navHidden、navigation、screenSize和 version对应的实际上就是在前面Android资源管理框架(Asset Manager)简要介绍和学习计划一文提到的18个资源维度。
? ? ? ? 了解了ResourceTable类和AaptAssets类的实现之后,我们就可以开始分析Android资源打包工具的执行过程了,如图4所示:

图4 Android资源打包工具的执行过程
? ? ? ? 假设我们当前要编译的应用程序资源目录结构如下所示:
图5 收集到的drawable和layout资源项列表
? ? ? ? 五. 编译values类资源
? ? ? ? 类型为values的资源描述的都是一些简单的值,如数组、颜色、尺寸、字符串和样式值等,这些资源是在编译的过程中进行收集的。接下来,我们就以字符串的编译过程来进行说明。
? ? ? ??在这篇文章中要用到的例子中,包含有一个strings.xml的文件,它的内容如下所示:
图6 收集到的string资源项列表
? ? ? ? 六. 给Bag资源分配ID
? ? ? ? 类型为values的资源除了是string之外,还有其它很多类型的资源,其中有一些比较特殊,如bag、style、plurals和array类的 资源。这些资源会给自己定义一些专用的值,这些带有专用值的资源就统称为Bag资源。例如,Android系统提供的 android:orientation属性的取值范围为{“vertical”、“horizontal”},就相当于是定义了vertical和 horizontal两个Bag。
? ? ? ? 在继续编译其它非values的资源之前,我们需要给之前收集到的Bag资源分配资源ID,因为它们可能会被其它非values类资源引用到。假设在res/values目录下,有一个attrs.xml文件,它的内容如下所示:
图7 收集到的Bag资源项列表
? ? ? ? 上述三个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”和TYPE_ENUM,用来表示它描述的是一个枚举类型的属性。注意,所有名称以“^”开头的bag都是表示一个内部元数据。
? ? ? ? 对于Bag资源来说,这一步需要给它们的元数据项分配资源ID,也就是给它们的bag分配资源ID。例如,对于上述的 custom_orientation来说,我们需要给它的^type、custom_vertical和custom_horizontal分配资源 ID,其中,^type分配到的是attr类型的资源ID,而custom_vertical和custom_horizontal分配到的是id类型的 资源ID。
? ? ? ? 七. 编译Xml资源文件
? ? ? ? 前面的六步操作为编译Xml资源文件准备好了所有的素材,因此,现在就开始要编译Xml资源文件了。除了values类型的资源文件,其它所有的Xml资源文件都需要编译。这里我们只挑layout类型的资源文件来说明Xml资源文件的编译过程,也就是这篇文章中要用到的例子中的main.xml文件,它的内容如下所示:
图8 Xml资源文件的编译过程
? ? ? ? 1. 解析Xml文件
? ? ? ? ?解析Xml文件是为了可以在内存中用一系列树形结构的XMLNode来表示它。XMLNode类的定义在文件frameworks/base/tools/aapt/XMLNode.h中,如下所示:
图9 增加两个类型为id的资源项
? ? ? ? 此外,对于main.xml文件的两个Button节点的android:text属性值“@string/start_in_process”和 “@string/start_in_new_process”,它们分别表示引用的是当前正在编译的资源包的名称分别为 “start_in_process”和“start_in_new_process”的string资源。这两个string资源在前面的第五步操作中 已经编译过了,因此,这里就可以直接获得它们的资源ID。
? ? ? ? 注意,一个资源项一旦创建之后,要获得它的资源ID是很容易的,因为它的Package ID、Type ID和Entry ID都是已知的。
? ? ? ? 4. 压平Xml文件
? ? ? ? 经过前面的三步操作之后,所需要的基本材料都已经准备好了,接下来就可以对Xml文件的内容进行扁平化处理了,实际上就是将Xml文件从文本格式转换为二进制格式,这个过程如图10所示:

图10 压平Xml文件
? ? ? ??将Xml文件从文本格式转换为二进制格式可以划分为六个步骤,接下来我们就详细分析每一个步骤。
? ? ? ? Step 1. 收集有资源ID的属性的名称字符串
? ? ? ? 这一步除了收集那些具有资源ID的Xml元素属性的名称字符串之外,还会将对应的资源ID收集起来放在一个数组中。这里收集到的属性名称字符串保存在一个字符串资源池中,它们与收集到的资源ID数组是一一对应的。
? ? ? ? 对于main.xml文件来说,具有资源ID的Xml元素属性的名称字符串有“orientation”、“layout_width”、 “layout_height”、“gravity”、“id”和“text”,假设它们对应的资源ID分别为0x010100c4、 0x010100f4、0x010100f5、0x010100af、0x010100d0和0x0101014f,那么最终得到的字符串资源池的前6个 位置和资源ID数组的对应关系如图11所示:

图11 属性名称字符串与属性资源ID的对应关系
? ? ? ? Step 2. 收集其它字符串
? ? ? ? 这一步收集的是Xml文件中的其它所有字符串。由于在前面的Step 1中,那些具有资源ID的Xml元素属性的名称字符串已经被收集过了,因此,它们在一步中不会被重复收集。对于main.xml文件来说,这一步收集到的字符串如图12所示:

图12 其它字符串
? ? ? ? 其中,“android”是android命名空间前缀,“http://schemas.android.com/apk/res/android”是 android命名空间uri,“LinearLayout”是LinearLayout元素的标签,“Button”是Button元素的标签。
? ? ? ? Step 3. 写入Xml文件头
? ? ? ? 最终编译出来的Xml二进制文件是一系列的chunk组成的,每一个chunk都有一个头部,用来描述chunk的元信息。同时,整个Xml二进制文件又可以看成一块总的chunk,它有一个类型为ResXMLTree_header的头部。
? ? ? ??ResXMLTree_header定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
图13 字符串资源池结构
? ? ? ? 注意,字符串偏移数组和字符串样式偏移数组的值分别是相对于stringStart和styleStart而言的。在解析二进制Xml文件的时候,通过这 两个偏移数组以及stringsStart和stylesStart的值就可以迅速地定位到第i个字符串。
? ? ? ? 接下来,我们就重点说说什么是字符串样式。假设有一个字符串资源池,它有五个字符串,分别是"apple"、“banana”、“orange”、 “<b>man</b><i>go</i>”和“pear”。注意到第四个字符串 “<b>man</b><i>go</i>”,它实际表示的是一个字符串“mango”,不过它的前三 个字符“man”通过b标签来描述为粗体的,而后两个字符通过i标签来描述为斜体的。这样实际上在整个字符串资源池中,包含了七个字符串,分别 是"apple"、“banana”、“orange”、“mango”、“pear”、“b”和“i”,其中,第四个字符串“mango”来有两个 sytle,第一个style表示第1到第3个字符是粗体的,第二个style表示第4到第5个字符是斜体的。
? ? ? ? 字符串与其样式描述是一一对应的,也变是说,如果第i个字符串是带有样式描述的,那么它的样式描述就位于样式内容块第i个位置上。以上面的字符串资源池为 例,由于第4个字符中带有样式描述,为了保持字符串与样式描述的一一对应关系,那么也需要假设前面3个字符串也带有样式描述的,不过需要将这3个字符串的 样式描述的个数设置为0。也就是说,在这种情况下,字符串的个数等于7,而样式描述的个数等于4,其中,第1到第3个字符串的样式描述的个数等于0,而第 4个字符串的样式描述的个数等于2。
? ? ? ? 假设一个字符串有N个样式描述,那么它在样式内容块中,就对应有N个ResStringPool_span,以及一个 ResStringPool_ref,其中,N个ResStringPool_span位于前面,用来描述每一个样式,而 ResStringPool_ref表示一个结束占位符。例如,对于上述的“mango”字符串来说,它就对应有2个 ResStringPool_span,以及1个ResStringPool_ref,而对于"apple"、“banana”和“orange”这三个 字符串来说,它们对应有0个ResStringPool_span,但是对应有1个ResStringPool_ref,最后三个字符串“pear”、 “b”和"i"对应有0个ResStringPool_span和0个ResStringPool_ref。
? ? ? ??ResStringPool_span和ResStringPool_ref定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
图14 命名空间chunk块
? ? ? ?ResXMLTree_node和ResXMLTree_namespaceExt定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
图15 标签为LinearLayout的Xml元素chunk
? ? ? ??ResXMLTree_attrExt、ResXMLTree_attribute和ResXMLTree_endElementExt定义在文件 frameworks/base/include/utils/ResourceTypes.h中,如下所示:
图16?CDATA类型的Xml Node的二进制表示
? ? ? ? ?ResXMLTree_cdataExt定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
图17 收集到的所有资源项
? ? ? ? 有了这些资源项之后,Android资源打包工具aapt就可以按照下面的流程来生成资源索引表resources.arsc了,如图18所示:

图18 资源索引表的生成过程
? ? ? ? 接下来,我们就以图17所示的资源项来说图18所示的资源索引表生成过程。
? ? ? ? 1. 收集类型字符串
? ? ? ? 在图17所示的资源项中,一共有4种类型的资源,分别是drawable、layout、string和id,于是对应的类型字符串就为“drawable”、“layout”、“string”和“id”。
? ? ? ? 注意,这些字符串是按Package来收集的,也就是说,当前被编译的应用程序资源有几个Package,就有几组对应的类型字符串,每一个组类型字符串都保存在其所属的Package中。
? ? ? ? 2. 收集资源项名称字符串
? ? ? ? 在图17所示的资源项中,一共有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”,于是收集到的资 源项名称字符串就为“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来收集的,也就是说,当前被编译的应用程序资源有几个Package,就有几组对应的资源项名称字符串,每一个组资源项名称字符串都保存在其所属的Package中。
? ? ? ? 3. 收集资源项值字符串
? ? ? ? 在图17所示的资源项中,一共有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来收集的,也就是说,当前所有参与编译的Package的资源项值字符串都会被统一收集在一起。
? ? ? ? 4. 生成Package数据块
? ? ? ??参与编译的每一个Package的资源项元信息都写在一块独立的数据上,这个数据块使用一个类型为ResTable_package的头部来描述。每一个Package的资源项元信息数据块的生成过程如图19所示:

图19 Package资源项元信息数据块的生成过程
? ? ? ? 这个生成过程可以分为5个步骤,接下来我们就以图17所示的资源项来说详细分析每一个步骤。
? ? ? ? Step 1. 写入Package资源项元信息数据块头部
? ? ? ??Package资源项元信息数据块头部是用一个ResTable_package来定义的。ResTable_package定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
图20 Package资源项元信息数据块结构
? ? ? ? 在Android资源中,有一种资源类型称为Public,它们一般是定义在res/values/public.xml文件中,形式如下所示:
图21 类型为drawable的规范数据块

图22 类型为layout的规范数据块

图23 类型为string的规范数据块

图24 类型为id的规范数据块
? ? ? ? 从图21到图24就可以看出,类型为drawable的资源项icon在设备的屏幕密度发生变化之后,Android资源管理框架需要重新对它进行加载, 以便获得更合适的资源项,而其它资源项无论设备配置信息发生何种变化,它们都不需要重新加载,因为它们只有一种配置。
? ? ? ? Step 5. 写入类型资源项数据块
? ? ? ? 类型资源项数据块用来描述资源项的具体信息, 这样我们就可以知道每一个资源项名称、值和配置等信息。类型资源项数据同样是按照类型和配置来组织的,也就是说,一个具有N个配置的类型一共对应有N个类型资源项数据块。
? ? ? ??类型资源项数据块的头部是用一个ResTable_type来定义的。ResTable_type定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
图25 类型为drawable和配置为ldpi的资源项数据块

图26 类型为drawable和配置为mdpi的资源项数据块

图27 类型为drawable和配置为hdpi的资源项数据块

图28 类型为layout和配置为default的资源项数据块

图29 类型为string和配置为default的资源项数据块

图30 类型为id和配置为default的资源项数据块
? ? ? ?注意,ResTable_type后面的uint32_t数组和ResTable_entry数组的大小不一定是相等的,考虑下面的资源目录:
图31 大小不等的uint32_t数组和ResTable_entry数组的资源项数据块
? ? ? ? 由于不存在类型为drawable、配置为ldpi,并且名称为logo的资源项,因此,在图31中,ResTable_type后面的uint32_t数组和ResTable_entry数组的大小是不相等的,并且没有相应的ResTable_entry的uint32_t数组元素的值会被设置为ResTable_type::NO_ENTRY。
? ? ? ? 每一个资源项数据都是通过一个ResTable_entry来定义的。ResTable_entry定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:图32 普通资源项写入到资源索引表的数据块结构
? ? ? ? 接着看一个Bag资源项的写入过程。以图7所示的Bag资源项custom_orientation为例,它有本个bag,分别是^type、 custom_vertical和custom_horizontal,其中,custom_vertical和custom_horizontal是两 个自定义的bag,它们的值分别等于0x0和0x1,而^type是一个系统内部定义的bag,它的值固定为0x10000。 注意,^type、custom_vertical和custom_horizontal均是类型为id的资源,假设它们分配的资源ID分别为 0x1000000、0x7f040000和7f040001。
? ? ? ? 一个Bag资源项写入到资源索引表的数据块结构如图33所示:

图33 Bag资源项写入到资源索引表的数据块结构
? ? ? ? 在图33中,紧跟在ResTable_entry后面的是一个ResTable_map_entry,用来描述后面要写入到的ResTable_map的信息。假设一个Bag资源项有N个bag,那么在ResTable_map_entry就有N个ResTable_map。
? ? ? ??ResTable_map_entry定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
[cpp] view plaincopy? ? ? ?ResTable_map_entry是从ResTable_entry继承下来的,我们首先看ResTable_entry的各个成员变量的取值:
? ? ? ?--size:等于sizeof(ResTable_map_entry)。
? ? ? ?--flags:由于在紧跟在ResTable_map_entry前面的ResTable_entry的成员变量flags已经描述过资源项的标志位了,因此,这里的flags就不用再设置了,它的值等于0。
? ? ? ?--key:由于在紧跟在ResTable_map_entry前面的ResTable_entry的成员变量key已经描述过资源项的名称了,因此,这里的key就不用再设置了,它的值等于0。
? ? ? ?ResTable_map_entry的各个成员变量的取值如下所示:
? ? ? ?--parent:指向父ResTable_map_entry的资源ID,如果没有父ResTable_map_entry,则等于0。
? ? ? ?--count:等于bag项的个数。
? ? ? ?Bag资源项的每一个bag都用一个ResTable_map来表示。ResTable_map定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
[cpp] view plaincopy? ? ? ??ResTable_map只有两个成员变量,其中:
? ? ? ? --name:等于bag的资源项ID。
? ? ? ? --value:等于bag的资源项值。
? ? ? ? 例如,对于custom_vertical来说,用来描述它的ResTable_map的成员变量name的值就等于0x7f040000,而成员变量value所指向的一个Res_value的各个成员变量的值如下所示:
? ? ? ? --size:等于sizeof(Res_value)。
? ? ? ? --res0:等于0,保留以后使用。
? ? ? ? --dataType:等于TYPE_INT_DEC,表示data是一个十进制的整数。
? ? ? ? --data:等于0。
? ? ? ? 我们可以依此类推,分别得到用来描述^type和custom_horizontal这两个bag的ResTable_map的值。
? ? ? ? 5. 写入资源索引表头部
? ? ? ??资源索引表头部使用一个ResTable_header来表示。ResTable_header定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:
[cpp] view plaincopy? ? ? ? 嵌入在ResTable_header内部的ResChunk_header的各个成员变量的取值如下所示:
? ? ? ? --type:等于RES_TABLE_TYPE,表示这是一个资源索引表头部。
? ? ? ? --headerSize:等于sizeof(ResTable_header),表示头部的大小。
? ? ? ? --size:等于整个resources.arsc文件的大小。
? ? ? ? ResTable_header的其它成员变量的取值如下所示:
? ? ? ? --packageCount:等于被编译的资源包的个数。
? ? ? ? 6. 写入资源项的值字符串资源池
? ? ? ? 在前面的第3步中,我们已经将所有的资源项的值字符串都收集起来了,因此,这里直接它们写入到资源索引表去就可以了。注意,这个字符串资源池包含了在所有的资源包里面所定义的资源项的值字符串,并且是紧跟在资源索引表头部的后面。
? ? ? ? 7. 写入Package数据块
? ? ? ? 在前面的第4步中,我们已经所有的Package数据块都收集起来了,因此,这里直接将它们写入到资源索引表去就可以了。这些Package数据块是依次写入到资源索引表去的,并且是紧跟在资源项的值字符串资源池的后面。
? ? ? ? 至此,资源项索引表的生成好了。
? ? ? ? 十. 编译AndroidManifest.xml文件
? ? ? ? 经过前面的九个步骤之后,应用程序的所有资源项就编译完成了,这时候就开始将应用程序的配置文件AndroidManifest.xml也编译成二进制格式的Xml文件。之所以要在应用程序的所有资源项都编译完成之后,再编译应用程序的配置文件,是因为后者可能会引用到前者。
? ? ? ??应用程序配置文件AndroidManifest.xml的编译过程与其它的Xml资源文件的编译过程是一样的,可以参考前面的第七步。注意,应用程序配置文件AndroidManifest.xml编译完成之后,Android资源打包工具appt还会验证它的完整性和正确性,例如,验证AndroidManifest.xml的根节点mainfest必须定义有android:package属性。
? ? ? ?十一. 生成R.java文件
? ? ? ?在前面的第八步中,我们已经将所有的资源项及其所对应的资源ID都收集起来了,因此,这里只要将直接将它们写入到指定的R.java文件去就可以了。例 如,假设分配给类型为layout的资源项main和sub的ID为0x7f030000和0x7f030001,那么在R.java文件,就会分别有两 个以main和sub为名称的常量,如下所示:
[java] view plaincopy? ? ? ? 注意,每一个资源类型在R.java文件中,都有一个对应的内部类,例如,类型为layout的资源项在R.java文件中对应的内部类为layout,而类型为string的资源项在R.java文件中对应的内部类就为string。
? ? ? ? 十二. 打包APK文件
? ? ? ? 所有资源文件都编译以及生成完成之后,就可以将它们打包到APK文件去了,包括:
? ? ? ? 1. assets目录。
? ? ? ? 2. res目录,但是不包括res/values目录, 这是因为res/values目录下的资源文件的内容经过编译之后,都直接写入到资源项索引表去了。
? ? ? ? 3. 资源项索引文件resources.arsc。
? ? ? ? 当然,除了这些资源文件外,应用程序的配置文件AndroidManifest.xml以及应用程序代码文件classes.dex,还有用来描述应用程 序的签名信息的文件,也会一并被打包到APK文件中去,这个APK文件可以直接拿到模拟器或者设备上去安装。
? ? ? ? 至此,我们就分析完成Android应用程序资源的编译和打包过程了,其中最重要的是要掌握以下四个要点:
? ? ? ? 1. ?Xml资源文件从文本格式编译为二进制格式的过程。
? ? ? ? 2. Xml资源文件的二进制格式。
? ? ? ? 3. 资源项索引表resources.arsc的生成过程。
? ? ? ? 4.?资源项索引表resources.arsc的二进制格式。
? ? ? ? 理解了Android应用程序资源的编译和打包过程之后,接下来我们就可以分析Android应用程序在运行时查找索引的过程了,敬请关注!
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!