资源、文件Android应用程序资源的编译和打包过程分析by小雨

摘要:
在本文中,我们将详细分析XML资源文件的编译和打包过程,为Android系统的资源管理框架奠定坚实的基础。接下来,在分析Android应用程序资源的编译和打包过程中,我们将关注XML资源的编译过程、资源ID文件R.java的生成过程以及资源索引表文件资源。arcc的生成过程太长。Android资源打包工具将在编译应用程序资源之前创建一个资源表。我们可以将mConfigs和mOrderedConfig视为具有不同名称的资源项,将mUniqueConfigs视为具有相同配置信息的资源项。

这段时间一直在习学资源、文件-之类的题问,上午正好有机会和大家共享一下.

        我们晓得,在一个APK文件中,除了有码代文件外之,还有很多资源文件。这些资源文件是通过Android资源打包工具aapt(Android Asset Package Tool)打包到APK文件里头的。在打包之前,大部分本文格式的XML资源文件还会被编译成二进制格式的XML资源文件。在本文中,我们就详细析分XML资源文件的编译和打包程过,为前面深刻懂得Android统系的资源管理架框打下坚固的础基。

    老罗的新浪微博:http://weibo.com/shengyangluo,欢送存眷!

        在面前Android资源管理架框(Asset Manager)要简绍介和习学划计一文中提到,只有那些类型为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所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第1张

    图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所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第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所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第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所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第4张

    图4 Android资源打包工具的行执程过

            设假我们前当要编译的应用程序资源目录结构如下所示:

project
  --AndroidManifest.xml
  --res
    --drawable-ldpi
      --icon.png
    --drawable-mdpi
      --icon.png
    --drawable-hdpi
      --icon.png
    --layout
      --main.xml
      --sub.xml
    --values
      --strings.xml

         接来下,我们就按照图4所示的步调来析分上述应用程序资源的编译和打包程过。

             一. 剖析AndroidManifest.xml

             剖析AndroidManifest.xml是为了得获要编译资源的应用程序的包称名。我们晓得,在AndroidManifest.xml文件中,manifest标签的package属性的值述描的就是应用程序的包称名。有了这个包称名后之,就够能创立资源表了,即创立一个ResourceTable对象。

             二. 添加被用引资源包

             Android统系定义了一套通用资源,这些资源可以被应用程序用引。例如,我们在XML局布文件中指定一个LinearLayout的android:orientation属性的值为“vertical”时,这个“vertical”现实上就是在统系资源包里头定义的一个值。

            在Android源码代工程环境中,Android统系供提的资源经过编译后,就位于out/target/common/obj/APPS/framework-res_intermediates/package-export.apk文件中,因此,在Android源码代工程环境中编译的应用程序资源,都市用引到这个package-export.apk。

            从面上的析分就够能看出,我们在编译一个Android应用程序的资源的时候,少至会涉及到两个包,其中一个被用引的统系资源包,另外一个就是前当正在编译的应用程序资源包。个一每包都可以定义自己的资源,同时它也可以用引其它包的资源。那么,一个包是通过什么式方来用引其它包的资源的呢?这就是我们熟习的资源ID了。资源ID是一个4字节的无符号整数,其中,最高字节表现Package ID,次高字节表现Type ID,最低两字节表现Entry ID。

            Package ID相称于是一个命名间空,定限资源的起源。Android统系前当定义了两个资源令命间空,其中一个统系资源令命间空,它的Package ID于等0x01,另外一个是应用程序资源令命间空,它的Package ID于等0x7f。有所位于[0x01, 0x7f]之间的Package ID都是法合的,而在这个范围外之的都是合法的Package ID。面前提到的统系资源包package-export.apk的Package ID就于等0x01,而我们在应用程序中定义的资源的Package ID的值都于等0x7f,这一点可以通过生成的R.java文件来验证。

            Type ID是指资源的类型ID。资源的类型有animator、anim、color、drawable、layout、menu、raw、string和xml等等干若种,每一种都市被付与一个ID。

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

            关于资源ID的更多述描,以及资源的用引关系,可以参考frameworks/base/libs/utils目录下的README文件。

            三. 搜集资源文件

            在编译应用程序资源之前,Android资源打包工具aapt会创立一个AaptAssets对象,用来搜集前当须要编译的资源文件。这些须要编译的资源文件就存保在AaptAssets类的成员变量mRes中,如下所示:

class AaptAssets : public AaptDir
{
    ......

private:
    ......

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

        AaptAssets类定义在文件frameworks/base/tools/aapt/AaptAssets.h中。

            AaptAssets类的成员变量mRes是一个类型为ResourceTypeSet的KeyedVector,这个KeyedVector的Key就是资源的类型称名。由此便可知,搜集到资源文件是按照类型来存保的。例如,对于我们在这篇文章中要用到的例子,一共有三种类型的资源,别分是drawable、layout和values,于是,就对应有三个ResourceTypeSet。

            从面前的图3可以看出,ResourceTypeSet类本身述描的也是一个KeyedVector,不过它里头存保的是一系列有着同相文件名的AaptGroup。例如,对于我们在这篇文章中要用到的例子:

            1. 类型为drawable的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。这两个AaptGroup都是只含包了一个AaptFile,别分是res/layout/main.xml和res/layout/sub.xml。这两个AaptFile一样是别分对应有一个AaptGroupEntry,不过这两个AaptGroupEntry述描的资源置配信息都是属于default的。

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

            四. 将搜集到的资源增长到资源表

            面前搜集到的资源只是存保在一个AaptAssets对象中,这一步须要将这些资源同时增长到一个资源表中去,即增长到面前所创立的一个ResourceTable对象中去,因为最后我们须要根据这个ResourceTable来生成资源索引表,即生成resources.arsc文件。

            注意,这一步搜集到资源表的资源是不括包values类型的资源的。类型为values的资源较比特别,它们要经过编译后之,才会添加到资源表中去。这个程过我们前面再述描。

            从面前的图2可以看出,在ResourceTable类中,个一每资源都是别分用一个Entry对象来述描的,这些Entry别分按照Pacakge、Type和ConfigList来分类存保。例如,对于我们在这篇文章中要用到的例子,设假它的包名为“shy.luo.activity”,那么在ResourceTable类的成员变量mPackages和mOrderedPackages中,就会别分存保有一个称名为shy.luo.activity”的Package,如下所示:

class ResourceTable : public ResTable::Accessor
{
    ......

private:
    ......

    DefaultKeyedVector<String16, sp<Package> > mPackages;
    Vector<sp<Package> > mOrderedPackages;
   
    ......
};

       ResourceTable类定义在文件frameworks/base/tools/aapt/ResourceTable.h中。

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

