## **概述**
Android應用程序主要由代碼和資源組成。資源主要就是指那些與UI相關的東西,例如UI布局、字符串和圖片等。代碼和資源分開可以使得應用程序在運行時根據實際需要來組織UI。這樣就可使得應用程序只需要編譯一次,就可以支持不同的UI布局。這種特性使得應用程序在運行時可以適應不同的屏幕大小和密度,以及不同的國家和語言等。資源在Android應用程序編譯的過程中,也會被編譯成二進制格式。這是為了壓縮資源存儲空間,以及加快運行時的資源解析速度。Android應用程序在運行的時候,資源管理器AssetManager和Resources會根據當前的機器設置,即屏幕大小、密度、方向,以及國家、地區語言的信息,查找正確的資源,并且進行解析,最后將它們渲染在UI上。
主要講Android應用程序資源的編譯、打包,以及它們在運行時的查找、解析過程。了解Android應用程序資源管理框架,有助于我們更好地開發出能夠適配多種機型的應用程序。
* Android資源框架概述
* Android資源編譯過程
* Android資源查找過程
## **Android資源框架概述**
**背景**
* Android設備屏幕大小和密度各不相同
* Android設備運行在不同國家、地區和語言上
**問題**
* 同一個應用程序需要支持不同的語言和UI布局
**方案**
* 代碼和資源分離
**流行的資源管理框架**
* Web開發:CSS文件
* MFC開發:RC文件
* WPF開發:XAML文件
* QT開發:QML文件
* COCOA開發:XIB文件
**Android設備的多樣性導致其資源組織和管理更復雜**
**資源分類**
* assets
* res
* animator
* anim
* color
* drawable
* layout
* menu
* raw
* values
* xml
**資源目錄組織**
* <resources_name>-<config_qualifier>

## **Android資源編譯過程**
**Android資源編譯框架**

**編譯過程主要是將XML文件從文本格式編譯為二進制格式**
* ?二進制格式的XML文件占用空間更小。這是由于所有XML元素的標簽、屬性名稱、屬性值和內容所涉及到的字符串都會被統一收集到一個字符串資源池中去,并且會去重。有了這個字符串資源池,原來使用字符串的地方就會被替換成一個索引到字符串資源池的整數值,從而可以減少文件的大小。
* ?二進制格式的XML文件解析速度更快。這是由于二進制格式的XML元素里面不再包含有字符串值,因此就避免了進行字符串解析,從而提高速度。
**為了支持運行時快速定位最匹配資源,編譯過程還會有為資源生成ID,以及生成資源索引表**
* 賦予每一個非assets資源一個ID值,這些ID值以常量的形式定義在一個R.java文件中
* 生成一個resources.arsc文件,用來描述那些具有ID值的資源的配置信息,它的內容就相當于是一個資源索引表
* 資源ID是一個4字節的無符號整數,其中,最高字節表示Package ID,次高字節表示Type ID,最低兩字節表示Entry ID
* Package ID相當于是一個命名空間,限定資源的來源。Android系統當前定義了兩個資源命令空間,其中一個系統資源命令空間,它的Package ID等于0x01,另外一個是應用程序資源命令空間,它的Package ID等于0x7f。所有位于[0x01, 0x7f]之間的Package ID都是合法的,而在這個范圍之外的都是非法的Package ID
* Type ID是指資源的類型ID。資源的類型有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干種,每一種都會被賦予一個ID
* Entry ID是指每一個資源在其所屬的資源類型中所出現的次序。注意,不同類型的資源的Entry ID有可能是相同的,但是由于它們的類型不同,我們仍然可以通過其資源ID來區別開來
**例子**

### **Step 1: 解析AndroidManifest.xml**
* 為了獲得要編譯資源的應用程序的包名稱。
* 在AndroidManifest.xml文件中,manifest標簽的package屬性的值描述的就是應用程序的包名稱。
* 有了這個包名稱之后,就可以創建一個資源表(Resource Table)
### **Step 2:添加被引用資源包**
* android:orientation=“vertical”中的vertical實際上是由系統定義的一個資源。
* 在AOSP中,系統資源經過編譯后,位于out/target/common/obj/APPS/framework-res_intermediates/package-export.apk文件中。
* 因此,在AOSP中編譯的應用程序資源,都會引用到系統資源包package-export.apk
### **Step 3: ?收集資源文件**
* 在我們的例子中,包含三種類型的資源drawable、layout和values
* 名稱相同的資源歸為一組。例如,名稱為icon.png的資源項res/drawable-ldpi/icon.png、res/drawable-mdpi/icon.png和res/drawable-hdpi/icon.png歸為一組。它們通過屏幕密度(ldpi、mdpi和hdpi)來區分
### **Step 4:將收集到的非values資源增加到資源表**

### **Step 5: 編譯values類資源**
* strings.xml文件的內容

* strings.xml文件編譯后得到的資源項

### **Step 6: 給自定義資源分配資源ID**
* 假設在res/values目錄下有一個attrs.xml文件

* attrs.xml文件被解析后

### **Step 7: 編譯Xml資源文件**
* 以res/layout/main.xml為例

#### **Step 7.1: 解析xml文件**
* 解析Xml文件是為了可以在內存中用一系列樹形結構的XMLNode來表示
#### **Step 7.2:賦予屬性名稱資源ID**
* 對于main.xml的根節點LinearLayout來說,就是要將它的屬性名稱android:orientation、android:layout_width、android:layout_height和android:gravity轉換為資源ID
### **Step 7.3: 解析屬性值**
* 例如,對于main.xml的屬性android:orientation來說,它的合法取值為horizontal或者vertical,這里需要進行驗證,并且將它們轉換為ID值
* 有些屬性值是屬于引用類型的,例如main.xml文件的兩個Button節點的android:id屬性值”@+id/button_start_in_process”和”@+id/button_start_in_new_process” ,將會生成新的資源項

> **注意**,一個資源項一旦創建之后,要獲得它的資源ID是很容易的,因為它的Package ID、Type ID和Entry ID都是已知的。
### **Step 7.4:將xml文件從文本格式轉換為二進制格式**

#### **Step 7.4.1: 收集有資源ID的屬性的名稱字符串**
* 將收集到的字符串及其資源ID分別保存一個字符串池以及資源數組中
* 對于main.xml文件來說,具有資源ID的Xml元素屬性的名稱字符串有”orientation”、”layout_width”、”layout_height”、”gravity”、”id”和”text”,假設它們的資源ID分別為0x010100c4、0x010100f4、0x010100f5、0x010100af、0x010100d0和0x0101014f:

#### **Step 7.4.2: 收集其它字符串**
* 這一步收集的字符串是不具有資源ID的
* 對于main.xml文件來說,這一步收集到的字符串如下所示:

#### **Step 7.4.3:寫入Xml文件頭**


**對于ResXMLTree_header頭部來說,內嵌在它里面的ResChunk_header的成員變量的值如下所示**:
* **type**:等于RES_XML_TYPE,描述這是一個Xml文件頭部。
* **headerSize**:等于sizeof(ResXMLTree_header),表示頭部的大小。
* **siz**e:等于整個二進制Xml文件的大小,包括頭部headerSize的大小。
#### **Step 7.4.4:寫入字符串資源池**
* 對于main.xml來說,依次寫入的字符串為”orientation”、”layout_width”、”layout_height”、”gravity”、”id”、"text"、"android"、”http://schemas.android.com/apk/res/android” 、”LinearLayout”和”Button”
* 寫入的字符串池同樣有一個頭部

**內嵌在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**:等于字符串樣式塊相對于其頭部的距離。
#### **Step 7.4.4:字符串池的結構**

