1. 程式人生 > >轉自老羅 Android應用程式資源的編譯和打包過程分析

轉自老羅 Android應用程式資源的編譯和打包過程分析

原文地址   http://blog.csdn.net/luoshengyang/article/details/8744683 轉載自老羅,轉載請說明

 

我們知道,在一個APK檔案中,除了有程式碼檔案之外,還有很多資原始檔。這些資原始檔是通過Android資源打包工具aapt(Android Asset Package Tool)打包到APK檔案裡面的。在打包之前,大部分文字格式的XML資原始檔還會被編譯成二進位制格式的XML資原始檔。在本文中,我們就詳細分析XML資原始檔的編譯和打包過程,為後面深入瞭解Android系統的資源管理框架打下堅實的基礎。

        在前面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所示:


圖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資源打包工具的執行過程

        假設我們當前要編譯的應用程式資源目錄結構如下所示:

[plain] view plain copy print ?
  1. project 
  2.   --AndroidManifest.xml 
  3.   --res 
  4.     --drawable-ldpi 
  5.       --icon.png 
  6.     --drawable-mdpi 
  7.       --icon.png 
  8.     --drawable-hdpi 
  9.       --icon.png 
  10.     --layout 
  11.       --main.xml 
  12.       --sub.xml 
  13.     --values 
  14.       --strings.xml 
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中,如下所示:

[cpp] view plain copy print ?
  1. class AaptAssets : public AaptDir 
  2.     ...... 
  3.  
  4. private
  5.     ...... 
  6.  
  7.     KeyedVector<String8, sp<ResourceTypeSet> >* mRes; 
  8. }; 
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,如下所示:

[cpp] view plain copy print ?
  1. class ResourceTable : public ResTable::Accessor 
  2.     ...... 
  3.  
  4. private
  5.     ...... 
  6.  
  7.     DefaultKeyedVector<String16, sp<Package> > mPackages; 
  8.     Vector<sp<Package> > mOrderedPackages; 
  9.     
  10.     ...... 
  11. }; 
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所示:


圖5 收集到的drawable和layout資源項列表

        五. 編譯values類資源

        型別為values的資源描述的都是一些簡單的值,如陣列、顏色、尺寸、字串和樣式值等,這些資源是在編譯的過程中進行收集的。接下來,我們就以字串的編譯過程來進行說明。

        在這篇文章中要用到的例子中,包含有一個strings.xml的檔案,它的內容如下所示:

[html] view plain copy print ?
  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <resources> 
  3.     <string name="app_name">Activity</string> 
  4.     <string name="sub_activity">Sub Activity</string> 
  5.     <string name="start_in_process">Start sub-activity in process</string> 
  6.     <string name="start_in_new_process">Start sub-activity in new process</string> 
  7.     <string name="finish">Finish activity</string> 
  8. </resources> 
<?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所示:


圖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檔案,它的內容如下所示:

[html] view plain copy print ?
  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <resources> 
  3.     <attr name="custom_orientation"> 
  4.         <enum name="custom_vertical" value="0" /> 
  5.         <enum name="custom_horizontal" value="1" /> 
  6.     </attr> 
  7. </resources> 
<?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所示:

圖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檔案,它的內容如下所示:

[html] view plain copy print ?
  1. <?xml version="1.0" encoding="utf-8"?> 
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  3.     android:orientation="vertical" 
  4.     android:layout_width="fill_parent" 
  5.     android:layout_height="fill_parent"  
  6.     android:gravity="center"> 
  7.     <Button  
  8.         android:id="@+id/button_start_in_process" 
  9.         android:layout_width="wrap_content" 
  10.         android:layout_height="wrap_content" 
  11.         android:gravity="center" 
  12.         android:text="@string/start_in_process" > 
  13.     </Button> 
  14.     <Button  
  15.         android:id="@+id/button_start_in_new_process" 
  16.         android:layout_width="wrap_content" 
  17.         android:layout_height="wrap_content" 
  18.         android:gravity="center" 
  19.         android:text="@string/start_in_new_process" > 
  20.     </Button> 
  21. </LinearLayout> 
<?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>

        Xml資原始檔main.xml的編譯過程如圖8所示:

圖8 Xml資原始檔的編譯過程

        1. 解析Xml檔案

         解析Xml檔案是為了可以在記憶體中用一系列樹形結構的XMLNode來表示它。XMLNode類的定義在檔案frameworks/base/tools/aapt/XMLNode.h中,如下所示:

[cpp] view plain copy print ?
  1. class XMLNode : public RefBase 
  2.     ...... 
  3.  
  4. private
  5.     ...... 
  6.  
  7.     String16 mElementName; 
  8.     Vector<sp<XMLNode> > mChildren; 
  9.     Vector<attribute_entry> mAttributes; 
  10.     ...... 
  11.     String16 mChars; 
  12.     ...... 
  13. }; 
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所示:


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

[cpp] view plain copy print ?
  1. /**
  2. * Header that appears at the front of every data chunk in a resource.
  3. */ 
  4. struct ResChunk_header 
  5.     // Type identifier for this chunk.  The meaning of this value depends 
  6.     // on the containing chunk. 
  7.     uint16_t type; 
  8.  
  9.     // Size of the chunk header (in bytes).  Adding this value to 
  10.     // the address of the chunk allows you to find its associated data 
  11.     // (if any). 
  12.     uint16_t headerSize; 
  13.  
  14.     // Total size of this chunk (in bytes).  This is the chunkSize plus 
  15.     // the size of any data associated with the chunk.  Adding this value 
  16.     // to the chunk allows you to completely skip its contents (including 
  17.     // any child chunks).  If this value is the same as chunkSize, there is 
  18.     // no data associated with the chunk. 
  19.     uint32_t size; 
  20. }; 
  21.  
  22. /**
  23. * XML tree header.  This appears at the front of an XML tree,
  24. * describing its content.  It is followed by a flat array of
  25. * ResXMLTree_node structures; the hierarchy of the XML doc