           1. 类型为drawable的Type含包有一个ConfigList。这个ConfigList的称名为icon.png,含包有三个Entry,别分为res/drawable-ldip/icon.png、res/drawable-mdip/icon.png和res/drawable-hdip/icon.png。个一每Entry都对应有一个ConfigDescription,用来述描不同的资源置配信息,即别分用来述描ldpi、mdpi和hdpi三种不同的屏幕度密。

           2. 类型为layout的Type含包有两个ConfigList。这两个ConfigList的称名别分为main.xml和sub.xml。称名为main.xml的ConfigList含包有一个Entry,即res/layout/main.xml。称名为sub.xml的ConfigList含包有一个Entry,即res/layout/sub/xml。

           上述失掉的五个Entry别分对应有五个Item,它们的对应关系以及内容如下图5所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第5张

    图5 搜集到的drawable和layout资源项列表

        五. 编译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,这个Type有五个ConfigList。这五个ConfigList的称名别分为“app_name”、“sub_activity”、“start_in_process”、“start_in_new_process”和“finish”,个一每ConfigList又别分含有一个Entry。

        上述失掉的五个Entry别分对应有五个Item,它们的对应关系以及内容如图6所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第6张

    图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文件,它的内容如下所示:

<?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>

        这个文件定义了一个称名为“custom_orientation”的属性,它是一个枚举类型的属性,可以取值为“custom_vertical”或者“custom_horizontal”。Android资源打包工具aapt在编译这个文件的时候,就会生成以下三个Entry,如图7所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第7张

    图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文件,它的内容如下所示:

<?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: 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="@string/start_in_process" >
    </Button>
    <Button 
        android: 
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="@string/start_in_new_process" >
    </Button>
</LinearLayout>

        Xml资源文件main.xml的编译程过如图8所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第8张

    图8 Xml资源文件的编译程过

        1. 剖析Xml文件

         剖析Xml文件是为了可以在内存用中一系列树形结构的XMLNode来表现它。XMLNode类的定义在文件frameworks/base/tools/aapt/XMLNode.h中,如下所示:

class XMLNode : public RefBase
{
    ......

private:
    ......

    String16 mElementName;
    Vector<sp<XMLNode> > mChildren;
    Vector<attribute_entry> mAttributes;
    ......
    String16 mChars;
    ......
};

        个一每XMLNode都表现一个Xml元素,其中:

        --mElementName,表现Xml元素标签。

        --mChars,表现Xml元素的本文内容。

        --mAttributes,表现Xml元素的属性列表。

        --mChildren,表现Xml元素的子元素。

        Xml文件剖析成完后之,就够能失掉一个用来述描根节点的XMLNode,接来下就够能通过这个根节点来成完其它的编译操纵。

        2. 付与属性称名资源ID

        这一步现实上就是给个一每Xml元素的属性称名都付与资源ID。例如,对于main.xml文件的根节点LinearLayout来讲,就是要别分给它的属性称名“android:orientation”、“android:layout_width”、“android:layout_height”和“android:gravity”付与一个资源ID。注意,上述这些属性都是在统系资源包里头定义的,因此,Android资源打包工具首先是要在统系资源包里头找到这些称名所对应的资源ID,然后才能赋给main.xml文件的根节点LinearLayout。

        对于统系资源包来讲,“android:orientation”、“android:layout_width”、“android:layout_height”和“android:gravity”等这些属性称名是它定义的一系列Bag资源,在它被编译的时候,就经已分配好资源ID了,就如面上的第六步操纵所示。

        个一每Xml文件都是从根节点开始给属性称名付与资源ID,然后再给递归给个一每子节点的属性称名付与资源ID,直到个一每节点的属性称名都得获了资源ID为止。

        3. 剖析属性值

        上一步是对Xml元素的属性的称名行进剖析,这一步是对Xml元素的属性的值行进剖析。例如,对于对于main.xml文件的根节点LinearLayout来讲,面前我们经已给它的属性android:orientation的称名付与了一个资源ID,这里就要给它的值“vertical”行进剖析。

        面前提到,android:orientation是在统系资源包定义的一个Bag资源,这个Bag资源分配有资源ID,而且会指定有元数据,也就是它可以取哪些值。对于android:orientation来讲,它的法合取值就为“horizontal”或者“vertical”。在统系资源包中,“horizontal”或者“vertical”也一样是一个Bag资源,它们的值别分被定义为0和1。

        Android资源打包工具是如何找到main.xml文件的根节点LinearLayout的属性android:orientation的字符串值“vertical”所对应的整数值1的呢?设假在上一步中,从统系资源包找到“android:orientation”的资源ID为0x010100c4,那么Android资源打包工具就会通过这个资源ID找到它的元数据,也就是两个称名别分为“horizontal”和“vertical”的bag,接着就根据字符串匹配到称名“vertical”的bag,最后就够能将这个bag的值1作为剖析结果了。

        注意,对于用引类型的属性值,要行进一些额定的处置。例如,对于main.xml文件的第一个Button节点的android:id属性值“@+id/button_start_in_process”,其中,“@”表现前面述描的属性是用引类型的,“+”表现如果该用引不存在,那么就建新一个,“id”表现用引的资源类型是id,“button_start_in_process”表现用引的称名。现实上,在"id"面前,还可以指定一个包名,例如,将main.xml文件的第一个Button节点的android:id属性值指定为“@+[package:]id/button_start_in_process” 。如果没有指定包名的话,那么就会认默在前当编译的包里头查找button_start_in_process这个用引。由于面前指有“+”符号,因此,如果在指定的包里头找不到button_start_in_process这个用引的话,那么就会在该包里头创立一个新的。无论button_start_in_process在指定的包里头本来就存在的,还是建新的,终究Android资源打包工具都是将它的资源ID作为剖析结果。

        在我们这个景情中,在剖析main.xml文件的两个Button节点的android:id属性值“@+id/button_start_in_process”和“@+id/button_start_in_new_process”时,前当正在编译的资源包没有含包有响应的用引的,因此,Android资源打包工具就会在前当正在编译的资源包里头增长两个类型为id的Entry,如图9所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第9张

    图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所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第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所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第11张

    图11 属性称名字符串与属性资源ID的对应关系

        Step 2. 搜集其它字符串

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

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第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中,如下所示:

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

/**
 * XML tree header.  This appears at the front of an XML tree,
 * describing its content.  It is followed by a flat array of
 * ResXMLTree_node structures; the hierarchy of the XML document
 * is described by the occurrance of RES_XML_START_ELEMENT_TYPE
 * and corresponding RES_XML_END_ELEMENT_TYPE nodes in the array.
 */
struct ResXMLTree_header
{
    struct ResChunk_header header;
};