#### **Step 7.4.4:樣式字符串**
* 假設有一個字符串” `<b>man</b><i>go</i>`”,實際上包含三個字符串”mango”、”b”和”I”,其中”mango”來有兩個sytle,第一個style表示第1到第3個字符是粗體的,第二個style表示第4到第5個字符是斜體的
* 樣式字符串由ResStringPool_span和ResStringPool_ref兩個結構描述
以字符串“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 7.4.5:寫入資源ID**
* 資源ID數據塊位于字符串池后面,它的頭部使用ResChunk_header來描述
* 以main.xml為例,字符串資源池的第一個字符串為”orientation”,而在資源ID數據塊記錄的第一個數據為0x010100c4,那么就表示屬性名稱字符串”orientation”對應的資源ID為0x010100c4
資源ID數據塊ResChunk_header各個成員變量的含義
**type**:等于RES_XML_RESOURCE_MAP_TYPE,表示這是一個從字符串資源池到資源ID的映射頭部。
**headerSize**:等于sizeof(ResChunk_header),表示頭部大小。
**size**:等于headerSize的大小再加上sizeof(uint32_t) * count,其中,count為收集到的資源ID的個數。
#### **Step 7.4.6:壓平Xml文件**
* 壓平Xml文件其實就是指將里面的各個Xml元素中的字符串都替換掉。這些字符串要么是被替換成到字符串資源池的一個索引,要么是替換成一個具有類型的其它值。
**Step 7.4.6.1:首先被壓平的是一個表示命名空間的Xml Node**
* 這個Xml Node用兩個ResXMLTree_node和兩個ResXMLTree_namespaceExt來表示:

**Step 7.4.6.1: ResXMLTree_node和ResXMLTree_namespaceExt的定義**

對于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” 在字符串資源池中的索引。
**Step 7.4.6.2:接下來被壓平的是標簽為LinearLayout的Xml Node**
* 這個Xml Node由兩個ResXMLTree_node、一個ResXMLTree_attrExt、一個ResXMLTree_endElementExt和四個ResXMLTree_attribute來表示

第一個ResXMLTree_node表示一個Node的開始,最后面的ResXMLTree_node及其后面的ResXMLTree_endElementExt表示一個Node的結束
**Step 7.4.6.2: ResXMLTree_attrExt的定義**

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。
**Step 7.4.6.2: ResXMLTree_attrExt的定義**

LinearLayout元素有四個屬性,每一個屬性都對應一個ResXMLTree_attribute,接下來我們就以名稱為“orientation”的屬性為例,來說明它的各個成員變量的取值,如下所示:
* **ns**:等于屬性orientation的命令空間在字符池資源池的索引,沒有指定則等于-1。
* **name**:等于屬性名稱字符串“orientation”在字符池資源池的索引。
* **rawValue**:等于屬性orientation的原始值“vertical”在字符池資源池的索引,這是可選的,如果不用保留,它的值就等于-1。
**Step 7.4.6.2: Res_value的定義**

**Step 7.4.6.2: ResXMLTree_endElementExt的定義**

**ns**:等于LinearLayout元素的命令空間在字符池資源池的索引,沒有指定則等于-1。
**name**:等于字符串“LinearLayout”在字符池資源池的索引。
**Step 7.4.6.3**:對于一個Xml文件來說,它除了有命名空間和普通標簽類型的Node之外,還有一些稱為CDATA類型的Node
* 假設一個Xml文件,它的一個Item標簽的內容如下所示:

* 字符串“This is a normal text”就稱為一個CDATA,它在二進制Xml文件中用一個ResXMLTree_node和一個ResXMLTree_cdataExt來描述

**Step 7.4.6.3**: ResXMLTree_cdataExt的定義

內嵌在上面的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文件出現的行號。
下面的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,這個值是沒有意義的。
### **Step 8:生成資源符號**
* 這里生成資源符號為后面生成R.java文件做好準備的
* 目前所有收集到的資源項都按照類型來保存在一個資源表中
* 只要依次遍歷資源表中的每一個Package的每一個Type的每一個Entry,就可以生成一系列的資源符號及其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”是第三個出現的字符串
### **Step 9:生成資源索引表**
經過前面的八個操作,所獲得的資源列表如下所示:

### **Step 9: 資源索引表生成過程**

#### **Step 9.1:收集類型字符串**
* 在我們的例子中,一共有4種類型的資源,分別是drawable、layout、string和id,于是對應的類型字符串就為“drawable”、“layout”、“string”和“id”
* 注意,這些字符串是按Package來收集的,也就是說,當前被編譯的應用程序資源有幾個Package,就有幾組對應的類型字符串,每一個組類型字符串都保存在其所屬的Package中
#### **Step 9.2:收集資源項名稱字符串**
* 在我們的例子中,收集到的資源項名稱字符串就為”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中。
#### **Step 9.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來收集的,也就是說,當前所有參與編譯的Package的資源項值字符串都會被統一收集在一起。
#### **Step 9.4:生成Package數據塊**
* 參與編譯的每一個Package的資源項元信息都寫在一塊獨立的數據上,這個數據塊使用一個類型為ResTable_package的頭部來描述

**Step 9.4.1**:寫入Package資源項元信息數據塊頭部,即一個ResTable_package結構體

嵌入在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資源項名稱字符串在資源項名稱字符串資源池中的索引,目前這個值設置為資源項名稱字符串資源池的大小。
**Step 9.4.1: ResTable_package結構體圖示**

**Step 9.4.1: public資源**
* 定義在res/values/public.xml文件,形式如下所示:

* 用來告訴Android資源編譯工具將類型為string的資源string3的ID固定為0x7f040001,以便第三方應用程序可以固定地通過0x7f040001來訪問字符串”string3”
**Step 9.4.2:寫入類型字符串資源池**
* 前面已經將每一個Package用到的類型字符串收集起來了,因此,這里就可以直接將它們寫入到Package資源項元信息數據塊頭部后面的那個數據塊去。
**Step 9.4.3:寫入資源項名稱字符串資源池**
* 前面已經將每一個Package用到的資源項名稱字符串收集起來了,這里就可以直接將它們寫入到類型字符串資源池后面的那個數據塊去。
**Step 9.4.4:寫入類型規范數據塊**
* 類型規范數據塊用來描述資源項的配置差異性。通過這個差異性描述,我們就可以知道每一個資源項的配置狀況。
* 知道了一個資源項的配置狀況之后,Android資源管理框架在檢測到設備的配置信息發生變化之后,就可以知道是否需要重新加載該資源項。
* 類型規范數據塊是按照類型來組織的,也就是說,每一種類型都對應有一個類型規范數據塊。
**Step 9.4.4:類型規范數據塊的頭部用一個ResTable_typeSpec來定義**

嵌入在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**:等于本類型的資源項個數,注意,這里是指名稱相同的資源項的個數。
**Step 9.4.4**: ResTable_typeSpec后面緊跟著的是一個大小為entryCount的uint32_t數組,
* 每一個數組元數,即每一個uint32_t,都是用來描述一個資源項的配置差異性的
* Android資源管理框架根據這個差異性信息,就可以知道當設備配置發生變化時,是否需要重新加載資源
**Step 9.4.4: 在我們的例子中,類型為drawable的規范數據塊內容:**

* 由此可知,類型為drawable的資源項icon在設備的屏幕密度發生變化之后,Android資源管理框架需要重新對它進行加載,以便獲得更合適的資源項
**Step 9.4.4: 在我們的例子中,類型為layout的規范數據塊內容:**

* 由此可知,類型為layout的資源項在設備配置變化之后,Android資源管理框架無需重新對它們進行加載,因為只有一種資源
**Step 9.4.4: 在我們的例子中,類型為string的規范數據塊內容**:

* 由此可知,類型為string的資源項在設備配置變化之后,Android資源管理框架無需重新對它們進行加載,因為只有一種資源
**Step 9.4.5:寫入類型資源項數據塊**
* 類型資源項數據塊用來描述資源項的具體信息, 這樣我們就可以知道每一個資源項名稱、值和配置等信息。類型資源項數據同樣是按照類型和配置來組織的,也就是說,一個具有N個配置的類型一共對應有N個類型資源項數據塊。
**Step 9.4.5:類型資源項數據塊的頭部用一個ResTable_type來定義**

