Android应用程序资源的编译和打包过程分析作者:罗升阳我们知道,在一个APK文件中,除了有代码文件之外,还有很多资源文件。这些资源文件是通过Android资源打包工具aapt(AndroidAssetPackageTool)打包到APK文件里面的。在打包之前,大部分文本格式的XML资源文件还会被编译成二进制格式的XML资源文件。在本文中,我们就详细分析XML资源文件的编译和打包过程,为后面深入了解Android系统的资源管理框架打下坚实的基础。在前面Android资源管理框架(AssetManager)简要介绍和学习计划一文中提到,只有那些类型为res/animator、res/anim、res/color、res/drawable(非Bitmap文件,即非.png、.9.png、.jpg、.gif文件)、res/layout、res/menu、res/values和res/xml的资源文件均会从文本格式的XML文件编译成二进制格式的XML文件,如图1所示:这些XML资源文件之所要从文本格式编译成二进制格式,是因为:1.二进制格式的XML文件占用空间更小。这是由于所有XML元素的标签、属性名称、属性值和内容所涉及到的字符串都会被统一收集到一个字符串资源池中去,并且会去重。有了这个字符串资源池,原来使用字符串的地方就会被替换成一个索引到字符串资源池的整数值,从而可以减少文件的大小。2.二进制格式的XML文件解析速度更快。这是由于二进制格式的XML元素里面不再包含有字符串值,因此就避免了进行字符串解析,从而提高速度。将XML资源文件从文本格式编译成二进制格式解决了空间占用以及解析效率的问题,但是对于Android资源管理框架来说,这只是完成了其中的一部分工作。Android资源管理框架的另外一个重要任务就是要根据资源ID来快速找到对应的资源。在前面Android资源管理框架(AssetManager)简要介绍和学习计划一文中提到,为了使得一个应用程序能够在运行时同时支持不同的大小和密度的屏幕,以及支持国际化,即支持不同的国家地区和语言,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所示:图2ResourceTable的实现ResourceTable类用来总体描述一个资源表,它的重要成员变量的含义如下所示:--mAssetsPackage:表示当前正在编译的资源的包名称。--mPackages:表示当前正在编译的资源包,每一个包都用一个Package对象来描述。例如,一般我们在编译应用程序资源时,都会引用系统预先编译好的资源包,这样当前正在编译的资源包除了目标应用程序资源包之外,就还有预先编译好的系统资源包。--mOrderedPackages:和mPackages一样,也是表示当前正在编译的资源包,不过它们是以PackageID从小到大的顺序保存在一个Vector里面的,而mPackages是一个以PackageName为Key的DefaultKeyedVector。--mAssets:表示当前编译的资源目录,它指向的是一个AaptAssets对象。Package类用来描述一个包,这个包可以是一个被引用的包,即一个预先编译好的包,也可以是一个正在编译的包,它的重要成员变量的含义如下所示:--mName:表示包的名称。--mTypes:表示包含的资源的类型,每一个类型都用一个Type对象来描述。资源的类型就是指animimator、anim、color、drawable、layout、menu和values等。--mOrderedTypes:和mTypes一样,也是表示包含的资源的类型,不过它们是TypeID从小到大的顺序保存在一个Vector里面的,而mTypes是一个以TypeName为Key的DefaultKeyedVector。Type类用来描述一个资源类型,它的重要成员变量的含义如下所示:--mName:表示资源类型名称。--mConfigs:表示包含的资源配置项列表,每一个配置项列表都包含了一系列同名的资源,使用一个ConfigList来描述。例如,假设有main.xml和sub.xml两个layout类型的资源,那么main.xml和sub.xml都分别对应有一个ConfigList。--mOrderedConfigs:和mConfigs一样,也是表示包含的资源配置项,不过它们是以EntryID从小到大的顺序保存在一个Vector里面的,而mConfigs是以EntryName来Key的DefaultKeyedVector。--mUniqueConfigs:表示包含的不同资源配置信息的个数。我们可以将mConfigs和mOrderedConfigs看作是按照名称的不同来划分资源项,而将mUniqueConfigs看作是按照配置信息的不同来划分资源项。ConfigList用来描述一个资源配置项列表,它的重要成员变量的含义如下所示:--mName:表示资源项名称,也称为EntryName。--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资源管理框架(AssetManager)简要介绍和学习计划一文提到的18个资源维度。前面提到,当前正在编译的资源目录是使用一个AaptAssets对象来描述的,它的实现如图3所示:图3AaptAssets类的实现AaptAssets类的重要成员变量的含义如下所示:--mPackage:表示当前正在编译的资源的包名称。--mRes:表示所包含的资源类型集,每一个资源类型都使用一个ResourceTypeSet来描述,并且以TypeName为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是以AaptGroupName为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资源管理框架(AssetManager)简要介绍和学习计划一文提到的18个资源维度。了解了ResourceTable类和AaptAssets类的实现之后,我们就可以开始分析Android资源打包工具的执行过程了,如图4所示:图4Android资源打包工具的执行过程假设我们当前要编译的应用程序资源目录结构如下所示:[plain]viewplaincopy1.project2.--AndroidManifest.xml3.--res4.--drawable-ldpi5.--icon.png6.--drawable-mdpi7.--icon.png8.--drawable-hdpi9.--icon.png10.--layout11.--main.xml12.--sub.xml13.--values14.--strings.xml接下来,我们就按照图4所示的步骤来分析上述应用程序资源的编译和打包过程。一.解析Andro