        ResXMLTree_header内嵌有一个类型为ResChunk_header的部头。事实上,每一种部头类型都市内嵌有一个类型为ResChunk_header的础基部头,并且这个ResChunk_header都是作为第一个成员变量现出的。这样在剖析二进制Xml文件的时候,只须要读出面前小大为sizeof(ResChunk_header)的数据块,并且通过识别其中的type值,就够能晓得现实正在处置的chunk的体具类型。

        对于ResXMLTree_header部头来讲,内嵌在它里头的ResChunk_header的成员变量的值如下所示:

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

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

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

        Step 4. 写入字符串资源池

        本来定义在Xml文件中的字符串经已在Step 1和Step 2中搜集终了,因此,这里就够能将它们写入到终究搜集到二进制格式的Xml文件中去。注意,写入的字符串是格严按照它们在字符串资源池中的序顺写入的。例如,对于main.xml来讲,次依写入的字符串为“orientation”、“layout_width”、“layout_height”、“gravity”、“id”、"text"、"android"、“http://schemas.android.com/apk/res/android”、“LinearLayout”和“Button”。之所以要格严按照这个序顺来写入,是因为接来下要将面前Step 1搜集到的资源ID组数也写入到二进制格式的Xml文件中去,并且要持保这个资源ID组数与字符串资源池前六个字符串的对应关系。

       写入的字符串池chunk一样也是拥有一个部头的,这个部头的类型为ResStringPool_header,它定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:

/**
 * Definition for a pool of strings.  The data of this chunk is an
 * array of uint32_t providing indices into the pool, relative to
 * stringsStart.  At stringsStart are all of the UTF-16 strings
 * concatenated together; each starts with a uint16_t of the string's
 * length and each ends with a 0x0000 terminator.  If a string is >
 * 32767 characters, the high bit of the length is set meaning to take
 * those 15 bits as a high word and it will be followed by another
 * uint16_t containing the low word.
 *
 * If styleCount is not zero, then immediately following the array of
 * uint32_t indices into the string table is another array of indices
 * into a style table starting at stylesStart.  Each entry in the
 * style table is an array of ResStringPool_span structures.
 */
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;
};

        内嵌在ResStringPool_header里头的ResChunk_header的成员变量的值如下所示:

        --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:于等字符串款式块于对相其部头的距离。

        无论是UTF8,还是UTF16的字符串码编,个一每字符串的面前都有2个字节表现其长度,而且前面以一个NULL字符结束。对于UTF8码编的字符串来讲,NULL字符应用一个字节的0x00来表现,而对于UTF16码编的字符串来讲,NULL字符应用两个字节的0x0000来表现。

        如果一个字符串的长度超越32767,那么就会应用更多的字节来表现。设假字符串的长度超越32767,那么前两个字节的最高位就会于等0,表现接来下的两个字节仍然是用来表现字符串长度的,并且前两个字表现高16位,而后两个字节表现低16位。

        除了ResStringPool_header部头、字符串内容块和字符串款式内容块外之,还有两个移偏组数,别分是字符串移偏组数和字符串款式移偏组数,这两个移偏组数的小大就别分于等字符串的数量stringCount和styleCount的值,而个一每元素都是一个无符号整数。全部字符中资源池的成组就如图13所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第13张

    图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中,如下所示:

/**
 * Reference to a string in a string pool.
 */
struct ResStringPool_ref
{
    // Index into the string pool table (uint32_t-offset from the indices
    // immediately after ResStringPool_header) at which to find the location
    // of the string data in the pool.
    uint32_t index;
};

/**
 * This structure defines a span of style information associated with
 * a string in the pool.
 */
struct ResStringPool_span
{
    enum {
        END = 0xFFFFFFFF
    };

    // This is the name of the span -- that is, the name of the XML
    // tag that defined it.  The special value END (0xFFFFFFFF) indicates
    // the end of an array of spans.
    ResStringPool_ref name;

    // The range of characters in the string that this span applies to.
    uint32_t firstChar, lastChar;
};

        由于ResStringPool_ref在这里现出的作用就是充任款式述描结束占位符,因此,它独一的成员变量index的取值就固定为ResStringPool_span::END。

        再来看ResStringPool_span是如何表现一个款式述描的。以字符串“mango”的第一个款式述描为例,对应的ResStringPool_span的各个成员变量的取值为:

        --name:于等字符串“b”在字符串资源池中的置位。

        --firstChar:于等0,即指向字符“m”。

        --lastChar:于等2,即指向字符"n"。

        综合起来就是表现字符串“man”是粗体的。

        再以字符串“mango”的第二个款式述描为例,对应的ResStringPool_span的各个成员变量的取值为:

        --name:于等字符串“i”在字符串资源池中的置位。

        --firstChar:于等3,即指向字符“g”。

        --lastChar:于等4,即指向字符“o”。

        综合起来就是表现字符串“go”是体斜的。

        另外有一个地方须要注意的是,字符串款式内容的最后会有8个字节,每4个字节都被填充为ResStringPool_span::END,用来表达字符串款式内容结束符。这个结束符可以在剖析程过用中作错误验证。

        Step 5. 写入资源ID

        在面前的Step 1中,我们把属性的资源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来表现,如图14所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第14张

    图14 命名间空chunk块

       ResXMLTree_node和ResXMLTree_namespaceExt定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:

/**
 * Basic XML tree node.  A single item in the XML document.  Extended info
 * about the node can be found after header.headerSize.
 */
struct ResXMLTree_node
{
    struct ResChunk_header header;

    // Line number in original source file at which this element appeared.
    uint32_t lineNumber;

    // Optional XML comment that was associated with this element; -1 if none.
    struct ResStringPool_ref comment;
};

/**
 * Extended XML tree node for namespace start/end nodes.
 * Appears header.headerSize bytes after a ResXMLTree_node.
 */
struct ResXMLTree_namespaceExt
{
    // The prefix of the namespace.
    struct ResStringPool_ref prefix;