嵌入在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的類圖。
**Step 9.4.5**: ResTable_type緊跟著的是一個大小為entryCount的uint32_t數組,每一個數組元數,即每一個uint32_t,都是用來描述一個資源項數據塊的偏移位置。緊跟在這個uint32_t數組后面的是一個大小為entryCount的ResTable_entry數組,每一個數組元素,即每一個ResTable_entry,都是用來描述一個資源項的具體信息。在我們的例子中,一共有4種不同類型的資源項,其中,類型為drawable的資源有1個資源項以及3種不同的配置,類型為layout的資源有2個資源項以及1種配置,類型為string的資源有5個資源項以及1種配置,類型為id的資源有2個資源項以及1種配置。這樣一共就對應有3 + 1 + 1 + 1個類型資源項數據塊。
**Step 9.4.5:類型為drawable和配置為ldpi的資源項數據塊**

**Step 9.4.5:類型為drawable和配置為mdpi的資源項數據塊**

**Step 9.4.5:類型為drawable和配置為hdpi的資源項數據塊**

**Step 9.4.5:類型為layout和配置為default的資源項數據塊**

**Step 9.4.5:類型為string和配置為default的資源項數據塊**

**Step 9.4.5:類型為id和配置為default的資源項數據塊**

**Step 9.4.5: 每一個資源項數據都是通過一個ResTable_entry來定義的**

ResTable_entry的各個成員變量的取值如下所示:
* **size**:等于sizeof(ResTable_entry),表示資源項頭部大小。
* **flags**:資源項標志位。如果是一個Bag資源項,那么FLAG_COMPLEX位就等于1,并且在ResTable_entry后面跟有一個ResTable_map數組,否則的話,在ResTable_entry后面跟的是一個Res_value。如果是一個可以被引用的資源項,那么FLAG_PUBLIC位就等于1。
* **key**:資源項名稱在資源項名稱字符串資源池的索引。
**Step 9.4.5: 普通資源項數據都是通過一個Res_value來定義的**

**Step 9.4.5: 自定義資源項數據都是通過一個ResTable_map_entry以及若干個ResTable_map來定義的**

**Step 9.4.5: 以前面的自定義資源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。
**Step 9.4.5: ResTable_map_entry的定義如下所示**:

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項的個數。
**Step 9.4.5: ResTable_map的定義如下所示:**

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。
#### **Step 9.5:寫入資源索引表頭部**
資源索引表頭部使用一個ResTable_header來表示

嵌入在ResTable_header內部的ResChunk_header的各個成員變量的取值如下所示:
* **type**:等于RES_TABLE_TYPE,表示這是一個資源索引表頭部。
* **headerSize**:等于sizeof(ResTable_header),表示頭部的大小。
* **size**:等于整個resources.arsc文件的大小。
ResTable_header的其它成員變量的取值如下所示:
* packageCount:等于被編譯的資源包的個數。
#### **Step 9.6:寫入資源項的值字符串資源池**
* 前面已經將所有的資源項的值字符串都收集起來了,因此,這里直接它們寫入到資源索引表去就可以了。
* 注意,這個字符串資源池包含了在所有的資源包里面所定義的資源項的值字符串,并且是緊跟在資源索引表頭部的后面。
嵌入在ResTable_header內部的ResChunk_header的各個成員變量的取值如下所示:
* **type**:等于RES_TABLE_TYPE,表示這是一個資源索引表頭部。
* **headerSize**:等于sizeof(ResTable_header),表示頭部的大小。
* **size**:等于整個resources.arsc文件的大小。
ResTable_header的其它成員變量的取值如下所示:
* **packageCount**:等于被編譯的資源包的個數。
#### **Step 9.7:寫入Package數據塊**
* 前面已經所有的Package數據塊都收集起來了,因此,這里直接將它們寫入到資源索引表去就可以了。
* 這些Package數據塊是依次寫入到資源索引表去的,并且是緊跟在資源項的值字符串資源池的后面。
### **Step 10:編譯AndroidManifest.xml文件**
* ?經過前面的操作,應用程序的所有資源項就編譯完成了,這時候就開始將應用程序的配置文件AndroidManifest.xml也編譯成二進制格式的Xml文件。
* 之所以要在應用程序的所有資源項都編譯完成之后,再編譯應用程序的配置文件,是因為后者可能會引用到前者。
### **Step 11:生成R.java文件**
* 前面已經將所有的資源項及其所對應的資源ID都收集起來了,因此,這里只要將直接將它們寫入到指定的R.java文件去就可以了。
* 例如,假設分配給類型為layout的資源項main和sub的ID為0x7f030000和0x7f030001,那么在R.java文件,就會分別有兩個以main和sub為名稱的常量,如下所示:

