### **一張圖搞懂dex**
[大圖這里](https://box.kancloud.cn/a60feaef41587bfe91f3bb1a10be6bd0_4287x1818.jpg)

:-: 圖1 dex
當然也可以通過下面的圖12 DexFile的文件格式,了解更清楚。
### **DEX文件詳解**
* 什么是dex文件?
* 如何生成一個dex文件
* dex文件的作用
* dex文件格式詳解
#### **什么是dex文件?**
dex文件是Android系統中的一種文件,是一種特殊的數據格式,和APK、jar 等格式文件類似。
能夠被DVM識別,加載并執行的文件格式。
簡單說就是優化后的android版.exe。每個apk安裝包里都有。包含應用程序的全部操作指令以及運行時數據。
相對于PC上的java虛擬機能運行.class;android上的Davlik虛擬機能運行.dex。
當java程序編譯成class后,還需要使用dx工具將所有的class文件整合到一個dex文件,目的是其中各個類能夠共享數據,在一定程度上降低了冗余,同時也是文件結構更加經湊,實驗表明,dex文件是傳統jar文件大小的50%左右

:-: 圖2 apk中的dex文件
**為何要研究dex格式**?因為dex里面包含了所有app代碼,利用反編譯工具可以獲取java源碼。理解并修改dex文件,就能更好的apk破解和防破解。
使用dex文件的最大目的是實現安全管理,但在追求安全的前提下,一定要注意對dex文件實現優化處理。
**注意**:并不是只有Java才可以生成dex文件,C和C++也可以生成dex文件
#### **如何生成一個dex文件?**
1. 通過IDE自動幫我們build生成
2. 手動通過dx命令去生成dex文件
在待測試的class文件目錄下(我將TestMain.class放到了F盤根目錄下),執行命令`dx --dex --output TestMain.dex TestMain.class`,就會生成TestMain.dex文件。
3. 手動運行dex文件在手機
在待測試的dex文件目錄下(我將TestMain.class放到了F盤根目錄下),通過`adb push TestMain.dex /storage/emulated/0`命令,然后通過`adb shell`命令進入手機,后執行`dalvikvm -cp /sdcard/TestMain.dex TestMain`,就會打印出
~~~
Hello World!
~~~
如下圖3所示(使用AS的終端,沒有用Windows的cmd命令)

:-: 圖3 手動運行dex文件
**注意**:
- 環境變量的配置,dex在SDK目錄下的build-tools目錄下有很多版本,這里可以選擇最新版本目錄下dx.bat配置到環境變量path路徑下;同樣,adb命令也同樣配置。
- 運行完dex文件,可以通過exit退出手機,電腦本地盤符
#### **查看dex文件**
[大圖這里](https://box.kancloud.cn/10e05e43d5249e53292c874a189ca716_909x547.jpg)

:-: 圖4 dex文件概貌
**通過010Editor工具(圖片來自網絡)**
[大圖這里](https://box.kancloud.cn/8208f0e93ba1fc2e20ae480e31865dca_636x585.png)

:-: 圖5 注:圖片來自網絡
下圖6是TestMain.dex通過010Editor工具得到的
[大圖這里](https://box.kancloud.cn/30cf8d988e17bb86e2dd619b89022153_599x909.jpg)

:-: 圖6 010Editor 檢測TestMain.dex結果
圖7是通過010Editor工具檢測TestMain.dex得到的 Template Result結果
[大圖這里](https://box.kancloud.cn/5b1f794e214d709e553756fd6d2b4bc2_1659x862.jpg)

:-: 圖7 010Editor檢測TemplateResult結果
**通過dexdump 命令查看(注意)**
利用build-tools 下的dexdump 命令查看,`dexdump -d -l plain TestMain.dex`,得到下面的結果
~~~
F:\>dexdump -d -l plain TestMain.dex
Processing 'TestMain.dex'...
Opened 'TestMain.dex', DEX version '035'
Class #0 -
Class descriptor : 'LTestMain;'
Access flags : 0x0001 (PUBLIC)
Superclass : 'Ljava/lang/Object;'
Interfaces -
Static fields -
Instance fields -
#0 : (in LTestMain;)
name : 'mX'
type : 'I'
access : 0x0001 (PUBLIC)
Direct methods -
#0 : (in LTestMain;)
name : '<init>'
type : '()V'
access : 0x10001 (PUBLIC CONSTRUCTOR)
code -
registers : 2
ins : 1
outs : 1
insns size : 7 16-bit code units
00015c: |[00015c] TestMain.<init>:()V
00016c: 7010 0400 0100 |0000: invoke-direct {v1}, Ljava/lang/Object;.<init>:()V // method@0004
000172: 1200 |0003: const/4 v0, #int 0 // #0
000174: 5910 0000 |0004: iput v0, v1, LTestMain;.mX:I // field@0000
000178: 0e00 |0006: return-void
catches : (none)
positions :
0x0000 line=11
0x0003 line=3
0x0006 line=12
locals :
0x0000 - 0x0007 reg=1 this LTestMain;
#1 : (in LTestMain;)
name : 'main'
type : '([Ljava/lang/String;)V'
access : 0x0009 (PUBLIC STATIC)
code -
registers : 4
ins : 1
outs : 2
insns size : 16 16-bit code units
00017c: |[00017c] TestMain.main:([Ljava/lang/String;)V
00018c: 2200 0100 |0000: new-instance v0, LTestMain; // type@0001
000190: 7010 0000 0000 |0002: invoke-direct {v0}, LTestMain;.<init>:()V // method@0000
000196: 6e10 0200 0000 |0005: invoke-virtual {v0}, LTestMain;.test:()V // method@0002
00019c: 6201 0100 |0008: sget-object v1, Ljava/lang/System;.out:Ljava/io/PrintStream; // field@0001
0001a0: 1a02 0100 |000a: const-string v2, "Hello World!" // string@0001
0001a4: 6e20 0300 2100 |000c: invoke-virtual {v1, v2}, Ljava/io/PrintStream;.println:(Ljava/lang/String;)V // method@0003
0001aa: 0e00 |000f: return-void
catches : (none)
positions :
0x0000 line=6
0x0005 line=7
0x0008 line=8
0x000f line=9
locals :
0x0005 - 0x0010 reg=0 testMainObject LTestMain;
0x0000 - 0x0010 reg=3 args [Ljava/lang/String;
Virtual methods -
#0 : (in LTestMain;)
name : 'test'
type : '()V'
access : 0x0001 (PUBLIC)
code -
registers : 1
ins : 1
outs : 0
insns size : 1 16-bit code units
0001ac: |[0001ac] TestMain.test:()V
0001bc: 0e00 |0000: return-void
catches : (none)
positions :
0x0000 line=15
locals :
0x0000 - 0x0001 reg=0 this LTestMain;
source_file_idx : 8 (TestMain.java)
~~~
- **registers**:Dalvik 最初目標是運行在以ARM 做CPU 的機器上的,**ARM 芯片的一個主要特點是寄存器多**。**寄存器多的話有好處,就是可以把操作數放在寄存器里,而不是像傳統VM 一樣放在棧中**。自然,**操作寄存器是比操作內存(棧嘛,其實就是一塊內存區域)快**。**registers 變量表示該方法運行過程中會使用多少個寄存器**。
- **ins**:輸入參數對應的個數
- **outs**:此函數內部調用其他函數,需要的參數個數。
- **insns size**:以4 字節為單位,代表該函數字節碼的長度(類似Class 文件的code[]數組)
更多內容參考:官網介紹----->[Dalvik 可執行文件格式](https://source.android.com/devices/tech/dalvik/dex-format.html)
#### **dex文件的作用**
記錄**整個工程中所有類的信息**,記住的整個工程所有類的信息
#### **dex文件格式詳解**
- 一種8位字節的二進制流文件
- 各個數據按順序緊密的排列,無間隙
- 整個應用中所有的Java源文件都放在一個dex中
[大圖這里](https://box.kancloud.cn/53f6fab5d77f3bb12a273bd0b01913fd_537x622.jpg)

:-: 圖8 dex文件結構
上圖中的**文件頭**部分,記錄了dex文件的信息,所有字段大致的一個分部;**索引區**部分,主要包含字符串、類型、方法原型、域、方法的索引;索引區最終又被存儲在**數據區**,其中鏈接數據區,主要存儲動態鏈接庫,so庫的信息。
源碼:**/dalvik/libdex/DexFile.h:DexFile**
~~~
struct DexFile {
/* directly-mapped "opt" header */
const DexOptHeader* pOptHeader;
/* pointers to directly-mapped structs and arrays in base DEX */
const DexHeader* pHeader;
const DexStringId* pStringIds;
const DexTypeId* pTypeIds;
const DexFieldId* pFieldIds;
const DexMethodId* pMethodIds;
const DexProtoId* pProtoIds;
const DexClassDef* pClassDefs;
const DexLink* pLinkData;
};
~~~
具體可查看[Android源碼官網](https://source.android.com/devices/tech/dalvik/dex-format)的關于dex文件結構的詳解,如下圖9([大圖這里](https://box.kancloud.cn/227684fb298d4c683bb0a6a5222a7bf3_856x765.jpg))

:-: 圖9 dex文件結構詳解
**總結**:
|數據名稱| 解釋|
| --- | --- |
|header |dex文件頭部,記錄整個dex文件的相關屬性|
|string_ids| 字符串數據索引,記錄了每個字符串在數據區的偏移量|
|type_ids |類似數據索引,記錄了每個類型的字符串索引|
|proto_ids |原型數據索引,記錄了方法聲明的字符串,返回類型字符串,參數列表|
|field_ids |字段數據索引,記錄了所屬類,類型以及方法名|
|method_ids| 類方法索引,記錄方法所屬類名,方法聲明以及方法名等信息|
|class_defs |類定義數據索引,記錄指定類各類信息,包括接口,超類,類數據偏移量|
|data |數據區,保存了各個類的真是數據|
|link_data |連接數據區|
**DEX 文件中會出現的數據類型**
|類型 |含義|
| --- | --- |
|u1 |等同于uint8_t,表示 1 字節的無符號 數|
|u2 |等同于 uint16_t,表示 2 字節的無符號數|
|u4 |等同于 uint32_t,表示 4 字節的無符號數|
|u8 |等同于 uint64_t,表示 8 字節的無符號數|
|sleb128| 有符號 LEB128,可變長度 1~5 字節|
|uleb128 |無符號 LEB128,可變長度 1~5 字節|
|uleb128p1| 無符號 LEB128 值加1,可變長 1~5 字節|
**/dalvik/libdex/DexFile.h**中定義如下
~~~
typedef uint8_t u1;
typedef uint16_t u2;
typedef uint32_t u4;
typedef uint64_t u8;
typedef int8_t s1;
typedef int16_t s2;
typedef int32_t s4;
typedef int64_t s8;
~~~
**LEB128**
LEB128(“Little-Endian Base 128”)表示任意有符號或無符號整數的可變長度編碼。該格式借鑒了 [DWARF3](http://dwarfstd.org/Dwarf3Std.php) 規范。在 .dex 文件中,LEB128 僅用于對 32 位數字進行編碼。
每個 LEB128 編碼值均由 1-5 個字節組成,共同表示一個 32 位的值。每個字節均已設置其最高有效位(序列中的最后一個字節除外,其最高有效位已清除)。每個字節的剩余 7 位均為有效負荷,即第一個字節中有 7 個最低有效位,第二個字節中也是 7 個,依此類推。對于有符號 LEB128 (sleb128),序列中最后一個字節的最高有效負荷位會進行符號擴展,以生成最終值。在無符號情況 (uleb128) 下,任何未明確表示的位都會被解譯為 0。
[大圖這里](https://box.kancloud.cn/ff0794c58d01df4845dc3939385f0a3f_855x87.jpg)

:-: 圖10 雙字節 LEB128 值的按位圖
變量 uleb128p1 用于表示一個有符號值,其表示法是編碼為 uleb128 的值加 1。這使得編碼 -1(或被視為無符號值 0xffffffff)成為一個單字節(但沒有任何其他負數),并且該編碼在下面這些明確說明的情況下非常實用:所表示的數值必須為非負數或 -1(或 0xffffffff);不允許任何其他負值(或不太可能需要使用較大的無符號值)。
以下是這類格式的一些示例:
|編碼序列 | As sleb128 | As uleb128 | As uleb128p1 |
| --- | --- | --- | --- |
|00 |0 | 0 | -1 |
|01 |1 |1 |0 |
|7f |-1 | 127 | 126 |
|80 | 7f |-128 |16256 |16255 |
**dex文件頭**
Dex文件頭主要包括校驗和以及其他結構的偏移地址和長度信息。
源碼位于 **/dalvik/libdex/DexFile.h:DexHeader**
~~~
struct DexHeader {
u1 magic[8]; /* includes version number */
u4 checksum; /* adler32 checksum */
u1 signature[kSHA1DigestLen]; /* SHA-1 hash */
u4 fileSize; /* length of entire file */
u4 headerSize; /* offset to start of next section */
u4 endianTag;
u4 linkSize;
u4 linkOff;
u4 mapOff;
u4 stringIdsSize;
u4 stringIdsOff;
u4 typeIdsSize;
u4 typeIdsOff;
u4 protoIdsSize;
u4 protoIdsOff;
u4 fieldIdsSize;
u4 fieldIdsOff;
u4 methodIdsSize;
u4 methodIdsOff;
u4 classDefsSize;
u4 classDefsOff;
u4 dataSize;
u4 dataOff;
};
~~~
具體詳解如下圖5所示
[大圖這里](https://box.kancloud.cn/c676a551dbf5a9a2ff1c999c652c284d_618x480.png)

:-: 圖11 dex文件頭信息
**各個字段詳解摘要**
**mapOff 字段**
指定 DexMapList 結構距離 Dex 頭的偏移
DexMapList 結構體:
~~~
struct DexMapList
{
u4 size; // DexMapItem 的個數
DexMapItem list[1]; // DexMapItem 結構
};
~~~
* size:表示接下來有多少個 DexMapItem
* list:是一個 DexMapItem 結構體數組
DexMapItem 結構體:
~~~
struct DexMapItem
{
u2 type; // kDexType 開頭的類型
u2 unused; // 未使用,用于對齊
u4 size; // 指定類型的個數
u4 offset; // 指定類型數據的文件偏移
};
~~~
type:一個枚舉常量
~~~
enum
{
kDexTypeHeaderItem = 0x0000, // 對應 DexHeader
kDexTypeStringIdItem = 0x0001, // 對應 stringIdsSize 與 stringIdsOff 字段
kDexTypeTypeIdItem = 0x0002, // 對應 typeIdsSize 與 typeIdsOff 字段
kDexTypeProtoIdItem = 0x0003, // 對應 protoIdsSize 與 protoIdsOff 字段
kDexTypeFieldIdItem = 0x0004, // 對應 fieldIdsSize 與 fieldIdsOff 字段
kDexTypeMethodIdItem = 0x0005, // 對應 methodIdsSize 與 methodIdsOff 字段
kDexTypeClassDefItem = 0x0006, // 對應 classDefsSize 與 classDefsOff 字段
kDexTypeMapList = 0x1000,
kDexTypeTypeList = 0x1001,
kDexTypeAnnotationSetRefList = 0x1002,
kDexTypeAnnotationSetItem = 0x1003,
kDexTypeClassDataItem = 0x2000,
kDexTypeCodeItem = 0x2001,
kDexTypeStringDataItem = 0x2002,
kDexTypeDebugInfoItem = 0x2003,
kDexTypeAnnotationItem = 0x2004,
kDexTypeEncodeArrayItem = 0x2005,
kDexTypeAnnotationsDirectoryItem = 0x2006
};
~~~
* size:指定類型的個數
* offset:指定類型數據的偏移
**DexStringId 結構體(stringIdsSize 與 stringIdsOff 字段)**
~~~
typedef struct _DexStringId
{
u4 stringDataOff; // 指向 MUTF-8 字符串的偏移
}DexStringId, *PDexStringId;
~~~
MUTF-8 編碼:
1) 使用 1~3 字節編碼長度
2) 大于 16 位的 Unicode 編碼 U+10000~U+10FFFF 使用 3 字節來編碼
3) U+0000 采用 2 字節編碼
4) 采用空字符 null 作為結尾
5) 第一個字節存放字節個數(不包含自已)
**DexTypeId 結構體(typeIdsSize 與 typeIdsOff 字段)**
是一個類型結構體
~~~
typedef struct _DexTypeId
{
u4 descriptorIdx; // 指向 DexStringId 列表的索引
}DexTypeId, *PDexTypeId;
~~~
* **descriptorIdx**:指向 DexStringId 列表的索引,它對應的字符串代表了具體類的類型
**DexProtoId 結構體(protoIdsSize 與 protoIdsOff 字段)**
是一個方法聲明結構體,方法聲明 = 返回類型 + 參數列表
~~~
typedef struct _DexProtoId
{
u4 shortyIdx; // 方法聲明字符串,指向 DexStringId 列表的索引
u4 returnTypeIdx; // 方法返回類型字符串,指向 DexStringId 列表的索引
u4 parametersOff; // 方法的參數列表,指向 DexTypeList 結構體的偏移
}DexProtoId, *PDexProtoId;
~~~
* shortyIdx:方法聲明字符串,方法聲明 = 返回類型 + 參數列表
* returnTypeIdx:方法返回類型字符串
* parametersOff:指向一個 DexTypeList 結構體,存放了方法的參數列表
DexTypeList 結構體:
~~~
typedef struct _DexTypeList
{
u4 size; // 接下來 DexTypeItem 的個數
DexTypeItem* list; // DexTypeItem 結構
}DexTypeList, *PDexTypeList;
~~~
* size:接下來 DexTypeItem 的個數
* list:是一個 DexTypeItem 結構體數組
DexTypeItem 結構體:
~~~
typedef struct _DexTypeItem
{
u2 typeIdx; // 指向 DexTypeId 列表的索引
}DexTypeItem, *PDexTypeItem;
~~~
typeIdx:DexTypeId 列表的索引
**DexFieldId 結構體(fieldIdsSize 與 fieldIdsOff 字段)**
指明了字段所有的類、字段的類型以及字段名
~~~
typedef struct _DexFieldId
{
u2 classIdx; // 類的類型,指向 DexTypeId 列表的索引
u2 typeIdx; // 字段的類型,指向 DexTypeId 列表的索引
u4 nameIdx; // 字段名,指向 DexStringId 列表的索引
}DexFieldId, *PDexFieldId;
~~~
* classIdx:類的類型
* typeIdx:字段的類型
* nameIdx:字段名
**DexMethodId 結構體(methodIdsSize 與 methodIdsOff 字段)**
方法結構體
~~~
typedef struct _DexMethodId
{
u2 classIdx; // 類的類型,指向 DexTypeId 列表的索引
u2 protoIdx; // 聲明的類型,指向 DexProtoId 列表的索引
u4 nameIdx; // 方法名,指向 DexStringId 列表的索引
}DexMethodId, *PDexMethodId;
~~~
* classIdx:類的類型
* protoIdx:聲明的類型
* nameIdx:方法名
**DexClassDef 結構體(classDefsSize 和 classDefsOff 字段)**
類結構體
~~~
typedef struct _DexClassDef
{
u4 classIdx; // 類的類型,指向 DexTypeId 列表的索引
u4 accessFlags; // 訪問標志
u4 superclassIdx; // 父類類型,指向 DexTypeId 列表的索引
u4 interfacesOff; // 接口,指向 DexTypeList 的偏移,否則為0
u4 sourceFileIdx; // 源文件名,指向 DexStringId 列表的索引
u4 annotationsOff; // 注解,指向 DexAnnotationsDirectoryItem 結構,或者為 0
u4 classDataOff; // 指向 DexClassData 結構的偏移,類的數據部分
u4 staticValuesOff; // 指向 DexEncodedArray 結構的偏移,記錄了類中的靜態數據,主要是靜態方法
}DexClassDef, *PDexClassDef;
~~~
* classIdx:類的類型,指向 DexTypeId 列表的索引
* accessFlags:訪問標志,它是以ACC_開頭的枚舉值
* superclassIdx:父類類型,指向 DexTypeId 列表的索引
* interfacesOff:接口,指向 DexTypeList 的偏移,如果沒有,則為 0
* sourceFileIdx:源文件名,指向 DexStringId 列表的索引
* annotationsOff:注解,指向 DexAnnotationsDirectoryItem 結構,或者為 0
* classDataOff:指向 DexClassData 結構的偏移,類的數據部分
* staticValuesOff:指向 DexEncodeArray 結構的偏移,記錄了類中的靜態數據,沒有則為 0
DexClassData 結構體:
~~~
typedef struct _DexClassData
{
DexClassDataHeader header; // 指定字段與方法的個數
DexField* staticFields; // 靜態字段,DexField 結構
DexField* instanceFields; // 實例字段,DexField 結構
DexMethod* directMethods; // 直接方法,DexMethod 結構
DexMethod* virtualMethods; // 虛方法,DexMethod 結構
}DexClassData, *PDexClassData;
~~~
* header:DexClassDataHeader 結構體,指定字段與方法的個數
* staticFields:靜態字段,DexField 結構體數組
* instanceFields:實例字段,DexField 結構體數組
* directMethods:直接方法,DexMthod 結構體數組
* virtualMethods:虛方法,DexMethod 結構體數組
DexClassDataHeader 結構體:
~~~
typedef struct _DexClassDataHeader
{
uleb128 staticFieldsSize; // 靜態字段個數
uleb128 instanceFieldsSize; // 實例字段個數
uleb128 directMethodsSize; // 直接方法個數
uleb128 virtualMethodsSize; // 虛方法個數
}DexClassDataHeader, *PDexClassDataHeader;
~~~
* staticFieldsSize:靜態字段個數
* instanceFieldsSize:實例字段個數
* directMethodsSize:直接方法個數
* virtualMethodsSize:虛方法個數
DexField 結構體:
~~~
typedef struct _DexField
{
uleb128 fieldIdx; // 指向 DexFieldId 的索引
uleb128 accessFlags; // 訪問標志
}DexField, *PDexField;
~~~
* fieldIdx:字段描述,指向 DexFieldId 的索引
* accessFlags:訪問標志
DexMethod 結構體:
~~~
typedef struct _DexMethod
{
uleb128 methodIdx; // 指向 DexMethodId 的索引
uleb128 accessFlags; // 訪問標志
uleb128 codeOff; // 指向 DexCode 結構的偏移
}DexMethod, *PDexMethod;
~~~
* methodIdx:方法描述,指向 DexMethodId 的索引
* accessFlags:訪問標志
* codeOff:指向 DexCode 結構的偏移
DexCode 結構體:
~~~
typedef struct _DexCode
{
u2 registersSize; // 使用的寄存器個數
u2 insSize; // 參數個數
u2 outsSize; // 調用其他方法時使用的寄存器個數
u2 triesSize; // Try/Catch 個數
u4 debbugInfoOff; // 指向調試信息的偏移
u4 insnsSize; // 指令集個數,以 2 字節為單位
u2* insns; // 指令集
}DexCode, *PDexCode;
~~~
還有一些不太常見的結構體,要用的時候再去看看就行了。Dex 文件的整體結構就這樣,就是一個多層索引的結構。
**string_ids(字符串索引)**
這一區域存儲的是Dex文件字符串資源的索引信息,該索引信息是目標字符串在Dex文件數據區所在的真實物理偏移量。
源碼位于 **/dalvik/libdex/DexFile.h:DexStringId**
~~~
struct DexStringId {
u4 stringDataOff; /* file offset to string_data_item */
};
~~~
**stringDataOff**記錄了目標字符串在Dex文件中的實際偏移量,虛擬機想讀取該字符串時,只需將Dex文件在內存中的起始地址加上stringDataOff所指的偏移量,就是該字符串在內存中的實際物理地址。
在Dex文件中,每個每個字符串對應一個**DexStringId**,大小4B。另外虛擬機通過DexHeader中的**String_ids_size**獲得當前Dex文件中的字符串的總數,通過乘法就可對該索引資源進行訪問。
**DexLink**
~~~
struct DexLink {
u1 bleargh;
};
~~~
#### **DexFile 在內存中的映射**
在Android系統中, java 源文件會被編譯為“ .jar ” 格式的dex類型文件, 在代碼中稱為dexfile 。在加載Class 之前, 必先讀取相應的jar文件。通常我們使用read()函數來讀取文件中的內容。但在Dalvik中使用mmap() 函數。和read()不同, mmap()函數會將dex文件映射到內存中,這樣通過普通的內存讀取操作即可訪問dexfile中的內容。
Dexfile的文件格式如圖12 所示, 主要有三部分組成:頭部,索引,數據。通過頭部可知索引的位置和數同,可知數據區的起始位置。其中classDefsOff 指定了ClassDef 在文件的起始位置, dataOff 指定了數據在文件的起始位置, ClassDef 即可理解為Class 的索引。通過讀取ClassDef 可獲知Class 的基本信息,其中classDataOff 指定了Class 數據在數據區的位置。
[大圖這里](https://box.kancloud.cn/31332ada4045c4713b69ae041c03cca9_901x751.jpg)

:-: 圖12 DexFile的文件格式
在將dexfile文件映射到內存后,會調用dexFileParse()函數對其分析,分析的結果存放于名為DexFile的數據結構中。DexFile 中的baseAddr指向映射區的起始位置, pClassDefs 指向ClassDefs(即class索引)的起始位置。由于在查找class 時,都是使用class的名字進行查找的,所以為了加快查找速度, 創建了一個hash表。在hash表中對class 名字進行hash,并生成index。這些操作都是在對文件解析時所完成的,這樣雖然在加載過程中比較耗時,但是在運行過程中可節省大量查找時間。
解析完后, 接下來開始加載class文件。在此需要將加載類用ClassObject來保存,所以在此需要先分析和ClassObject 相關的幾個數據結構。
首先在文件Object.h 中可以看到如下對結構體Object 的定義。(android2.3.7源碼)
~~~
typedef struct Object {
/* ptr to class object */
ClassObject* clazz;
/*
* A word containing either a "thin" lock or a "fat" monitor. See
* the comments in Sync.c for a description of its layout.
*/
u4 lock;
} Object;
~~~
通過結構體Object定義了基本類的實現,這里有如下兩個變量。
* lock : 對應Obejct 對象中的鎖實現,即notify wait 的處理。
* clazz : 是結構體指針,姑且不看結構體內容,這里用了指針的定義。
下面會有更多的結構體定義:
~~~
struct DataObject {
Object obj; /* MUST be first item */
/* variable #of u4 slots; u8 uses 2 slots */
u4 instanceData[1];
};
struct StringObject {
Object obj; /* MUST be first item */
/* variable #of u4 slots; u8 uses 2 slots */
u4 instanceData[1];
};
~~~
我們看到最熟悉的一個詞StringObject ,把這個結構體展開后是下面的樣子。
~~~
struct StringObject {
/* ptr t o class object */
ClassObject* clazz ;
/* variable #of u4 slots; u8 uses 2 slots */
u4 lock;
u4 instanceData[1];
};
~~~
由此不難發現, 任何對象的內存結構體中第一行都是Object結構體,而這個結構體第一個總是一個ClassObejct,第二個總是lock 。按照C++中的技巧,這些結構體可以當成Object結構體使用,因此所有的類在內存中都具有“對象”的功能,即可以找到一個類(ClassObject),可以有一個鎖(lock) 。
StringObject是對String類進行管理的數據對象,ArrayObejct是數據相關的管理。
#### **ClassObject-Class 在加載后的表現形式**
在解析完文件后, 接下來需要加載Class 的具體內容。在Dalvik中, 由數據結構ClassObject負責存放加載的信息。如圖13所示,加載過程會在內存中alloc幾個區域,分別存放directMethods 、virtualMethods 、sfields 、ifields 。這些信息是從dex 文件的數據區中讀取的,首先會讀取Class 的詳細信息,從中獲得directMethod 、virtua!Method 、sfield 、ifield 等的信息,然后再讀取。在此需要注意, 在C lassObj ect 結構中有個名為super 的成員,通過super成員可以指向它的超類。
[大圖這里](https://box.kancloud.cn/c2f4f23796b3858cf8f7813ad30dedf5_965x623.jpg)

圖13 加載過程
#### **Android dex 文件優化**
對Android dex 文件進行優化來說, 需要注意的一點是dex文件的結構是緊湊的,但是我們**還是要想方設法地進行提高程序的運行速度,我們就仍然需要對dex文件進行進一步優化**。
調整所有字段的字節序( LITTLE_ENDIAN),和對齊結構中的每一個域來驗證dex文件中的所有類,并對一些特定的類進行優化或對方法里的操作碼進行優化。優化后的文件大小會有所增加, 大約是原Android dex文件的1~4 倍。
**優化時機**
優化發生的時機有兩個:
* 對于預置應用來說,可以在系統編譯后,生成優化文件,以ODEX 結尾。這樣在發布時除APK文件(不包含dex)以外,還有一個相應的Android dex 文件。
* 對于非預置應用, 包含在APK文件里的dex 文件會在運行時被優化,優化后的文件將被保存在緩存中。
如下圖14所示代碼調用流程

圖14 代碼調用流程
**每一個Android應用都運行在一個Dalvik虛擬機實例里,而每一個虛擬機實例都是一個獨立的進程空間**。虛擬機的線程機制,內存分配和管理, Mutex等都是依賴底層操作系統而實現的。
**所有Android應用的線程都對應一個Linux線程(可參考----[理解Android線程創建流程](http://www.hmoore.net/alex_wsc/androidsystem/403698))**,虛擬機因而可以更多地依賴操作系統的線程調度和管理機制。**不同的應用在不同的進程空間里運行,加之對不同來源的應用都使用不同的Linux用戶來運行,可以最大限度地保護應用的安全和獨立運行。**
**Zygote是一個虛擬機進程,同時也是一個虛擬機實例的孵化器,每當系統要求執行一個Android應用程序,Zygote就會孵化出一個子進程來執行該應用程序**。這樣做的好處顯而易見:**Zygote進程是在系統啟動時產生的,它會完成虛擬機的初始化,庫的載,預置類庫的加載和初始化等操作,而在系統需要一個新的虛擬機實例時,Zygote通過復制自身,最快速地提供一個虛擬機實例。另外,對于一些只讀的系統庫,所有虛擬機實例都和Zygote 共享一塊內存區域,大大節省了內存開銷**。
Android 應用所使用的編程語言是Java語言,和Java SE 一樣,編譯時使用Oracle JDK 將Java源程序編程成標準的Java 字節碼文件(. class 文件)。而后通過工具軟件DX 把所有的字節碼文件轉成Android dex 文件(classes . dex) 。最后使用Android 打包工具(aapt)將dex 文件、資源文件以及AndroidManifest.xml 文件(二進制格式)組合成一個應用程序包(APK) 。應用程序包可以被發布到手機上運行。

圖15 Android應用編譯及運行流程
#### **odex 介紹**
odex 是Optimized dex 的簡寫,也就是優化后的dex 文件。**為什么要優化呢?主要還是為了提高Dalvik 虛擬機的運行速度**。但是odex 不是簡單的、通用的優化,而是在其優化過程中,依賴系統已經編譯好的其他模塊,簡單點說:
* 從Class 文件到dex 文件是針對Android 平臺的一種優化,是一種通用的優化。優化過程中,唯一的輸入是Class 文件。
* odex 文件就是dex 文件具體在某個系統(不同手機,不同手機的OS,不同版本的OS 等)上的優化。**odex 文件的優化依賴系統上的幾個核心模塊**( 由BOOTCLASSPATH 環境變量給出, 一般是/system/framework/下的jar 包,尤其是core.jar)。odex 的優化就好像是把中那些本來需要在執行過程中做的類校驗、調用其他類函數時的解析等工作給提前處理了。
通過利用dexopt得到test.odex,接著利用dexdump得到其內容,最后可以利用Beyond Compare比較這兩個文件的差異。
如下圖所示

圖16 test.dex 和test.odex 差異
圖16中,綠色框中是test.dex的內容,紅色框中是test.odex的內容,這也是兩個文件的差異內容:
* test.dex中,TestMain類僅僅是PUBLIC的,但test.odex則增加了**VERIFIED和OPTIMIZED**兩項。**VERIFIED**是表示該類被校驗過了,至于校驗什么東西,以后再說。
* 然后就是一些方法的不同了。優化后的odex文件,一些字節碼指令變成了xxx-quick。比如圖中最后一句代碼對于的字節碼中,未優化前**invoke-virtual**指令表示從**method table**指定項(圖中是0002)里找到目標函數,而優化后的odex使用了**invoke-virtual-quick**表示從**vtable**中找到目標函數(圖中是000b)。
vtable是虛表的意思,一般在OOP實現中用得很多。vtable一定比methodtable快么?那倒是有可能。我個人猜測:
* method表應該是每個dex文件獨有的,即它是基于dex文件的。
* 根據odex文件的生成方法(后面會講),我覺得vtable恐怕是把dex文件及依賴的類(比如Java基礎類,如Object類等)放一起進行了處理,最終得到一張大的vtable。這個odex文件依賴的一些函數都放在vtable中。運行時直接調用指定位置的函數就好,不需要再解析了。以上僅是我的猜測。
> **注意**:
> **odex文件由dexopt生成,這個工具在SDK里沒有,只能由源碼生成**。odex文件的生成有三種方式:
> * **preopt**:即OEM廠商(比如手機廠商),在制作鏡像的時候,就把那些需要放到鏡像文件里的jar包,APK等預先生成對應的odex文件,然后再把classes.dex文件從jar包和APK中去掉以節省文件體積。
> * **installd**:當一個apk安裝的時候,PackageManagerService會調用installd的服務,將apk中的class.dex進行處理。當然,這種情況下,APK中的class.dex不會被剔除。
> * **dalvik VM**:preopt是廠商的行為,可做可不做。如果沒有做的話,dalvik VM在加載一個dex文件的時候,會先生成odex。所以,dalvik VM實際上用得是odex文件。以后我們研究dalvik VM的時候會看到這部分內容。
>
> 實際上dex轉odex是利用了dalvik vm,里邊也會運行dalvik vm的相關方法。
**總結**:
1. 以標準角度來看,Class文件是由Java VM規范定義的,所以通用性更廣。dex或者是odex只不過是規范在Android平臺上的一種具體實現罷了,而且dex/odex在很多地方也需要遵守規范。因為dex文件的來源其實還是Class文件。
2. 對于初學者而言,我建議了解Class文件的結構為主。另外,關于dex/odex的文件結構,除非有明確需求(比如要自己修改字節碼等),否則以了解原理就可以。而且,將來我們看到dalvik vm的實際代碼后,你會發現dex的文件內容還是會轉換成代碼里的那些你很熟悉的類型,數據結構。比如dex存儲字符串是一種優化后的方法,但是到vm代碼中,還不是只能用字符串來表示嗎?
3. 另外,你還會發現,Class、dex還是odex文件都存儲了很多源碼中的信息,比如類名、函數名、參數信息、成員變量信息等,而且直接用得是字符串。這和Native的二進制比起來,就容易看懂多了。
#### **參考鏈接**
[深入理解Android之Java虛擬機Dalvik](http://blog.csdn.net/innost/article/details/50377905)
[Androidsource之Dalvik 字節碼](https://source.android.com/devices/tech/dalvik/dalvik-bytecode)
[Androidsource之Dalvik 可執行文件格式(dex文件)](https://source.android.com/devices/tech/dalvik/dex-format)
[Android安全–Dex文件格式詳解](http://www.blogfshare.com/dex-format.html)
[詳細描述了dex/odex指令的格式----->Dalvik opcodes](http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html)
[解釋器中對 標號 的使用](http://www.cnblogs.com/openix/archive/2012/12/14/2818495.html)
[A deep dive into DEX file format](https://elinux.org/images/d/d9/A_deep_dive_into_dex_file_format--chiossi.pdf)
[Dex文件格式詳解](http://www.jianshu.com/p/f7f0a712ddfe)
[android中Dex文件結構詳解](https://bbs.pediy.com/thread-203417.htm)
[Dex文件及Dalvik字節碼格式解析](http://blog.csdn.net/u012342262/article/details/52559190)
[Dex 文件格式詳解](http://blog.csdn.net/anhuizhuanjiao/article/details/49814549)
[Dex文件格式詳解](http://blog.csdn.net/woblog/article/details/52106546)
[Android關于Dex拆分(MultiDex)技術詳解](http://blog.csdn.net/tan6458/article/details/54315037)
- 前言
- Android 熱補丁技術——資源的熱修復
- 插件化系列詳解
- Dex分包——MultiDex
- Google官網——配置方法數超過 64K 的應用
- IMOOC熱修復與插件化筆記
- 第1章 class文件與dex文件解析
- Class文件解析
- dex文件解析
- class與dex對比
- 第2章 虛擬機深入講解
- 第3章 ClassLoader原理講解
- 類的加載過程
- ClassLoade源碼分析
- Android中的動態加載
- 第4章 熱修復簡單講解
- 第5章 熱修復AndFix詳解
- 第6章 熱修復Tinker詳解及兩種方式接入
- 第7章 引入熱修復后代碼及版本管理
- 第8章 插件化原理深入講解
- 第9章 使用Small完成插件化
- 第10章 使用Atlas完成插件化
- 第11章 課程整體總結
- DN學院熱修復插件化筆錄
- 插件化
- 熱修復
- Android APP開發應掌握的底層知識
- 概述
- Binder
- AIDL
- AMS
- Activity的啟動和通信原理
- App啟動流程第2篇
- App內部的頁面跳轉
- Context家族史
- Service
- BroadcastReceiver
- ContentProvider
- PMS及App安裝過程