    // The URI of the namespace.
    struct ResStringPool_ref uri;
};

        对于main.xml文件来讲,在它的命名间空chunk中,内嵌在第一个ResXMLTree_node里头的ResChunk_header的各个成员变量的取值如下所示:

        --type:于等RES_XML_START_NAMESPACE_TYPE,表现命名间空开始标签的部头。

        --headerSize:于等sizeof(ResXMLTree_node),表现部头的小大。

        --size:于等sizeof(ResXMLTree_node) + sizeof(ResXMLTree_namespaceExt)。

        第一个ResXMLTree_node的其余成员变量的取值如下所示:

        --lineNumber:于等命名间空开始标签在本来本文格式的Xml文件现出的行号。

        --comment:于等命名间空的注释在字符池资源池的索引。

       内嵌在第二个ResXMLTree_node里头的ResChunk_header的各个成员变量的取值如下所示:

        --type:于等RES_XML_END_NAMESPACE_TYPE,表现命名间空结束标签的部头。

        --headerSize:于等sizeof(ResXMLTree_node),表现部头的小大。

        --size:于等sizeof(ResXMLTree_node) + sizeof(ResXMLTree_namespaceExt)。

        第二个ResXMLTree_node的其余成员变量的取值如下所示:

        --lineNumber:于等命名间空结束标签在本来本文格式的Xml文件现出的行号。

        --comment:于等0xffffffff,即-1。

        两个ResXMLTree_namespaceExt的内容都是一样的,它们的成员变量的取值如下所示:

        --prefix:于等字符串“android”在字符串资源池中的索引。

        --uri:于等字符串“http://schemas.android.com/apk/res/android”在字符串资源池中的索引。

        接来下被压平的是标签为LinearLayout的Xml Node。这个Xml Node由两个ResXMLTree_node、一个ResXMLTree_attrExt、一个ResXMLTree_endElementExt和四个ResXMLTree_attribute来表现,如图15所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第15张

    图15 标签为LinearLayout的Xml元素chunk

        ResXMLTree_attrExt、ResXMLTree_attribute和ResXMLTree_endElementExt定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:

/**
 * Extended XML tree node for start tags -- includes attribute
 * information.
 * Appears header.headerSize bytes after a ResXMLTree_node.
 */
struct ResXMLTree_attrExt
{
    // String of the full namespace of this element.
    struct ResStringPool_ref ns;

    // String name of this node if it is an ELEMENT; the raw
    // character data if this is a CDATA node.
    struct ResStringPool_ref name;

    // Byte offset from the start of this structure where the attributes start.
    uint16_t attributeStart;

    // Size of the ResXMLTree_attribute structures that follow.
    uint16_t attributeSize;

    // Number of attributes associated with an ELEMENT.  These are
    // available as an array of ResXMLTree_attribute structures
    // immediately following this node.
    uint16_t attributeCount;

    // Index (1-based) of the "id" attribute. 0 if none.
    uint16_t idIndex;

    // Index (1-based) of the "class" attribute. 0 if none.
    uint16_t classIndex;

    // Index (1-based) of the "style" attribute. 0 if none.
    uint16_t styleIndex;
};

struct ResXMLTree_attribute
{
    // Namespace of this attribute.
    struct ResStringPool_ref ns;

    // Name of this attribute.
    struct ResStringPool_ref name;

    // The original raw string value of this attribute.
    struct ResStringPool_ref rawValue;

    // Processesd typed value of this attribute.
    struct Res_value typedValue;
};

/**
 * Extended XML tree node for element start/end nodes.
 * Appears header.headerSize bytes after a ResXMLTree_node.
 */
struct ResXMLTree_endElementExt
{
    // String of the full namespace of this element.
    struct ResStringPool_ref ns;

    // String name of this node if it is an ELEMENT; the raw
    // character data if this is a CDATA node.
    struct ResStringPool_ref name;
};

        内嵌在第一个ResXMLTree_node里头的ResChunk_header的各个成员变量的取值如下所示:

        --type:于等RES_XML_START_ELEMENT_TYPE,表现LinearLayout开始标签的部头。

        --headerSize:于等sizeof(ResXMLTree_node),表现部头的小大。

        --size:于等sizeof(ResXMLTree_node) + sizeof(ResXMLTree_attrExt) + sizeof(ResXMLTree_attribute) * 4。

        第一个ResXMLTree_node的其余成员变量的取值如下所示:

        --lineNumber:于等LinearLayout开始标签在本来本文格式的Xml文件现出的行号。

        --comment:于等LinearLayout标签的注释在字符池资源池的索引。

        ResXMLTree_attrExt的各个成员变量的取值如下所示:

        --ns:于等LinearLayout元素的令命间空在字符池资源池的索引,没有指定则于等-1。

        --name:于等字符串“LinearLayout”在字符池资源池的索引。

        --attributeStart:于等sizeof(ResXMLTree_attrExt),表现LinearLayout的属性chunk相对type值为RES_XML_START_ELEMENT_TYPE的ResXMLTree_node部头的置位。

        --attributeSize:于等sizeof(ResXMLTree_attribute),表现个一每属性占据的chunk小大。

        --attributeCount:于等4,表现有4个属性chunk。

        --idIndex:如果LinearLayout元素有一个称名为“id”的属性,那么就将它出当初属性列表中的置位再加上1的值记录在idIndex中,否则的话,idIndex的值就于等0。

        --classIndex:如果LinearLayout元素有一个称名为“class”的属性,那么就将它出当初属性列表中的置位再加上1的值记录在classIndex中,否则的话,classIndex的值就于等0。

        --styleIndex:如果LinearLayout元素有一个称名为“style”的属性,那么就将它出当初属性列表中的置位再加上1的值记录在styleIndex中,否则的话,styleIndex的值就于等0。

        LinearLayout元素有四个属性,个一每属性都对应一个ResXMLTree_attribute,接来下我们就以称名为“orientation”的属性为例,来讲明它的各个成员变量的取值,如下所示:

        --ns:于等属性orientation的令命间空在字符池资源池的索引,没有指定则于等-1。

        --name:于等属性称名字符串“orientation”在字符池资源池的索引。

        --rawValue:于等属性orientation的原始值“vertical”在字符池资源池的索引,这是可选的,如果不用保留,它的值就于等-1。

        称名为“orientation”的ResXMLTree_attribute的成员变量typedValue是一个Res_value,它定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:

/**
 * Representation of a value in a resource, supplying type
 * information.
 */
struct Res_value
{
    // Number of bytes in this structure.
    uint16_t size;

    // Always set to 0.
    uint8_t res0;

    // Type of the data value.
    enum {
        // Contains no data.
        TYPE_NULL = 0x00,
        // The 'data' holds a ResTable_ref, a reference to another resource
        // table entry.
        TYPE_REFERENCE = 0x01,
        // The 'data' holds an attribute resource identifier.
        TYPE_ATTRIBUTE = 0x02,
        // The 'data' holds an index into the containing resource table's
        // global value string pool.
        TYPE_STRING = 0x03,
        // The 'data' holds a single-precision floating point number.
        TYPE_FLOAT = 0x04,
        // The 'data' holds a complex number encoding a dimension value,
        // such as "100in".
        TYPE_DIMENSION = 0x05,
        // The 'data' holds a complex number encoding a fraction of a
        // container.
        TYPE_FRACTION = 0x06,

        // Beginning of integer flavors...
        TYPE_FIRST_INT = 0x10,

        // The 'data' is a raw integer value of the form n..n.
        TYPE_INT_DEC = 0x10,
        // The 'data' is a raw integer value of the form 0xn..n.
        TYPE_INT_HEX = 0x11,
        // The 'data' is either 0 or 1, for input "false" or "true" respectively.
        TYPE_INT_BOOLEAN = 0x12,

        // Beginning of color integer flavors...
        TYPE_FIRST_COLOR_INT = 0x1c,

        // The 'data' is a raw integer value of the form #aarrggbb.
        TYPE_INT_COLOR_ARGB8 = 0x1c,
        // The 'data' is a raw integer value of the form #rrggbb.

        // The 'data' is a raw integer value of the form #aarrggbb.
        TYPE_INT_COLOR_ARGB8 = 0x1c,
        // The 'data' is a raw integer value of the form #rrggbb.
        TYPE_INT_COLOR_RGB8 = 0x1d,
        // The 'data' is a raw integer value of the form #argb.
        TYPE_INT_COLOR_ARGB4 = 0x1e,
        // The 'data' is a raw integer value of the form #rgb.
        TYPE_INT_COLOR_RGB4 = 0x1f,

        // ...end of integer flavors.
        TYPE_LAST_COLOR_INT = 0x1f,

        // ...end of integer flavors.
        TYPE_LAST_INT = 0x1f
    };
    uint8_t dataType;

    // Structure of complex data values (TYPE_UNIT and TYPE_FRACTION)
    enum {
        // Where the unit type information is.  This gives us 16 possible
        // types, as defined below.
        COMPLEX_UNIT_SHIFT = 0,
        COMPLEX_UNIT_MASK = 0xf,

        // TYPE_DIMENSION: Value is raw pixels.
        COMPLEX_UNIT_PX = 0,
        // TYPE_DIMENSION: Value is Device Independent Pixels.
        COMPLEX_UNIT_DIP = 1,
        // TYPE_DIMENSION: Value is a Scaled device independent Pixels.
        COMPLEX_UNIT_SP = 2,
        // TYPE_DIMENSION: Value is in points.
        COMPLEX_UNIT_PT = 3,
        // TYPE_DIMENSION: Value is in inches.
        COMPLEX_UNIT_IN = 4,
        // TYPE_DIMENSION: Value is in millimeters.
        COMPLEX_UNIT_MM = 5,

        // TYPE_FRACTION: A basic fraction of the overall size.
        COMPLEX_UNIT_FRACTION = 0,
        // TYPE_FRACTION: A fraction of the parent size.
        COMPLEX_UNIT_FRACTION_PARENT = 1,

        // Where the radix information is, telling where the decimal place
        // appears in the mantissa.  This give us 4 possible fixed point
        // representations as defined below.
        COMPLEX_RADIX_SHIFT = 4,
        COMPLEX_RADIX_MASK = 0x3,

        // The mantissa is an integral number -- i.e., 0xnnnnnn.0
        COMPLEX_RADIX_23p0 = 0,
        // The mantissa magnitude is 16 bits -- i.e, 0xnnnn.nn
        COMPLEX_RADIX_16p7 = 1,
        // The mantissa magnitude is 8 bits -- i.e, 0xnn.nnnn
        COMPLEX_RADIX_8p15 = 2,

        // The mantissa magnitude is 0 bits -- i.e, 0x0.nnnnnn
        COMPLEX_RADIX_0p23 = 3,

        // Where the actual value is.  This gives us 23 bits of
        // precision.  The top bit is the sign.
        COMPLEX_MANTISSA_SHIFT = 8,
        COMPLEX_MANTISSA_MASK = 0xffffff
    };

    // The data for this item, as interpreted according to dataType.
    uint32_t data;

    void copyFrom_dtoh(const Res_value& src);
};

         一个属性的值经过剖析后之,也就是经过面前编译Xml资源的第3个操纵后之,就用一个Res_value来表现。 例如,对于称名为“orientation”的属性的值“vertical”来讲,经过剖析后之,它就会用一个Res_value来表现,这个Res_value的各个成员变量的值如下所示:

        --size:于等sizeof(Res_value)。

        --res0:于等0,保留给以后用。

        --dataType:于等TYPE_INT_DEC,表现数据类型,即这是一个十进制式形的整数值。

        --data:于等1,参考面前编译Xml资源的第3个操纵。

        更多的数据类型,请参考Res_value定义里头的注释。从这里我们就够能看出,在剖析二进制格式的Xml文件的程过中,当我们晓得一个属性的称名在字符串资源池的索引后之,就够能通过这个索引在字符串资源池中找到对应的属性称名字符中,同时,通过这个索引还可以在资源ID的那块chunk中找到对应的属性资源ID,而有了这个属性资源ID后之,我们就够能进一步地验证该属性的取值是不是正确等操纵。

        内嵌在第二个ResXMLTree_node里头的ResChunk_header的各个成员变量的取值如下所示:

        --type:于等RES_XML_END_ELEMENT_TYPE,表现LinearLayout结束标签的部头。

        --headerSize:于等sizeof(ResXMLTree_node),表现部头的小大。

        --size:于等sizeof(ResXMLTree_node) + sizeof(ResXMLTree_endElementExt) 。

        第二个ResXMLTree_node的其余成员变量的取值如下所示:

        --lineNumber:于等LinearLayout结束标签在本来本文格式的Xml文件现出的行号。

        --comment:于等-1。

        ResXMLTree_endElementExt的各个成员变量的取值如下所示:

        --ns:于等LinearLayout元素的令命间空在字符池资源池的索引,没有指定则于等-1。

        --name:于等字符串“LinearLayout”在字符池资源池的索引。

        注意,位于称名为“gravity”的ResXMLTree_attribute和第二个ResXMLTree_node之间的chunk是用来写入LinearLayout元素的两个子元素Button的内容的。这两个Button与LinearLayout一样,都是以同相的结构递归写入到终究的二进制Xml文件中去的,区分只在于结构内容的不同,以及属性个数的不同。

        例如,对于第一个Button的属性id来讲,它所对应的ResXMLTree_attribute的成员变量typedValue所指向的一个Res_value的各个成员变量的值如下所示:

        --size:于等sizeof(Res_value)。

        --res0:于等0,保留给以后用。

        --dataType:于等TYPE_REFERENCE,表现数据类型,即这是一个用引值。

        --data:于等分配给称名为“button_start_in_process”的ID的资源ID值,它的Package ID于等0x7f,而Type ID于等0x04,Entry ID取决于它的现出序次。

        又如,对于第一个Button的属性text来讲,它所对应的ResXMLTree_attribute的成员变量typedValue所指向的一个Res_value的各个成员变量的值如下所示:

        --size:于等sizeof(Res_value)。

        --res0:于等0,保留给以后用。

        --dataType:于等TYPE_REFERENCE,表现数据类型,即这是一个用引值。

        --data:于等分配给称名“start_in_process”的字符串的资源ID值,它的Package ID于等0x7f,而Type ID于等0x05,Entry ID取决于它的现出序次。

        对于一个Xml文件来讲,它除了有命名间空和普通标签类型的Node外之,还有一些称为CDATA类型的Node,例如,设假一个Xml文件,它的一个Item标签的内容如下所示:

......

    <Item>This is a normal text</Item>

......

        那么字符串“This is a normal text”就称为一个CDATA,它在二进制Xml文件用中一个ResXMLTree_node和一个ResXMLTree_cdataExt来述描,如图16所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第16张

    图16 CDATA类型的Xml Node的二进制表现

         ResXMLTree_cdataExt定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:

/**
 * Extended XML tree node for CDATA tags -- includes the CDATA string.
 * Appears header.headerSize bytes after a ResXMLTree_node.
 */
struct ResXMLTree_cdataExt
{
    // The raw CDATA character data.
    struct ResStringPool_ref data;

    // The typed value of the character data if this is a CDATA node.
    struct Res_value typedData;
};

         内嵌在面上的ResXMLTree_node的ResChunk_header的各个成员变量的取值如下所示:

        --type:于等RES_XML_CDATA_TYPE,表现CDATA部头。

        --headerSize:于等sizeof(ResXMLTree_node),表现部头的小大。

        --size:于等sizeof(ResXMLTree_node) + sizeof(ResXMLTree_cdataExt) 。

        面上的ResXMLTree_node的其余成员变量的取值如下所示:

        --lineNumber:于等字符串“This is a normal text”在本来本文格式的Xml文件现出的行号。

        --comment:于等字符串“This is a normal text”的注释,如果没有则于等-1。

        下面的ResXMLTree_cdataExt的成员变量data于等字符串“This is a normal text”在字符串资源池的索引,另外一个成员变量typedData所指向的一个Res_value的各个成员变量的值如下所示:

        --size:于等sizeof(Res_value)。

        --res0:于等0,保留给以后用。

        --dataType:于等TYPE_NULL,表现没有含包数据,数据经已含包在ResXMLTree_cdataExt的成员变量data中。

        --data:于等0,由于dataType于等TYPE_NULL,这个值是没有意义的。

        至此,一个Xml文件就从按照图8以及图10的步调从本文格式编译成二进制格式了。当有所的Xml文件都编译成完后之,接来下就开始生成资源符号了。

        八. 生成资源符号

        这里生成资源符号为前面生成R.java文件做好备准的。从面前的操纵可以晓得,有所搜集到的资源项都按照类型来存保在一个资源表中,即存保在一个ResourceTable对象。因此,Android资源打包工具aapt只要遍历个一每Package里头的个一每Type,然后取出个一每Entry的称名,并且根据这个Entry在自己的Type里头现出的序次来计算失掉它的资源ID,那么就够能生成一个资源符号了,这个资源符号由称名以及资源ID所成组。

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

        九. 生成资源索引表

        我们首先总结一下,经过上述八个操纵后之,所得获的资源列表如图17所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第17张

    图17 搜集到的有所资源项

        有了这些资源项后之,Android资源打包工具aapt就够能按照下面的流程来生成资源索引表resources.arsc了,如图18所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第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所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第19张

    图19 Package资源项元信息数据块的生成程过

        这个生成程过可以分为5个步调,接来下我们就以图17所示的资源项来讲详细析分个一每步调。

        Step 1. 写入Package资源项元信息数据块部头

        Package资源项元信息数据块部头是用一个ResTable_package来定义的。ResTable_package定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:

/**
 * 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.
    char16_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;
};

        嵌入在ResTable_package外部的ResChunk_header的各个成员变量的取值如下所示:

        --type:于等RES_TABLE_PACKAGE_TYPE,表现这是一个Package资源项元信息数据块部头。

        --headerSize:于等sizeof(ResTable_package),表现部头小大。

        --size:于等sizeof(ResTable_package) + 类型字符串资源池小大 + 资源项称名字符串资源池小大 + 类型规范数据块小大 + 数据项信息数据块小大。

        ResTable_package的其它成员变量的取值如下所示:

        --id:于等Package ID。

        --name:于等Package Name。

        --typeStrings:于等类型字符串资源池相对部头的移偏置位。

        --lastPublicType:于等最后一个导出的Public类型字符串在类型字符串资源池中的索引,目前这个值设置为类型字符串资源池的小大。

        --keyStrings:于等资源项称名字符串相对部头的移偏置位。

        --lastPublicKey:于等最后一个导出的Public资源项称名字符串在资源项称名字符串资源池中的索引,目前这个值设置为资源项称名字符串资源池的小大。

        我们可以通过图20来清楚地看到一个Package资源项元信息数据块的结构:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第20张

    图20 Package资源项元信息数据块结构

        在Android资源中,有一种资源类型称为Public,它们一般是定义在res/values/public.xml文件中,式形如下所示:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <public type="string" name="string3"   />
</resources>

        这个public.xml用来诉告Android资源打包工具aapt,将类型为string的资源string3的ID固定为0x7f040001。为什么须要将某一个资源项的ID固定来下呢?一般来讲,当我们将自己定义的资源导出来给第三方应用程序应用时,为了保证以后修改这些导出资源时,仍然保证第三方应用程序的兼容性,就须要给那些导出资源一个固定的资源ID。

        每当Android资源打包工具aapt重新编译被修改过的资源时,都市重新给这些资源付与ID,这便可能会造成同一个资源项在两次不同的编译中被付与不同的ID。种这情况就会给第三方应用程序程序带来麻烦,因为后者一般是设假一个ID对应的永远是同一个资源的。因此,当我们将自己定义的资源导出来给第三方应用程序应用时,就须要通过public.xml文件将导出来的资源的ID固定来下。

        我们用一个例子来讲public.xml文件的作用,考虑下面这个strings.xml文件:

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

        设假Android资源打包工具aapt为字符串资源项string1和string3分配到的资源ID如下所示:

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

        这时候第三方应用程序就会认为0x7f040001用引的永远是字符串“String 3”。

        设假将来的某一天,我们须要在strings.xml文件中增长一个新的字符串,如下所示:

<?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>

        如果没有上述的public.xml文件,那么Android资源打包工具aapt为字符串资源项string1、 string2和string3分配的资源ID就会如下所示:

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

        这就完蛋了,这时候第三方应用程序通过0x7f040001用引到的字符串变成了“String 2”。

        如果我们应用上述的public.xml文件将字符串“String 3”固定为0x7f040001,那么Android资源打包工具aapt为字符串资源项string1、 string2和string3分配的资源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
    }
}

        这样第三方应用程序通过0x7f040001用引到的字符串仍然是“String 3”。

        注意,我们在开发应用程序时,一般是不须要用到public.xml文件的,因为我们的资源基本上都是在外部应用的,不会导出来给第三方应用程序应用。只在外部应用的资源,不管它的ID如何变化,我们都可以通过R.java文件定义的常量来正确地用引它们。只有统系定义的资源包才会应用到public.xml文件,因为它定义的资源是须要供提给第三方应用程序应用的。

        Step 2. 写入类型字符串资源池

        在面前的第1个操纵中,我们经已将个一每Package用到的类型字符串搜集起来了,因此,这里就够能直接将它们写入到Package资源项元信息数据块部头前面的那个数据块去。

        Step 3. 写入资源项称名字符串资源池

        在面前的第2个操纵中,我们经已将个一每Package用到的资源项称名字符串搜集起来了,这里就够能直接将它们写入到类型字符串资源池前面的那个数据块去。

        Step 4. 写入类型规范数据块

        类型规范数据块用来述描资源项的置配差异性。通过这个差异性述描,我们就够能晓得个一每资源项的置配状况。类型规范数据块是按照类型来织组的,也就是说,每一种类型都对应有一个类型规范数据块。

        类型规范数据块的部头是用一个ResTable_typeSpec来定义的。ResTable_typeSpec定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:

/**
 * A specification of the resources defined by a particular type.
 *
 * There should be one of these chunks for each resource type.
 *
 * This structure is followed by an array of integers providing the set of
 * configuation change flags (ResTable_config::CONFIG_*) that have multiple
 * resources for that configuration.  In addition, the high bit is set if that
 * resource has been made public.
 */
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 {
        // Additional flag indicating an entry is public.
        SPEC_PUBLIC = 0x40000000
    };
};

        嵌入在ResTable_typeSpec里头的ResChunk_header的各个成员变量的取值如下所示:

        --type:于等RES_TABLE_TYPE_SPEC_TYPE,用来述描一个类型规范部头。

        --headerSize:于等sizeof(ResTable_typeSpec),表现部头的小大。

        --size:于等sizeof(ResTable_typeSpec) + sizeof(uint32_t) * entryCount,其中,entryCount表现本类型的资源项个数。

        ResTable_typeSpec的其它成员变量的取值如下所示:

        --id:表现资源的Type ID。

        --res0:于等0,保留以后应用。

        --res1:于等0,保留以后应用。

        --entryCount:于等本类型的资源项个数,注意,这里是指称名同相的资源项的个数。

        ResTable_typeSpec前面紧跟着的是一个小大为entryCount的uint32_t组数,个一每组数元数,即个一每uint32_t,都是用来述描一个资源项的置配差异性的。例如,在图17中,称名为icon的drawable资源项有三种不同的屏幕置配ldpi、mdpi和hdpi,于是用来述描它的置配差异性的uint32_t的第CONFIG_DENSITY位就于等1,而其余位都于等0。又如,在图17中,称名为main的layout资源项只有一种置配default,于是用来述描它的置配差异性的uint32_t的值就于等0。此外,如果一个资源项是导出的,即它的资源ID是通过public.xml来固定的,那么用来述描它的置配差异性的uint32_t的第ResTable_typeSpec::SPEC_PUBLIC位也会被设置为1。

        在图17中,一共有4种不同类型的资源项,它们所对应的4个类型规范数据块如图21至图24所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第21张

    图21 类型为drawable的规范数据块

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第22张

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

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第23张

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

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第24张

    图24 类型为id的规范数据块

        Step 5. 写入类型资源项数据块

        类型资源项数据块用来述描资源项的体具信息, 这样我们就够能晓得个一每资源项称名、值和置配等信息。类型资源项数据一样是按照类型和置配来织组的,也就是说,一个拥有N个置配的类型一共对应有N个类型资源项数据块。

        类型资源项数据块的部头是用一个ResTable_type来定义的。ResTable_type定义在文件frameworks/base/include/utils/ResourceTypes.h中,如下所示:

/**
 * A collection of resource entries for a particular resource data
 * type. Followed by an array of uint32_t defining the resource
 * values, corresponding to the array of type strings in the
 * ResTable_package::typeStrings string block. Each of these hold an
 * index from entriesStart; a value of NO_ENTRY means that entry is
 * not defined.
 *
 * There may be multiple of these chunks for a particular resource type,
 * supply different configuration variations for the resource values of
 * that type.
 *
 * It would be nice to have an additional ordered index of entries, so
 * we can do a binary search if trying to find a resource by string name.
 */
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;

    // Must be 0.
    uint8_t res0;
    // Must be 0.
    uint16_t res1;

    // 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.
    ResTable_config config;
};

        嵌入在ResTable_type里头的ResChunk_header的各个成员变量的取值如下所示:

        --type:于等RES_TABLE_TYPE_TYPE,用来述描一个类型资源项部头。

        --headerSize:于等sizeof(ResTable_type),表现部头的小大。

        --size:于等sizeof(ResTable_type) + sizeof(uint32_t) * entryCount,其中,entryCount表现本类型的资源项个数。

        ResTable_type的其它成员变量的取值如下所示:

        --id:表现资源的Type ID。

        --res0:于等0,保留以后应用。

        --res1:于等0,保留以后应用。

        --entryCount:于等本类型的资源项个数,注意,这里是指称名同相的资源项的个数。

        --entriesStart:于等资源项数据块相对部头的移偏值。

        --config:指向一个ResTable_config,用来述描置配信息,它的定义可以参考图2的类图。

        ResTable_type紧跟着的是一个小大为entryCount的uint32_t组数,个一每组数元数,即个一每uint32_t,都是用来述描一个资源项数据块的移偏置位。紧跟在这个uint32_t组数前面的是一个小大为entryCount的ResTable_entry组数,个一每组数元素,即个一每ResTable_entry,都是用来述描一个资源项的体具信息。

        在图17中,一共有4种不同类型的资源项,其中,类型为drawable的资源有1个资源项以及3种不同的置配,类型为layout的资源有2个资源项以及1种置配,类型为string的资源有5个资源项以及1种置配,类型为id的资源有2个资源项以及1种置配,这样一共就对应有3 + 1 + 1 + 1个类型资源项数据块,如图25至图图30所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第25张

    图25 类型为drawable和置配为ldpi的资源项数据块

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第26张

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

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第27张

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

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第28张

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

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第29张

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

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第30张

    图30 类型为id和置配为default的资源项数据块

       注意,ResTable_type前面的uint32_t组数和ResTable_entry组数的小大不一定是相等的,考虑下面的资源目录:

--res
  --drawable-ldpi
    --icon.png
  --drawable-mdpi
    --icon.png
    --logo.png
  --drawable-hdpi
    --logo.png

        那么终究失掉类型为drawable和置配为ldpi的资源项数据块如图31所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第31张

    图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中,如下所示:
/**
 * This is the beginning of information about an entry in the resource
 * table.  It holds the reference to the name of this entry, and is
 * immediately followed by one of:
 *   * A Res_value structure, if FLAG_COMPLEX is -not- set.
 *   * An array of ResTable_map structures, if FLAG_COMPLEX is set.
 *     These supply a set of name/value mappings of data.
 */
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
    };
    uint16_t flags;

    // Reference into ResTable_package::keyStrings identifying this entry.
    struct ResStringPool_ref key;
};

        ResTable_entry的各个成员变量的取值如下所示:

            --size:于等sizeof(ResTable_entry),表现资源项部头小大。

            --flags:资源项标志位。如果是一个Bag资源项,那么FLAG_COMPLEX位就于等1,并且在ResTable_entry前面跟有一个ResTable_map组数,否则的话,ResTable_entry前面跟的是一个Res_value。如果是一个可以被用引的资源项,那么FLAG_PUBLIC位就于等1。

            --key:资源项称名在资源项称名字符串资源池的索引。

        接来下我们就分两种情况来讨论资源项信息写入到资源索引表的程过。

        首先看一个普通的资源项,即一个非Bag资源项的写入程过。从图2可以晓得,个一每资源项的数据都是用一个Item来述描的。在这个Item中,有一个类型为Res_value的成员变量parsedValue,它表现一个资源项经过剖析后失掉值。

        面前在析分Xml资源文件的编译程过时,我们经已绍介过Res_value的定义了。设假前当要写入的资源项是类型为layout的main,从图17可以晓得,它的值是一个字符串“res/layout/main.xml”。字符串“res/layout/main.xml”在面前的第3步中经已被写入到一个资源项值字符串池中去了,我们设假它被写入在第3个置位上,那么用来述描资源项main的Res_value的各个成员变量的取值如下所示:

            --size:于等sizeof(Res_value)。

            --res0:于等0,保留以后应用。

            --dataType:于等TYPE_STRING。

            --data:于等0x3。

            我们通过图32来总结一个普通资源项写入到资源表索引表的数据块结构:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第32张

    图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所示:

    资源、文件Android应用程序资源的编译和打包过程分析by小雨第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中,如下所示:

/**
 *  This is a reference to a unique entry (a ResTable_entry structure)
 *  in a resource table.  The value is structured as: 0xpptteeee,
 *  where pp is the package index, tt is the type index in that
 *  package, and eeee is the entry index in that type.  The package
 *  and type values start at 1 for the first item, to help catch cases
 *  where they have not been supplied.
 */
struct ResTable_ref
{
    uint32_t ident;
};

/**
 * Extended form of a ResTable_entry for map entries, defining a parent map
 * resource from which to inherit values.
 */
struct ResTable_map_entry : public ResTable_entry
{
    // Resource identifier of the parent mapping, or 0 if there is none.
    ResTable_ref parent;
    // Number of name/value pairs that follow for FLAG_COMPLEX.
    uint32_t count;
};

       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中,如下所示:

struct ResTable_map
{
    // The resource identifier defining this mapping's name.  For attribute
    // resources, 'name' can be one of the following special resource types
    // to supply meta-data about the attribute; for all other resource types
    // it must be an attribute resource.
    ResTable_ref name;

    .....

    // This mapping's value.
    Res_value value;
};

        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中,如下所示:

/**
 * Header for a resource table.  Its data contains a series of
 * additional chunks:
 *   * A ResStringPool_header containing all table values.
 *   * One or more ResTable_package chunks.
 *
 * Specific entries within a resource table can be uniquely identified
 * with a single integer as defined by the ResTable_ref structure.
 */
struct ResTable_header
{
    struct ResChunk_header header;

    // The number of ResTable_package structures.
    uint32_t packageCount;
};

        嵌入在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为称名的常量,如下所示:

public final class R {
    ......

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

    ......
}

        注意,个一每资源类型在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,欢送存眷!

文章结束给大家分享下程序员的一些笑话语录: 《诺基亚投资手机浏览器UCWEB,资金不详或控股》杯具了,好不容易养大的闺女嫁外国。(心疼是你养的吗?中国创业型公司创业初期哪个从国有银行贷到过钱?)

免责声明:文章转载自《资源、文件Android应用程序资源的编译和打包过程分析by小雨》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇原始套接字简介【转载】K8S单机版nacos下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Dubbo封装rest服务返回结果

由于Dubbo服务考虑到一个是给其他系统通过RPC调用,另外一个是提供HTTP协议本身系统的后台管理页面,因此Dubbo返回参数在rest返回的时候配置拦截器进行处理。 在拦截器中,对返回参数封装成如下对象,并统一输出到前端。 1 packagecom.wjs.common.web; 2 3 importorg.apache.commons.lang....

【转】Android APK的数字签名的作用和意义

1. 什么是数字签名? 数字签名就是为你的程序打上一种标记,来作为你自己的标识,当别人看到签名的时候会知道它是与你相关的     2. 为什么要数字签名? 最简单直接的回答: 系统要求的。  Android系统要求每一个Android应用程序必须要经过数字签名才能够安装到系统中,也就是说如果一个Android应用程序没有经过数字签名,是没有...

kafka springboot (或 springcloud ) 整合

狂创客圈 经典图书 : 《Netty Zookeeper Redis 高并发实战》 面试必备 + 面试必备 + 面试必备 【博客园总入口 】 疯狂创客圈 经典图书 : 《SpringCloud、Nginx高并发核心编程》 大厂必备 + 大厂必备 + 大厂必备 【博客园总入口 】 入大厂+涨工资必备: 高并发【 亿级流量IM实战】 实战系列 【 Sprin...

tips

史上最全的Unity面试题(持续更新总结。。。。。。) 包含答案的Unity面试题 这个是我刚刚整理出的Unity面试题,为了帮助大家面试,同时帮助大家更好地复习Unity知识点,如果大家发现有什么错误,(包括错别字和知识点),或者发现哪里描述的不清晰,请在下面留言,我会重新更新,希望大家共同来帮助开发者 一:什么是协同程序? 在主线程运行的同时开启另...

.NET插件系统

面临的问题       在开发插件系统中,我们通常会面临这样的问题:        一些功能并不是在开启时就要被使用的,例如VS中的大量功能对一个大部分程序员来说用不着,但框架本身却应该向用户提供该插件的相应信息?        在可视化的插件功能列表中,我们不仅希望提供简单的插件名称信息,更希望能以图片,或动画等形式展示其功能特性,便于用户选择。   ...

ES6 阮一峰阅读学习

参考: ECMAScript6入门   就是随便看看,了解一下。 一、ECMAScript6简介 1. 什么是ECMAScript6? JavaScript语言的下一代标准。2015年6月发布,正式名称是《ECMAScript2015标准》。 思考:编程语言和英语、法语语言差不多吧。其实就是语言的标准、规范之类,和英语语法差不多的东西吧 = =! 二、le...