### **Step 12:打包資源文件到APK包**
* assets目錄
* res目錄,但是不包括values子目錄的文件,這些文件的資源項已經包含在resources.arsc文件中
* resources.arsc
* AndroidManifest.xml
### **Android資源查找過程**
#### **Android資源查找框架**

#### **Android資源查找框架相關實現類**

每一個App Package都有一個Resources對象和一個AssetManager對象,用來查找本Package的資源,每一個App Process又都包含有一個Resources對象和一個AssetManager對象用來查找系統資源
#### **Android資源查找框架的初始化**
**設置資源路徑**
* AssetManager::addAssetPath
* /system/framework/framework-res.apk
* /vendor/overlay/framework/framework-res.apk(optional)
* Self Apk File
**設置配置信息**
* AssetManager::setConfiguration
對于System Resources來說,資源路徑只包含/system/framework/framework-res.apk和/vendor/overlay/framework/framework-res.apk(optional)
#### **根據資源ID找到資源Value**
* Step 1: 根據資源ID的Package ID在resources.arsc中找到對應的Package數據塊(ResTable_package)
* Step 2: 根據資源ID的Type ID在Package數據塊找到對應的類型規范數據塊(ResTable_typeSpec)和類型資源項數據塊(ResTable_type)
* Step 3: 根據資源ID的Entry ID在對應的類型資源項數據塊中找到與當前設備配置最匹配的資源項
* Step 4: 返回最匹配的資源項值及其配置差異性
* 找到的資源Value是一個文件路徑,則通過AssetManager打開該文件,并且對它進行解析以及使用,否則直接使用
ResTable_typeSpec和ResTable_type均有一個id域,該id域描述的就是Type ID,因此根據資源ID的Type ID在Package數據塊找到對應的類型規范數據塊和類型資源項數據塊
每一個類型資源項數據塊都包含有一個資源項數組,以Entry ID為索引,即可在資源項數組的資源項
對于一個包含多種配置的資源項來說,它就會對應多個ResTable_type,因此需要根據當前設備配置最匹配的資源項找到最匹配的資源項
同時返回資源項的類型規范數據塊(描述配置差異性)是為了讓調用者知道它正在查找的資源有哪些配置,這樣當設備配置發生變化時,就可以知道有沒有必要更新資源項的值,即重新查找最匹配的資源項
#### **資源匹配算法**

#### **資源匹配算法示例**
* 資源配置清單

* 設備配置信息

* Step 1: 消除與設備配置沖突的drawable目錄,即drawable-fr-rCA目錄,因為設備設置的語言是en-GB:

* Step 2:從MMC開始,選擇一個資源組織維度來過渡從Step 1篩選后剩下來的目錄。
* Step 3:檢查Step 2選擇的維度是否有對應的資源目錄。如果沒有,就返回到Step 2繼續處理。如果有,那么就繼續往下執行Step 4。在我們示例中,要一直重復執行Step 2,直到檢查到language這個維度時。
* Step 4: 消除那些不包含有Step 2所選擇的資源維度的目錄。在我們的示例中,就是要消除那些不包含有en這個language的目錄:

* Step 5:繼續執行Step 2、Step 3和Step 4,直到找到一個最匹配的資源目錄為止,即剩下最后一個目錄為止。在我們的示例中,下一個要檢查的維度是screen orienation。由于設備的screen orienation為port。因此,所有不包含有port資源維度的目錄將被消除

