Android 編譯打包的那些疑問

Mr.S發表於2019-05-04

我們平時都是用 AS 進行打包,這就造成了很多盲點,我們就來看看究竟是咋回事,提前宣告這篇文章講的不全,只講一些疑惑盲點,需要全面學習的,看老羅的吧,詳細的令人髮指。

我們從結果入手,看看打包完畢的apk裡面是啥模樣,把.apk 修改成 .zip 解壓縮。按圖索驥哈哈。

Android 編譯打包的那些疑問
一共五個檔案,第一個 AndroidManifest.xml 我們很熟悉了。開啟看一下。

Android 編譯打包的那些疑問
我的個乖乖,咋回事。 然後在開啟 res 檔案,很多維度的 drawable 資料夾啊。

Android 編譯打包的那些疑問
隨便開啟一個吧,看看裡面的 xml 檔案,nm,咋也都是二進位制資料了呢?

Android 編譯打包的那些疑問

好吧,這些貌似是和 資源編譯打包有關係哈,印象裡好像是這麼回事,趕緊百度,谷歌。看到了如下的神圖貌似和我們遇到的類似啊。

Android 編譯打包的那些疑問

Android 編譯打包的那些疑問
aapt 這傢伙,把我們的資源搞了一下。他想幹什麼呢?帶著疑問我們去學習。

1、為啥要把文字格式的xml 轉換成 二進位制格式的

為啥呢?如果猜測的話,應該不是閒的沒事搞得,我們知道安卓面臨的問題一直是空間和時間的問題,這麼搞,無非就是省空間,提升速度。

2、怎麼做呢?

把所有 xml 元素的標籤,屬性,內容等字串統一放到一個 字串資源池裡面去,然後在用到這個字串的地方用索引來代替,這是偷懶的行家啊,也是計算機裡的區域性思想的發揚光大。這樣就可以解決空間佔用的大小了。 那麼怎麼就速度快了呢?因為這裡的字串用的是索引,所以就不必每次都解析字串了,這還不快嗎?重複利用多開心啊。

ok,這個 xml 二進位制的問題也就解決完畢。但是一個問題的結束往往伴隨著另一個問題的開始。那個字串資源池在哪裡呢?

這就引出了我們的上面 五大部分的 Resources.arsc。先容我百度,谷歌下哈。 blog.csdn.net/beyond702/a… 很顯然我不是要解析 Resources.arsc,反正我知道了,這個字串資源池就在 Resources.arsc 中,名字叫 Global String Pool,這樣就可以了。

好了回到 aapt 這個傢伙。

據網上總結他有以下幾個重要的工作。

1、 assert 和 res/raw 目錄下的所有資源原封不動打包到 apk 裡。 2、對 res/ 目錄下的檔案進行編譯處理 比如 xml 編譯成二進位制檔案,png 等圖片也會進行優化處理。 3、除了 assert 資源之外的所有資源都會賦予一個資源ID常量,並且聲稱一個資源索引表 Resources.arsc。 4、把 AndroidManifest.xml 也進行二進位制處理。 5、把上面四步驟中聲稱的結果儲存到一個*.ap_ 檔案,把各個資源 ID 常量定義在 R.java 檔案中。

這麼一來解答了不少疑惑,但是 *.ap_ 是個啥玩意呢?下圖是網上的,說實話,我反正沒看到,我實驗了下沒有 .ap 檔案。咋辦呢?繼續搜吧,可能是文章有點老了。

Android 編譯打包的那些疑問

Android 編譯打包的那些疑問

還是看官方文件吧,我擦,appt2 了啊,好吧,看英文文件使我快樂(?)。文件上說 appt 已經棄用,開始使用 appt2 了,雖然老專案也在用,但是你懂得。

developer.android.com/studio/comm…

Android 編譯打包的那些疑問
終於找到了一篇文章,講述 appt2 編譯的。

www.colabug.com/1787983.htm…

原來是 appt2 將原來的資源編譯打包過程拆分成了兩部分,編譯和連結,提高了資源的編譯效能。當只有一個資源發上改變的時候,只需要重新編譯改變的檔案就行了。其他不變的進行連結就行了。之前 appt 是將所有的資源進行merge ,merge 完畢重新對所有資源進行編譯,產生一個 資源 ap_ 檔案。這個也就是一個壓縮包。

Android 編譯打包的那些疑問

具體細節大家有興趣可以搞一搞。我心裡還是有點不明白這些個過程,所有又找了一篇文章來看看。

www.jianshu.com/p/d487f0aa9…

這個的圖真是詳細的很啊。

Android 編譯打包的那些疑問

我們要編譯的應用程式的資源結構目錄。圖文結合一下,不然不知道說的什麼。

Android 編譯打包的那些疑問

第一步:解析AndroidManifest.xml

獲得包名,根據包名建立資源表 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。

舉個例子說明下吧(其實我就是抄的)

1、型別是 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。

2、型別是 layout 的的ResourceTypeSet 有兩個AaptGroup,分別是 main.xml 和 sub.xml。都只包含了一個 AaptFile ,分別是res/layout/main.xml和res/layout/sub.xml。同樣分別對應一個AaptGroupEntry。這兩個AaptGroupEntry描述的資源配置資訊都是屬於default的。

3、型別為 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物件來描述,其中:

1、型別是 drawable 的Type,包含一個 ConfigList。名稱為 icon.png。包含了三個 Entry,分別是res/drawable-ldpi/icon.png、res/drawable-mdpi/icon.png和res/drawable-hdpi/icon.png。每一個 Entry 對應一個 ConfigDescription,用來描述不同的資源配置資訊。

