往期目錄:java
Class 文件格式詳解android
Smali 語法解析 —— 類github
咱們在解壓縮 APK 文件以後,會看到一個叫作 resources.arsc
的文件,它的格式稱之爲 ARSC 文件格式 。那麼它的做用是什麼呢?你們對 R 文件確定都十分熟悉,它存儲了資源的 ID。在打包過程當中,但凡使用到資源的地方都是使用這個 ID 來代替的。ARSC 文件就是一個資源索引表,它能夠幫助系統根據資源 ID 快速找到資源。微信
當咱們使用 ApkTool 反編譯的時候,會在 res/value
目錄下生成一個 public.xml
文件,裏面就記錄了資源項及其對應的 ID,以下圖所示:數據結構
雖然沒有細看過 ApkTool 的源碼,但我猜想這應該就是根據 ARSC 文件解析出來的。關於 ARSC 的文件結構,網上有一張很好的圖片,拿過來給你們看一下:
若是以爲有點繞,能夠對照我畫的思惟導圖來閱讀後面的文章:
ARSC 文件格式的數據結構在 AOSP 中也有相應的定義,位於 ResourceType.h 文件中 。總體上能夠分爲下面三大塊:
ResTableHeader :
文件頭ResStringPool :
資源項值字符串池ResTablePackage :
數據塊其中 ResTablePackage
項最爲複雜,包含了 ARSC 文件的數據塊內容。其餘兩塊內容較爲簡單。下面就來一一解析。
struct ResTable_header {
struct ResChunk_header header;
// The number of ResTable_package structures.
uint32_t packageCount;
};
複製代碼
這裏的 header 是 ResChunk_header
類型,咱們先來看一下這個類,它在 ARSC 文件的其餘部分也會出現不少次。其實 ARSC 文件和 AndroidManifest.xml 文件有一些相似,也是由一個一個 Chunk 組成的。每個 Chunk 都有固定的 ResTable_header,具體格式以下:
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;
};
複製代碼
type
是該 Chunk 的標識符,不一樣的 Chunk 都有本身的標識符。headerSize
表示當前 Chunk Header 的大小。size
表示當前 Chunk 的大小。
ResChunkHeader 的結構仍是很簡單的,咱們再回到 ResTableHeader。它除了 header
字段以外,還有一個 packageCount
字段,表示 ARSC 文件 ResTablePackage 的個數,即數據塊的個數,一般是 1。
ResTableHeader
後面緊接着的就是 ResStringPool
,存放了 APK 中全部資源項值的字符串內容。我這裏就不貼 ResStringPool 的結構圖了,建議閱讀的時候直接對照着我上面給的思惟導圖,或者對照着 010 Editor 的解析結果。
ResStringPool 也有一個頭,叫作 ResStringPoolHeader
,其格式以下:
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;
};
複製代碼
有六個字段,來分別看一下:
header :
ResChunkHeader,其 type 是 RES_STRING_POOL_TYPE
stringCount :
字符串個數styleCount :
字符串樣式個數flags :
字符串的屬性,可取值包括0x000(UTF-16),0x001(字符串通過排序)、0X100(UTF-8)和他們的組合值stringsStart :
字符串內容偏移量stylesStart :
字符串樣式內容偏移量ResStringPoolHeader
以後跟着的是兩個偏移量數組 stringOffsets
和 styleOffsets
,分別是字符串內容偏移量數組和字符串樣式內容偏移量數組。上面提到的偏移量都是相對整個 ResStringPool 的。根據起始偏移量和每一個字符串的偏移量數組,咱們就能夠獲取到全部字符串了。注意這裏的字符串並非純粹的字符串,它也是有結構的。 u16len
和 u8len
,分別表明 UTF-8
和 UTF-16
下的字符串長度。那麼如何區分呢?以前的 ResStringPoolHeader 中的 flags
屬性就標記了編碼格式。若是是 utf-8,則字符串以 0x00
結尾,開頭前兩個字節分別表示 u8len 和 u16len。若是是 utf-16,則字符串以 0x0000
結尾,開頭前兩個字節表示 u16len
,沒有 u8len
字段。
下面簡單看一下解析代碼:
private ResStringPoolHeader parseStringPoolType(List<String> stringPoolList) {
int currentPosition = reader.getCurrentPosition();
ResStringPoolHeader stringPoolHeader = new ResStringPoolHeader();
try {
stringPoolHeader.parse(reader);
List<Integer> stringOffsets = new ArrayList<>(stringPoolHeader.stringCount);
for (int i = 0; i < stringPoolHeader.stringCount; i++) {
int offset = reader.readInt();
stringOffsets.add(offset);
}
List<Integer> styleOffsets = new ArrayList<>();
for (int i = 0; i < stringPoolHeader.styleCount; i++) {
styleOffsets.add(reader.readInt());
}
int position = reader.getCurrentPosition();
for (int i = 0; i < stringPoolHeader.stringCount; i++) {
int length = 0;
int skipLength = 0;
if (stringPoolHeader.flags == ResStringPoolHeader.UTF8_FLAG) {
int u16len = reader.read(position + stringOffsets.get(i), 1)[0];
int u8len = reader.read(position + stringOffsets.get(i), 1)[0];
length = u8len;
skipLength = 1; // 若是是 utf-8,則字符串以 0x00結尾
} else {
int u16len =reader.readUnsignedShort();
length = u16len;
skipLength = 2; // 若是是 utf-16,則字符串以 0x0000結尾
}
String string = "";
try {
string = new String(reader.read(position + stringOffsets.get(i) + 2, skipLength*length));
reader.skip(skipLength);
} catch (Exception e) {
log(" parse string[%d] error!", i);
}
stringPoolList.add(string);
log(" stringPool[%d]: %s", i, string);
}
for (int i = 0; i < stringPoolHeader.styleCount; i++) {
int index = reader.readInt();
int firstChar = reader.readInt();
int lastChar = reader.readInt();
ResSpanStyle resSpanStyle = new ResSpanStyle(index, firstChar, lastChar);
log(resSpanStyle.toString());
reader.skip(4); // 0xffff
}
reader.moveTo(currentPosition + stringPoolHeader.resChunkHeader.size);
return stringPoolHeader;
} catch (IOException e) {
log(" parse string pool type error!");
}
return null;
}
複製代碼
我拿我本身的 Wanandroid 安裝包解壓獲得的 ARSC 文件作測試,一共打印了 2411
個字符串,以下圖所示:
看完了字符串池,接下來就是最最重要的資源數據塊 ResTablePackage
了。
從文章開頭給出的思惟導圖就能夠看出來,ResTablePackage
佔據了 ARSC 文件內容的大半壁江山。ResTablePackage 又能夠分爲五小塊,以下所示:
ResTablePackageHeader :
頭信息typeStrings :
資源類型字符串池keyStrings :
資源項名稱字符串池ResTableTypeSpec :
資源表規範ResTableType :
資源表類型配置下面來一一進行解析。
其實我只是爲了保持名稱統一才取了個名字叫 ResTablePackageHeader,可是在 AOSP 中是叫作 ResTable_package
,其內容以下所示:
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.
uint16_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;
uint32_t typeIdOffset;
};
複製代碼
header :
ResChunkHeader , 其 type 是 RES_TABLE_PACKAGE_TYPE
id :
包的 ID, 等於 Package Id,通常用戶包的 Package Id 爲 0X7F
, 系統資源包的 Package Id 爲 0X01
。name :
包名typeStrings :
資源類型字符串池在 ResTablePackage 中的偏移量lastPublicType :
通常資源類型字符串資源池的元素個數keyStrings :
資源名稱字符串池在 ResTablePackage 中的偏移量lastPublicKey :
通常指資源項名稱字符串資源池的元素個數。typeIdOffset :
未知,值爲 0typeStrings
是資源類型字符串池,既然是資源類型,很容易就想到 string
、layout
、drawable
、mipmap
等等,這些都是資源類型。說直白點,就是一般寫代碼時候的 R.
後面跟的東西。typeStrings
就是一個 ResStringPool
,因此它的解析方式和以前是如出一轍的。直接看一下解析結果:
typeStrings:
stringPool[0]: a n i m
stringPool[1]: a n i m a t o r
stringPool[2]: a t t r
stringPool[3]: b o o l
stringPool[4]: c o l o r
stringPool[5]: d i m e n
stringPool[6]: d r a w a b l e
stringPool[7]: i d
stringPool[8]: i n t e g e r
stringPool[9]: i n t e r p o l a t o r
stringPool[10]: l a y o u t
stringPool[11]: m e n u
stringPool[12]: m i p m a p
stringPool[13]: r a w
stringPool[14]: s t r i n g
stringPool[15]: s t y l e
複製代碼
這裏是 utf-16 編碼的。
keyStrings
是資源項名稱字符串池,它也是 ResStringPool
,就再也不多說了,直接看解析結果:
keyStrings:
stringPool[0]: abc_fade_in
stringPool[1]: abc_fade_out
stringPool[2]: abc_grow_fade_in_from_bottom
stringPool[3]: abc_popup_enter
stringPool[4]: abc_popup_exit
stringPool[5]: abc_shrink_fade_out_from_bottom
stringPool[6]: abc_slide_in_bottom
stringPool[7]: abc_slide_in_top
stringPool[8]: abc_slide_out_bottom
stringPool[9]: abc_slide_out_top
stringPool[10]: abc_tooltip_enter
stringPool[11]: abc_tooltip_exit
stringPool[12]: btn_checkbox_to_checked_box_inner_merged_animation
stringPool[13]: btn_checkbox_to_checked_box_outer_merged_animation
stringPool[14]: btn_checkbox_to_checked_icon_null_animation
...
...
...
stringPool[2322]: Widget.Support.CoordinatorLayout
stringPool[2323]: leak_canary_LeakCanary.Base
stringPool[2324]: leak_canary_Theme.Transparent
複製代碼
資源項名稱字符串池 keyStrings 以後是 ResTableTypeSpec
和 ResTableType
,它們是不定的交叉出現的。咱們先來看看 ResTableTypeSpec
。
ResTableTypeSpec
是資源表規範,用來描述資源項的配置差別性。系統根據不一樣設備的配置差別就能夠加載不一樣的資源項。該部分數據結構對應結構體 ResTable_typeSpec
:
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
};
};
複製代碼
header
: ResChunkHeader,其 type 是 RES_TABLE_TYPE_SPEC_TYPE
id
: 標識資源的 Type ID, Type ID 是指資源的類型 ID 。資源的類型有 animator、anim、color、drawable、layout、menu、raw、string 和 xml 等等若干種,每一種都會被賦予一個 IDres0
: must be 0res1
: must be 0entryCount
: 等於本類型的資源項個數,指名稱相同的資源項的個數緊接着後面的是 entryCount 個 uint_32 數組,數組每一個元素都是用來描述資源項的配置差別性的。
ResTableType
是資源項的具體信息,包括資源項的名稱,類型,值和配置等等。對應結構體 ResTable_type
:
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;
};
複製代碼
header
: ResChunkHeader,其 type 是 RES_TABLE_TYPE_TYPE
id
: 標識資源的 Type ID, Type ID 是指資源的類型 ID 。資源的類型有 animator、anim、color、drawable、layout、menu、raw、string 和 xml 等等若干種,每一種都會被賦予一個 IDres0
: must be 0res1
: must be 0entryCount
: 資源項的個數entryStart
:資源項相對於本結構的偏移量config
: 資源的配置信息config
以後是一個大小爲 entryCount 的 uint32_t 數組,用於描述資源項數據庫的偏移量。這個偏移量數組以後是一個 ResTableEntry[]
,咱們再來看一下這塊內容。
ResTableEntry
是資源項數據,對應結構體 ResTable_entry
:
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,
// If set, this is a weak resource and may be overriden by strong
// resources of the same name/type. This is only useful during
// linking with other resource tables.
FLAG_WEAK = 0x0004
};
uint16_t flags;
// Reference into ResTable_package::keyStrings identifying this entry.
struct ResStringPool_ref key;
};
複製代碼
size
: 該結構體大小flags
: 標誌位key
: 資源項名稱在資源項名稱字符串資源池的索引根據 flags 的不一樣,後面的數據結構也有所不一樣。若是 flags 包含 FLAG_COMPLEX(0x0001)
,則該數據結構是 ResTableMapEntry
,ResTableMapEntry
是繼承自 ResTableEntry
的,在原有結構上多了兩個 uint32_t 字段 parent
和 count
。parent
表示父資源項的 ID。count
表示接下來有多少個 ResTableMap
。ResTableMap
結構以下所示:
struct ResTable_map {
ResTable_ref name; // 資源名稱
Res_value value; // 資源值
}
複製代碼
再來看看 ResValue
:
struct Res_value {
uint16_t size;
uint8_t res0;
uint8_t dataType;
data_type data;
複製代碼
以上就是 flags 包含 FLAG_COMPLEX(0x0001)
時表示的 ResTableMapEntry
的結構。若是不包含的話,就直接是 Res_value
。
最後關於 Package 數據塊的內容其實說的比較簡略,由於我的以爲了解了解就能夠了。若是想要深刻學習的話,我的十分推薦老羅的一篇文章,Android應用程序資源的編譯和打包過程分析,寫的至關的詳細。
最後仍是給出解析代碼地址,ResParser 。
文章首發微信公衆號:
秉心說
, 專一 Java 、 Android 原創知識分享,LeetCode 題解。更多 JDK 源碼解析,掃碼關注我吧!