- 前言
- Android組件設計思想
- Android源代碼開發和調試環境搭建
- Android源代碼下載和編譯
- Android源代碼情景分析法
- Android源代碼調試分析法
- 手把手教你為手機編譯ROM
- 在Ubuntu上下載、編譯和安裝Android最新源代碼
- 在Ubuntu上下載、編譯和安裝Android最新內核源代碼(Linux Kernel)
- 如何單獨編譯Android源代碼中的模塊
- 在Ubuntu上為Android系統編寫Linux內核驅動程序
- 在Ubuntu上為Android系統內置C可執行程序測試Linux內核驅動程序
- 在Ubuntu上為Android增加硬件抽象層(HAL)模塊訪問Linux內核驅動程序
- 在Ubuntu為Android硬件抽象層(HAL)模塊編寫JNI方法提供Java訪問硬件服務接口
- 在Ubuntu上為Android系統的Application Frameworks層增加硬件訪問服務
- 在Ubuntu上為Android系統內置Java應用程序測試Application Frameworks層的硬件服務
- Android源代碼倉庫及其管理工具Repo分析
- Android編譯系統簡要介紹和學習計劃
- Android編譯系統環境初始化過程分析
- Android源代碼編譯命令m/mm/mmm/make分析
- Android系統鏡像文件的打包過程分析
- 從CM刷機過程和原理分析Android系統結構
- Android系統架構概述
- Android系統整體架構
- android專用驅動
- Android硬件抽象層HAL
- Android應用程序組件
- Android應用程序框架
- Android用戶界面架構
- Android虛擬機之Dalvik虛擬機
- Android硬件抽象層
- Android硬件抽象層(HAL)概要介紹和學習計劃
- Android專用驅動
- Android Logger驅動系統
- Android日志系統驅動程序Logger源代碼分析
- Android應用程序框架層和系統運行庫層日志系統源代碼分析
- Android日志系統Logcat源代碼簡要分析
- Android Binder驅動系統
- Android進程間通信(IPC)機制Binder簡要介紹和學習計劃
- 淺談Service Manager成為Android進程間通信(IPC)機制Binder守護進程之路
- 淺談Android系統進程間通信(IPC)機制Binder中的Server和Client獲得Service Manager接口之路
- Android系統進程間通信(IPC)機制Binder中的Server啟動過程源代碼分析
- Android系統進程間通信(IPC)機制Binder中的Client獲得Server遠程接口過程源代碼分析
- Android系統進程間通信Binder機制在應用程序框架層的Java接口源代碼分析
- Android Ashmem驅動系統
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)簡要介紹和學習計劃
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)驅動程序源代碼分析
- Android系統匿名共享內存Ashmem(Anonymous Shared Memory)在進程間共享的原理分析
- Android系統匿名共享內存(Anonymous Shared Memory)C++調用接口分析
- Android應用程序進程管理
- Android應用程序進程啟動過程的源代碼分析
- Android系統進程Zygote啟動過程的源代碼分析
- Android系統默認Home應用程序(Launcher)的啟動過程源代碼分析
- Android應用程序消息機制
- Android應用程序消息處理機制(Looper、Handler)分析
- Android應用程序線程消息循環模型分析
- Android應用程序輸入事件分發和處理機制
- Android應用程序鍵盤(Keyboard)消息處理機制分析
- Android應用程序UI架構
- Android系統的開機畫面顯示過程分析
- Android幀緩沖區(Frame Buffer)硬件抽象層(HAL)模塊Gralloc的實現原理分析
- SurfaceFlinger
- Android系統Surface機制的SurfaceFlinger服務
- SurfaceFlinger服務簡要介紹和學習計劃
- 啟動過程分析
- 對幀緩沖區(Frame Buffer)的管理分析
- 線程模型分析
- 渲染應用程序UI的過程分析
- Android應用程序與SurfaceFlinger服務的關系
- 概述和學習計劃
- 連接過程分析
- 共享UI元數據(SharedClient)的創建過程分析
- 創建Surface的過程分析
- 渲染Surface的過程分析
- Android應用程序窗口(Activity)
- 實現框架簡要介紹和學習計劃
- 運行上下文環境(Context)的創建過程分析
- 窗口對象(Window)的創建過程分析
- 視圖對象(View)的創建過程分析
- 與WindowManagerService服務的連接過程分析
- 繪圖表面(Surface)的創建過程分析
- 測量(Measure)、布局(Layout)和繪制(Draw)過程分析
- WindowManagerService
- WindowManagerService的簡要介紹和學習計劃
- 計算Activity窗口大小的過程分析
- 對窗口的組織方式分析
- 對輸入法窗口(Input Method Window)的管理分析
- 對壁紙窗口(Wallpaper Window)的管理分析
- 計算窗口Z軸位置的過程分析
- 顯示Activity組件的啟動窗口(Starting Window)的過程分析
- 切換Activity窗口(App Transition)的過程分析
- 顯示窗口動畫的原理分析
- Android控件TextView的實現原理分析
- Android視圖SurfaceView的實現原理分析
- Android應用程序UI硬件加速渲染
- 簡要介紹和學習計劃
- 環境初始化過程分析
- 預加載資源地圖集服務(Asset Atlas Service)分析
- Display List構建過程分析
- Display List渲染過程分析
- 動畫執行過程分析
- Android應用程序資源管理框架
- Android資源管理框架(Asset Manager)
- Asset Manager 簡要介紹和學習計劃
- 編譯和打包過程分析
- Asset Manager的創建過程分析
- 查找過程分析
- Dalvik虛擬機和ART虛擬機
- Dalvik虛擬機
- Dalvik虛擬機簡要介紹和學習計劃
- Dalvik虛擬機的啟動過程分析
- Dalvik虛擬機的運行過程分析
- Dalvik虛擬機JNI方法的注冊過程分析
- Dalvik虛擬機進程和線程的創建過程分析
- Dalvik虛擬機垃圾收集機制簡要介紹和學習計劃
- Dalvik虛擬機Java堆創建過程分析
- Dalvik虛擬機為新創建對象分配內存的過程分析
- Dalvik虛擬機垃圾收集(GC)過程分析
- ART虛擬機
- Android ART運行時無縫替換Dalvik虛擬機的過程分析
- Android運行時ART簡要介紹和學習計劃
- Android運行時ART加載OAT文件的過程分析
- Android運行時ART加載類和方法的過程分析
- Android運行時ART執行類方法的過程分析
- ART運行時垃圾收集機制簡要介紹和學習計劃
- ART運行時Java堆創建過程分析
- ART運行時為新創建對象分配內存的過程分析
- ART運行時垃圾收集(GC)過程分析
- ART運行時Compacting GC簡要介紹和學習計劃
- ART運行時Compacting GC堆創建過程分析
- ART運行時Compacting GC為新創建對象分配內存的過程分析
- ART運行時Semi-Space(SS)和Generational Semi-Space(GSS)GC執行過程分析
- ART運行時Mark-Compact( MC)GC執行過程分析
- ART運行時Foreground GC和Background GC切換過程分析
- Android安全機制
- SEAndroid安全機制簡要介紹和學習計劃
- SEAndroid安全機制框架分析
- SEAndroid安全機制中的文件安全上下文關聯分析
- SEAndroid安全機制中的進程安全上下文關聯分析
- SEAndroid安全機制對Android屬性訪問的保護分析
- SEAndroid安全機制對Binder IPC的保護分析
- 從NDK在非Root手機上的調試原理探討Android的安全機制
- APK防反編譯
- Android視頻硬解穩定性問題探討和處理
- Android系統的智能指針(輕量級指針、強指針和弱指針)的實現原理分析
- Android應用程序安裝過程源代碼分析
- Android應用程序啟動過程源代碼分析
- 四大組件源代碼分析
- Activity
- Android應用程序的Activity啟動過程簡要介紹和學習計劃
- Android應用程序內部啟動Activity過程(startActivity)的源代碼分析
- 解開Android應用程序組件Activity的"singleTask"之謎
- Android應用程序在新的進程中啟動新的Activity的方法和過程分析
- Service
- Android應用程序綁定服務(bindService)的過程源代碼分析
- ContentProvider
- Android應用程序組件Content Provider簡要介紹和學習計劃
- Android應用程序組件Content Provider應用實例
- Android應用程序組件Content Provider的啟動過程源代碼分析
- Android應用程序組件Content Provider在應用程序之間共享數據的原理分析
- Android應用程序組件Content Provider的共享數據更新通知機制分析
- BroadcastReceiver
- Android系統中的廣播(Broadcast)機制簡要介紹和學習計劃
- Android應用程序注冊廣播接收器(registerReceiver)的過程分析
- Android應用程序發送廣播(sendBroadcast)的過程分析