Android 編譯打包的那些疑問

Android 編譯打包的那些疑問
Android 編譯打包的那些疑問

第五步:編譯 values 類資源

型別為 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。

Android 編譯打包的那些疑問

六、給Bag資源分配ID

型別 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>
複製程式碼

Android 編譯打包的那些疑問
看完了 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資原始檔

前六步都是為了編譯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>
複製程式碼

Android 編譯打包的那些疑問
1、解析xml 檔案 這個就不多說了,最後得到根節點的 XMLNode。 2、賦予屬性名稱資源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。直到都獲得為止。 3、解析屬性值。 上一步只是對屬性的解析,這一步是對屬性值的解析。比如 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都是已知的。

4、壓平xml 這個詞很新鮮啊,第一次聽說這麼個東西,就是對xml檔案的內容進行扁平化處理,實際就是將xml檔案格式轉換成二進位制格式。過程如下圖。

Android 編譯打包的那些疑問

一共分六步,(mmp好複雜) step 1、收集有資源 ID 的屬性的名稱字串 這一步除了會收集 資源 ID的屬性的名稱字串之外,還會將對應的資源ID收集到一個陣列中,這裡收集到的屬性名稱字串儲存在一個字串資源池中。他們與收集到的資源ID是一一對應的。也就是下圖這樣子滴。

Android 編譯打包的那些疑問

step 2、收集其他字串 看到第一步我還在納悶,怎麼收集的字串就只有屬性的呢?原來還有其他的也放入字串資源池裡,不過對於字元不會重複收集,畢竟是字典嘛。

Android 編譯打包的那些疑問

step 3、寫入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 4、寫入字串資源池 原來定義在xml檔案中的字串已經在 1、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 6、寫入資源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來表示,如圖所示:

Android 編譯打包的那些疑問
反正就是嗶哩嗶哩一大堆的約定協議引數,我就不多說了,感興趣大家就看老羅的文章研究下。反正就是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。

Android 編譯打包的那些疑問
感覺又是很多步驟,真實心累的一批哈,看老羅的文章真的是想睡啊~~
Android 編譯打包的那些疑問

上面我們壓平了xml,基本已經完成了一半的任務了,剩下一般就是生成這個resources.arsc 檔案,估計又是一大堆的規則。知道大概意思就行了,到了需要的時候再仔細研究就好了。

step 1、收集型別字串

一共有四種型別分別是 drawable 、layout、string 和 id。對應的型別字串也是drawable 、layout、string 和 id。注意這些字串是按照報名 package 來收集的。有幾個報名就有幾組對應的型別字串。

step 2、收集資源項 名稱 字串 比如上面的例子中有 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 3、收集資源項 值 字串 上一步是 名稱 這一回是 值。一共有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 4、生成package資料塊 參與編譯的每一個 package 的資源項 元資訊 都寫在一個獨立的資料上,這個資料塊使用和一個型別為 ResTable_package 的頭部來描述。最後是下圖這樣的形式來的。

Android 編譯打包的那些疑問
說一個比較不注意的東西點,但是感覺挺重要的點吧,在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資料塊

  1. 寫入Package資源項元資訊資料塊頭部
  2. 寫入型別字串資源池 在上面步驟中我們將每一個package 用到的型別字串手機起來了,因此直接把他寫到package資源項元資訊塊頭部後面的資料塊中去。

3.寫入資源名稱字串資源池 我們已經把資源項名稱字串收集了。因此可以將他們直接寫到型別字串資源池後面的那個資料塊中。 4.寫入型別規範資料塊 每一個型別都對應了一個型別規範資料塊 5.寫入型別資源項資料塊

step 5、寫入資源索引表頭部 step 6、寫入資源項的值字串資源池 在前三個步驟中已經收集了這些,我們按照相關規則寫入就可以了。 step 7、寫入package資料塊 在第四步的時候我們已經收集到了它的資訊,同樣按規則寫入就可以了。

十、編譯AndroidManifest檔案 經過前面九個步驟,終於把應用程式的所有資源項都編譯完成了。這個時候就開始講應用程式的配置檔案 AndroidManifest.xml也編譯成二進位制的xml檔案。和之前的道理是一樣的。當然aapt也會驗證這個檔案的完整性和正確性什麼的。

十一、生成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;
    }
 
    ......
}
複製程式碼

十二、打包APK檔案 所有資原始檔都編譯以及生成完畢之後就可以打包到apk檔案中去了。包括以下檔案: 1、assert目錄 2、res目錄,但是不包括 res/values 目錄,這是因為 res/values 目錄下的檔案經過編譯後,直接寫入到了資源項索引表去了。 3、資源項索引檔案 resources.arsc 當然除了這些資原始檔,應用的配置檔案 AndroidManifest.xml 以及應用程式碼檔案 class.dex,還有用來描述程式的簽名資訊的檔案,也會被一併打包到 APK中去,這個APK檔案可以直接安裝了。

終於看完了,其中最重要的四個要點:

1、xml 資原始檔從文字格式編譯成二進位制格式的過程

2、xml 資原始檔的二進位制格式

3、專案資源索引檔案 resources.arsc 的生成過程

4、專案資源索引檔案 resources.arsc的二進位制格式

www.jianshu.com/p/d487f0aa9… blog.csdn.net/luoshengyan… juejin.im/entry/58b78… www.10tiao.com/html/597/20… www.jianshu.com/p/3cc131db2…

相關文章