咱們平時都是用 AS 進行打包,這就形成了不少盲點,咱們就來看看到底是咋回事,提早聲明這篇文章講的不全,只講一些疑惑盲點,須要全面學習的,看老羅的吧,詳細的使人髮指。html
咱們從結果入手,看看打包完畢的apk裏面是啥模樣,把.apk 修改爲 .zip 解壓縮。按圖索驥哈哈。java
一共五個文件,第一個 AndroidManifest.xml 咱們很熟悉了。打開看一下。 個人個乖乖,咋回事。 而後在打開 res 文件,不少維度的 drawable 文件夾啊。 隨便打開一個吧,看看裏面的 xml 文件,nm,咋也都是二進制數據了呢?好吧,這些貌似是和 資源編譯打包有關係哈,印象裏好像是這麼回事,趕忙百度,谷歌。看到了以下的神圖貌似和咱們遇到的相似啊。node
aapt 這傢伙,把咱們的資源搞了一下。他想幹什麼呢?帶着疑問咱們去學習。爲啥呢?若是猜想的話,應該不是閒的沒事搞得,咱們知道安卓面臨的問題一直是空間和時間的問題,這麼搞,無非就是省空間,提高速度。android
把全部 xml 元素的標籤,屬性,內容等字符串統一放到一個 字符串資源池裏面去,而後在用到這個字符串的地方用索引來代替,這是偷懶的行家啊,也是計算機裏的局部思想的發揚光大。這樣就能夠解決空間佔用的大小了。 那麼怎麼就速度快了呢?由於這裏的字符串用的是索引,因此就沒必要每次都解析字符串了,這還不快嗎?重複利用多開心啊。數組
ok,這個 xml 二進制的問題也就解決完畢。可是一個問題的結束每每伴隨着另外一個問題的開始。那個字符串資源池在哪裏呢?bash
這就引出了咱們的上面 五大部分的 Resources.arsc。先容我百度,谷歌下哈。 blog.csdn.net/beyond702/a… 很顯然我不是要解析 Resources.arsc,反正我知道了,這個字符串資源池就在 Resources.arsc 中,名字叫 Global String Pool,這樣就能夠了。app
好了回到 aapt 這個傢伙。工具
據網上總結他有如下幾個重要的工做。性能
一、 assert 和 res/raw 目錄下的全部資源原封不動打包到 apk 裏。 二、對 res/ 目錄下的文件進行編譯處理 好比 xml 編譯成二進制文件,png 等圖片也會進行優化處理。 三、除了 assert 資源以外的全部資源都會賦予一個資源ID常量,而且聲稱一個資源索引表 Resources.arsc。 四、把 AndroidManifest.xml 也進行二進制處理。 五、把上面四步驟中聲稱的結果保存到一個*.ap_ 文件,把各個資源 ID 常量定義在 R.java 文件中。學習
這麼一來解答了很多疑惑,可是 *.ap_ 是個啥玩意呢?下圖是網上的,說實話,我反正沒看到,我實驗了下沒有 .ap 文件。咋辦呢?繼續搜吧,多是文章有點老了。
仍是看官方文檔吧,我擦,appt2 了啊,好吧,看英文文檔使我快樂(😢)。文檔上說 appt 已經棄用,開始使用 appt2 了,雖然老項目也在用,可是你懂得。
developer.android.com/studio/comm…
終於找到了一篇文章,講述 appt2 編譯的。原來是 appt2 將原來的資源編譯打包過程拆分紅了兩部分,編譯和連接,提升了資源的編譯性能。當只有一個資源發上改變的時候,只須要從新編譯改變的文件就好了。其餘不變的進行連接就好了。以前 appt 是將全部的資源進行merge ,merge 完畢從新對全部資源進行編譯,產生一個 資源 ap_ 文件。這個也就是一個壓縮包。
具體細節你們有興趣能夠搞一搞。我內心仍是有點不明白這些個過程,全部又找了一篇文章來看看。
這個的圖真是詳細的很啊。
咱們要編譯的應用程序的資源結構目錄。圖文結合一下,否則不知道說的什麼。
得到包名,根據包名建立資源表 ResourceTable 。 那什麼是 ResourceTable 呢?Android資源打包工具在編譯應用資源以前,會建立一個資源表,當編譯完成後,就能夠拿着這個資源表,去生成資源索引文件 resources.arsc。
不光咱們本身的應用擁有資源包,android 系統也定義了一套通用資源。因此須要把這個也添加上。最重要的的 資源ID 的命名規則是這樣的。一共四位,Package ID,次高字節表示Type ID,最低兩字節表示Entry ID。
Package ID:好比系統的就是0x01 ,咱們本身的就是0x7f。
Type ID:資源的類型有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干種,每一種都會被賦予一個ID。
Entry ID:是指每個資源在其所屬的資源類型中所出現的次序。注意,不一樣類型的資源的Entry ID有多是相同的,可是因爲它們的類型不一樣,咱們仍然能夠經過其資源ID來區別開來。
總結就是 先按包名來分,而後看類型,最後看順序。
在編譯應用程序以前,aapt 會建立一個 AaptAsserts 對象。用來收集當前須要編譯的資源文件。保存到 AaptAsserts 的成員變量 mRes。
KeyedVector<String8, sp<ResourceTypeSet> >* mRes;
複製代碼
收集資源是按照類型來保存的 分別是 drawable、layout、values。因此對應了三種 ResourceTypeSet。
舉個例子說明下吧(其實我就是抄的)
一、類型是 drawalbe 的ResourceTypeSet 只有一個AaptGroup,它的名稱是 icon.png。這個AaptGroup 裏包含了三個文件 res/drawable-ldpi/icon.png、res/drawable-mdpi/icon.png和res/drawable-hdpi/icon.png。 每個文件都用一個 AaptFile 來描述,而且都對應一個 AaptGroupEntry。每一個 AaptGroupEntry 描述的都是不一樣的資源配置信息,即他們所描述的屏幕密度是ldpi、mdpi和hdpi。
二、類型是 layout 的的ResourceTypeSet 有兩個AaptGroup,分別是 main.xml 和 sub.xml。都只包含了一個 AaptFile ,分別是res/layout/main.xml和res/layout/sub.xml。一樣分別對應一個AaptGroupEntry。這兩個AaptGroupEntry描述的資源配置信息都是屬於default的。
三、類型爲 values 的ResourceTypeSet 只有一個 AaptGroup,爲 string.xml。包含了一個 AaptFile 即 res/values/strings.xml。一樣對應一個AaptGroupEntry,這個AaptGroupEntry描述的資源配置信息也是屬於default的。
上一步只是保存到 AaptAsserts 對象裏,咱們須要將這些資源同時增長到 ResourceTable 對象中,爲啥子呢?由於咱們要用 ResourceTable 來生成 resources.arsc。這樣看來思路就有點清晰了。
須要注意的是: 收集的資源不包括 values 類型的資源,它比較特殊,要通過編譯纔會添加到資源表中。(ps:又增長了一個問題)
舉個例子:
在這個名稱爲「shy.luo.activity」的Package中,分別包含有drawable和layout兩種類型的資源,每一種類型使用一個Type對象來描述,其中:
一、類型是 drawable 的Type,包含一個 ConfigList。名稱爲 icon.png。包含了三個 Entry,分別是res/drawable-ldpi/icon.png、res/drawable-mdpi/icon.png和res/drawable-hdpi/icon.png。每個 Entry 對應一個 ConfigDescription,用來描述不一樣的資源配置信息。
類型爲 values 終於開始要編譯了,以前的疑問看來要在這裏進行解答了。咱們一般用 values 來描述一些簡單的值,好比 顏色,大小,尺寸等等。這些資源是在編譯的過程當中收集的。具體怎麼收集看下邊。
strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Activity</string>
<string name="sub_activity">Sub Activity</string>
<string name="start_in_process">Start sub-activity in process</string>
<string name="start_in_new_process">Start sub-activity in new process</string>
<string name="finish">Finish activity</string>
</resources>
複製代碼
這個文件通過編譯後,資源表中會多了一個名爲 string 的 Type。這個 Tpye 還有五個 ConfigList 。這五個 ConfigList 的名稱分別爲 「app_name」、「sub_activity」、「start_in_process」、「start_in_new_process」和「finish」,每個ConfigList又分別含有一個Entry。
類型 values的資源除了 string 以外,還會有 bag,style,array 等。統一稱爲 Bag 資源。好比 Android 系統提供的android:orientation屬性的取值範圍爲{「vertical」、「horizontal」},就至關因而定義了vertical和horizontal兩個Bag。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<attr name="custom_orientation">
<enum name="custom_vertical" value="0" />
<enum name="custom_horizontal" value="1" />
</attr>
</resources>
複製代碼
看完了 Bag 的解釋。咱們看看是如何分配的ID的。上面是三個Entry均爲 Bag 資源。其中 custom_vertical(id類型資源)和custom_horizontal( id類型資源)是custom_orientation(attr類型資源)的兩個bag。咱們能夠將custom_vertical和custom_horizontal當作是custom_orientation的兩個元數據,用來描述custom_orientation的取值範圍。實際上,custom_orientation 還有一個內部元數據,用來描述它的類型。這個內部元數據也是經過一個 bag 來表示的,這個 bag 的名稱和值,分別是「^type」和TPYE_ENUM ,用來表示他描述的一個枚舉類型的屬性。注意:全部「^」開頭的bag都是表示一個內部元數據。
對於 Bag 資源來講,這一步須要給他們的元數據項分配資源ID,也就是給他們的bag分配資源ID,例如上述的 custom_orientation 來講,咱們須要給它的 ^type 、custom_horizontal 和 custom_vertical 分配資源ID。其中 ^type 分配到的是 attr 類型的資源ID,而custom_vertical和custom_horizontal分配的是 id 類型的資源ID。
前六步都是爲了編譯xml資源文件作準備。不容易啊。
開始編譯:
除了 values 類型的資源文件,其餘全部xml資源文件都須要編譯。以 main.xml 爲例。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center">
<Button
android:id="@+id/button_start_in_process"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/start_in_process" >
</Button>
<Button
android:id="@+id/button_start_in_new_process"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/start_in_new_process" >
</Button>
</LinearLayout>
複製代碼
一、解析xml 文件 這個就很少說了,最後獲得根節點的 XMLNode。 二、賦予屬性名稱資源ID。 好比根節點 LinearLayout 裏面有「android:orientation」、「android:layout_width」、「android:layout_height」和「android:gravity」都須要賦予一個資源ID。這就給是在系統資源包裏定義的。因此AAPT會從系統資源包裏找到這些名稱對應的資源ID,而後才能賦給main.xml 的根節點LinearLayout。
注意:對於系統資源包來講「android:orientation」、「android:layout_width」、「android:layout_height」等這些屬性名稱都是它定義的一系列 Bag資源。在被編譯的時候就分配好資源ID了。如第六步。
每個xml文件都是從根節點開始給屬性名稱賦予資源ID的,而後在遞歸給每個子節點的屬性名稱賦予資源ID。直到都得到爲止。 三、解析屬性值。 上一步只是對屬性的解析,這一步是對屬性值的解析。好比 main.xml 文件的根節點 LinearLayout 來講,咱們已經給他的屬性 android:orientation 賦值了一個資源 ID,這裏就是要給他的值 vertical 進行解析。上面說到了,android:orientation 是 Bag 資源,這個 Bag 資源分配有資源 ID,也會有元數據,也就是它的取值。對於 android:orientation 的合法取值就是 horizontal 或者 vertical 。而這兩個也是 Bag 資源。他們的值分別被定義爲 0 和 1。
AAPT 是如何找到 main.xml ->LinearLayout-> android:orientation->vertical 等於 1 的呢?假設上一步從系統資源找到資源 ID 0x010100c4,那麼 AAPT 會找到它的元數據,也就是 名爲 horizontal 和 vertical 的 Bag。接着根據字符串匹配到 vertical 的 Bag,最後就能夠將這個 Bag 解析了。
講個很基礎的一個東西,平時常常用,可是基本不會注意的一個點,對於引用類型的屬性值,好比 android:id屬性值「@+id/button_start_in_process」,其中 @ 表示後面描述的屬性是因引用類型的。+ 表示若是該引用不存在那麼就新建一個。id 表示引用的資源類型是 id。button_start_in_process 是個名稱。實際上在 id 以前還能夠加 包名,@+[package:]id/button_start_in_process 就是這樣的,若是不指定那就從當前的包裏查找。
再舉個例子,好比 android:text屬性值「@string/start_in_process」,在第五步的時候已經編譯過了,因此在這裏能夠直接獲取他們的資源 ID。
注意:一個資源項一旦創建以後,要得到它的 ID 是很容易的,由於它的 package id、tpye id和 entry id都是已知的。
四、壓平xml 這個詞很新鮮啊,第一次據說這麼個東西,就是對xml文件的內容進行扁平化處理,實際就是將xml文件格式轉換成二進制格式。過程以下圖。
一共分六步,(mmp好複雜) step 一、收集有資源 ID 的屬性的名稱字符串 這一步除了會收集 資源 ID的屬性的名稱字符串以外,還會將對應的資源ID收集到一個數組中,這裏收集到的屬性名稱字符串保存在一個字符串資源池中。他們與收集到的資源ID是一一對應的。也就是下圖這樣子滴。
step 二、收集其餘字符串 看到第一步我還在納悶,怎麼收集的字符串就只有屬性的呢?原來還有其餘的也放入字符串資源池裏,不過對於字符不會重複收集,畢竟是字典嘛。
step 三、寫入XML文件頭 最終編譯出來的XML二進制文件是一系列的chunk組成,每個chunk都有一個頭部,用來描述chunk的元信息,同時整個xml文件又能夠當作一個總的chunk。它有一個類型爲 ResXMLTree_header的頭部。
struct ResChunk_header
{
uint16_t type;
uint16_t headerSize;
uint32_t size;
};
struct ResXMLTree_header
{
struct ResChunk_header header;
};
複製代碼
--type:等於RES_XML_TYPE,描述這是一個Xml文件頭部。
--headerSize:等於sizeof(ResXMLTree_header),表示頭部的大小。
--size:等於整個二進制Xml文件的大小,包括頭部headerSize的大小。
step 四、寫入字符串資源池 原來定義在xml文件中的字符串已經在 一、2步收集完畢,所以,咱們能夠將它們寫入最終收集到二進制格式的xml文件中。寫入的順序必須嚴格按照在字符串資源池中的寫入順序。例如,對於main.xml來講,依次寫入的字符串爲「orientation」、「layout_width」、「layout_height」、「gravity」、「id」、"text"、"android"、「schemas.android.com/apk/res/and… step 1 收集到的資源 ID 數組也要寫入二進制格式的xml中,保持這個資源ID 和字符串資源池對應字符串的對應關係。 寫入的字符串池chunk一樣也是具備一個頭部的,這個頭部的類型爲ResStringPool_header,它定義在文件frameworks/base/include/utils/ResourceTypes.h中,以下所示:
struct ResStringPool_header
{
struct ResChunk_header header;
// Number of strings in this pool (number of uint32_t indices that follow
// in the data).
uint32_t stringCount;
// Number of style span arrays in the pool (number of uint32_t indices
// follow the string indices).
uint32_t styleCount;
// Flags.
enum {
// If set, the string index is sorted by the string values (based
// on strcmp16()).
SORTED_FLAG = 1<<0,
// String pool is encoded in UTF-8
UTF8_FLAG = 1<<8
};
uint32_t flags;
// Index from header of the string data.
uint32_t stringsStart;
// Index from header of the style data.
uint32_t stylesStart;
};
複製代碼
--type:等於RES_STRING_POOL_TYPE,描述這是一個字符串資源池。
--headerSize:等於sizeof(ResStringPool_header),表示頭部的大小。
--size:整個字符串chunk的大小,包括頭部headerSize的大小。
ResStringPool_header的其他成員變量的值以下所示:
--stringCount:等於字符串的數量。
--styleCount:等於字符串的樣式的數量。
--flags:等於0、SORTED_FLAG、UTF8_FLAG或者它們的組合值,用來描述字符串資源串的屬性,例如,SORTED_FLAG位等於1表示字符串是通過排序的,而UTF8_FLAG位等於1表示字符串是使用UTF8編碼的,不然就是UTF16編碼的。
--stringsStart:等於字符串內容塊相對於其頭部的距離。
--stylesStart:等於字符串樣式塊相對於其頭部的距離。
step 六、寫入資源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 六、壓平xml 壓平xml就是將各個xml元素中的字符串都替換掉。要麼被替換成字符串資源池的一個索引,要麼是被替換成一個具備類型的其餘值。咱們以main.xml 爲例。
首先壓平的是一個表示命名空間的xml node。這個Xml Node用兩個ResXMLTree_node和兩個ResXMLTree_namespaceExt來表示,如圖所示:
反正就是嗶哩嗶哩一大堆的約定協議參數,我就很少說了,感興趣你們就看老羅的文章研究下。反正就是xml各類稀裏糊塗的定規矩,解析什麼的。這些生成的資源符號爲後面生成R.java 作準備。全部的資源項按照類型保存在 ResourceTable對象中,所以 AAPT須要遍歷每個package中的每一個tpye,取出每個 entry。根據這些entry在 type 中的順序計算他們資源ID。那麼就能夠生成一個資源符號了。這個資源符號由名稱和資源ID組成。
對於strings.xml文件中名稱爲「start_in_process」的Entry來講,它是一個類型爲string的資源項,假設它出現的次序爲第3,那麼它的資源符號就等於R.string.start_in_process,對應的資源ID就爲0x7f050002,其中,高字節0x7f表示Package ID,次高字節0x05表示string的Type ID,而低兩字節0x02就表示「start_in_process」是第三個出現的字符串。
通過上面八個步驟,終於得到了資源列表,有了這個 aapt 就能夠按照下面的流程生成 資源索引表 resources.arsc。
感受又是不少步驟,真實心累的一批哈,看老羅的文章真的是想睡啊~~上面咱們壓平了xml,基本已經完成了一半的任務了,剩下通常就是生成這個resources.arsc 文件,估計又是一大堆的規則。知道大概意思就好了,到了須要的時候再仔細研究就行了。
step 一、收集類型字符串
一共有四種類型分別是 drawable 、layout、string 和 id。對應的類型字符串也是drawable 、layout、string 和 id。注意這些字符串是按照報名 package 來收集的。有幾個報名就有幾組對應的類型字符串。
step 二、收集資源項 名稱 字符串 好比上面的例子中有 12 個資源項,「icon」、「icon」、「icon」、「main」、「sub」、「app_name」、「sub_activity」、「start_in_process」、「start_in_new_process」、「finish」、「button_start_in_process」和「button_start_in_new_process」 對應的名稱字符串也是它們。對的這個也按照 package 來分組。
step 三、收集資源項 值 字符串 上一步是 名稱 這一回是 值。一共有12個資源項,可是隻有10項是具備值字符串的,它們分別是「res/drawable-ldpi/icon.png」、「res/drawable-mdpi/icon.png」、「res/drawable-hdpi/icon.png」、「res/layout/main.xml」、「res/layout/sub.xml」、「Activity」、「Sub Activity」、「Start sub-activity in process」、「Start sub-activity in new process」和「Finish activity」。須要注意的是這些字符串不是按照包 package 來區分的,會被統一收集起來。
step 四、生成package數據塊 參與編譯的每個 package 的資源項 元信息 都寫在一個獨立的數據上,這個數據塊使用和一個類型爲 ResTable_package 的頭部來描述。最後是下圖這樣的形式來的。
說一個比較不注意的東西點,可是感受挺重要的點吧,在Android資源中,有一種資源類型成爲public,他們通常是定義在 res/values/public.xml 。好比下面這樣的,這個public.xml是用來告訴aapt,將類型爲string的資源string3的ID 固定爲0x7f040001,爲何要固定呢?當咱們本身自定義的資源導出來給第三方程序使用的時候,爲了保證之後修改這些導出資源時,仍然能保證第三方應用程序的兼容性,就須要給這些導出資源一個固定的資源ID。舉個栗子,否則很差理解。那就對比一下有什麼區別吧,咱們首先創建一個public.xml 裏面放一個string3.而後新建兩個String,一個string3,一個string1。咱們看一下public和沒有public的區別吧。res/values/public.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<public type="string" name="string3" id="0x7f040001" />
</resources>
複製代碼
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="string1">String 1</string>
<string name="string3">String 3</string>
</resources>
複製代碼
假設 資源ID是下圖這樣的,那麼第三方引用string3的資源ID永遠是0x7f040001。
public final class R {
// ...
public static final class string {
public static final int string1=0x7f040000;
public static final int string3=0x7f040001;
}
}
複製代碼
當有一天,咱們增長一個string,按照咱們以前所說的他們是按順序來收集以及分配資源ID的。因此會有所改變。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="string1">String 1</string>
<string name="string2">String 2</string>
<string name="string3">String 3</string>
</resources>
複製代碼
假設string3 沒有引入public.xml 中,咱們應該猜到是下圖這樣的
public final class R {
// ...
public static final class string {
public static final int string1=0x7f040000;
public static final int string2=0x7f040001;
public static final int string3=0x7f040002; // New ID! Was 0x7f040001
}
}
複製代碼
可是咱們放入 public中了,因此它的資源ID是固定的,就是下圖這樣的。應該挺好理解的。
public final class R {
// ...
public static final class string {
public static final int string1=0x7f040000;
public static final int string2=0x7f040002;
public static final int string3=0x7f040001; // Resource ID from public.xml
}
}
複製代碼
須要注意的是咱們本身開發應用程序是不須要pubic.xml 這麼搞一下的,基本都是內部使用。不會導出來給第三方app使用,只在內部使用的資源,無論它的資源ID怎麼變化,咱們都能經過R.java 文件定義的常量來引用他們。只有系統定義的資源包纔會使用到public.xml 文件。由於它定義的資源須要提供給第三方應用程序使用的。
其實有一個更常見的情景,咱們在反編譯的時候,就會看到有這個public.xml 發現R.java的id好像都跑到public.xml裏面去了,這是爲何呢?由於咱們反編譯以後再從新打包,編譯,對資源ID會從新編排這是一個隨機的過程,可是咱們的代碼裏面仍是以前的資源id,那麼就亂套了,因此生成一個public.xml保持這些資源ID的固定,感受又學到知識了,啊哈哈,之前沒怎麼關心過這個東西,不知道這個還有這一層含義。
繼續咱們的 生成package數據塊
3.寫入資源名稱字符串資源池 咱們已經把資源項名稱字符串收集了。所以能夠將他們直接寫到類型字符串資源池後面的那個數據塊中。 4.寫入類型規範數據塊 每個類型都對應了一個類型規範數據塊 5.寫入類型資源項數據塊
step 五、寫入資源索引表頭部 step 六、寫入資源項的值字符串資源池 在前三個步驟中已經收集了這些,咱們按照相關規則寫入就能夠了。 step 七、寫入package數據塊 在第四步的時候咱們已經收集到了它的信息,一樣按規則寫入就能夠了。
10、編譯AndroidManifest文件 通過前面九個步驟,終於把應用程序的全部資源項都編譯完成了。這個時候就開始講應用程序的配置文件 AndroidManifest.xml也編譯成二進制的xml文件。和以前的道理是同樣的。固然aapt也會驗證這個文件的完整性和正確性什麼的。
11、生成R.java 在第八步的時候,咱們已經收集到這些資源ID,這裏只是寫入到R.java 文件中就行了。
public final class R {
......
public static final class layout {
public static final int main=0x7f030000;
public static final int sub=0x7f030001;
}
......
}
複製代碼
12、打包APK文件 全部資源文件都編譯以及生成完畢以後就能夠打包到apk文件中去了。包括如下文件: 一、assert目錄 二、res目錄,可是不包括 res/values 目錄,這是由於 res/values 目錄下的文件通過編譯後,直接寫入到了資源項索引表去了。 三、資源項索引文件 resources.arsc 固然除了這些資源文件,應用的配置文件 AndroidManifest.xml 以及應用代碼文件 class.dex,還有用來描述程序的簽名信息的文件,也會被一併打包到 APK中去,這個APK文件能夠直接安裝了。
終於看完了,其中最重要的四個要點:
一、xml 資源文件從文本格式編譯成二進制格式的過程
二、xml 資源文件的二進制格式
三、項目資源索引文件 resources.arsc 的生成過程
四、項目資源索引文件 resources.arsc的二進制格式
好了,這麼長的文章,我感受我是記不住的,那麼就提煉一下吧,簡化流程。
閉上眼睛不去看上面這張圖,腦子還記的什麼呢?忽然想起了倚天屠龍記張三丰傳授張無忌的橋段。啊~全忘記了~~呸,我們這個可不行。既然忘記了,那咱們就想一想咱們要作什麼?貌似有兩個點
一、xml 二進制話 更節省空間更快速 二、resources.arsc 和 R.java 也就是各類收集資源ID
咱們知道這兩點就夠了,知道了要作什麼,而後發散:如何作。
xml二進制話:其實就是從resources.arsc這個小字典裏找到字符串相應的序列號,用0x0011來表示。這個相對來講就簡單了一點。
難點是如何生成resources.arsc,那麼咱們就想啊,既然要生成這個資源集合文件,那麼咱們用包名這個惟一標識來建立 資源表吧,那麼如何找到包名呢?解析 AndroidManifest.xml,找到了包名,根據包名,建立這個 資源表。
so:總結一下就是:一、建立資源表
有了資源表這個文件,那麼該怎麼往裏邊填寫呢?那麼咱們就要知道咱們收集的是啥?咱們收集的資源文件,能夠分爲 drawable、layout、color、menu、raw、string和xml等,對了還有系統的資源文件,怎麼區分系統和咱們應用呢?根據包名package啊,而後根據類型type,根據Entry就各類資源ID了。這道怎麼區分了,咱們就該收集資源了。須要說的是咱們不能直接往表裏提交啊,萬一錯了咋辦,就須要想用一個臨時的容器,而後再往資源表裏複製過去。
首先既然是收集資源,就要有必定的順序和規矩,不能東一棒槌,西一錐子的,那麼咱們就以資源的類型來收集。好比 layout 、values、drawable。咱們先用drawable來舉例子,好比 xh,xxh,xxxh 都有一個icon.png。咱們就想啊,怎麼來區分呢?都是 icon 啊,那麼咱們就用組來分吧,好比drawable的容器是set,而後下一個粒度就是組group了,一組icon.png,而後裏面的icon.png就是一個單個的文件file,那麼怎麼怎麼知道它的一些特徵呢?這個文件就須要一個它的描述文件就是entry,這樣就知道這個drawable在xh等其餘維度信息。固然別的類型也是如此。
收集到了臨時容器,那麼就須要往資源表放,須要注意的是values就先別放了,放其餘類型的資源文件,values資源須要編譯後再放。
放的時候和臨時容器稍有差別,不過大同小異,每個資源也是用 entry 表示,根據 package,tpye,configList 來分類。
至於values資源爲何那麼特殊呢?它分爲string 和 bag資源,這些bag資源會自定義一些本身的專用值,因此須要編譯獲得最終的結果才行。對了這個bag資源分爲 attr類型資源和id資源類型,怎麼區分呢,attr有個內部元數據「^type」。
好了收集了資源,由於前面values是處於編譯階段,因此也就連帶着開始編譯xml資源文件。
解析xml,把裏面bag或者其餘Values的資源轉換成真實的值。就是根據資源的名字,類型,所在包找到對應的資源,根據資源找到它的元數據,就是須要的值了。
而後就開始壓扁xml,也就是二進制他,那麼就須要咱們首先先收集這個xml裏面的資源的屬性名稱字符串,而後收集其餘String字符串。把收集到的這些字符串放到字符串資源池裏面。而後根據資源池裏做爲字典,按照資源序號來從新生成這個xml,也就是二進制xml。就是由於values資源中的bag資源是在values編譯的時候就被賦予資源ID的,可是其餘資源目前是沒有資源ID,只有對應路徑的資源名稱和資源數據。因此咱們須要給資源賦值ID。也就是生成資源符號。就是資源名稱+資源ID,爲R.java作準備。
固然有了以前的全部東西,就能夠根據規則整成 resources.arsc。各類添添補補什麼的。最後生成R.java 文件。最終把生成的resources.arsc、AndroidManifest.xml、res目錄(沒有values 早被打包到resources.arsc了)、assert還有class.dex 和 簽名信息文件 一同打進apk包裏。
好了完工。連粘貼帶複製,加上本身手敲終於稍微理清楚了這個關係,不過還須要回頭再看一下這個過程纔好,明確目的,一步一步根據思路去實現,不糾結具體實現,理清楚核心思想就能夠了。
copy from 如下博客: