一、dex/class淺析
1、class與dex對比
型別 | class檔案 | dex檔案 |
---|---|---|
定義 | 能夠被jvm識別、載入並執行的檔案格式 | 能夠被dvm識別、載入並執行的檔案格式 |
如何生成 | 使用java命令(javac) | 使用java命令、dx命令 |
作用 | 記錄一個類檔案的所有資訊 | 記錄整個工程中所有類檔案的資訊 |
2、生成class與dex檔案的指令
生成並執行class檔案對於我們而言實在太熟悉了,這裡只演示dex檔案的生成與執行。
以 Hello World 為例:
public class Hello {
public static void main(String[] args){
System.out.println("Hello LQR!");
}
}
複製程式碼
1)生成dex檔案
生成dex檔案需要用到dx指令,與java指令一樣,也是對應一個對應的程式來執行的,最好配置到環境變數中,具體可看文章末尾。
生成dex檔案之前需要先生成class檔案,所需指令如下:
javac -target 1.6 -source 1.6 Hello.java
dx --dex -- output Hello.dex Hello.class
複製程式碼
2)執行dex檔案
class檔案的執行需要依賴jvm,同理,dex檔案的執行需要依賴dvm,所以dex檔案需要在Android上才能執行。所需指令如下:
adb push Hello.dex /storage/emulated/0
adb shell
dalvikvm -cp /sdcard/Hello.dex Hello
複製程式碼
使用adb將dex檔案放送到Android手機的SD卡目錄之後,再使用adb進入shell,執行dvm指令即可。
二、class檔案結構深入
1、class檔案結構:
- 一種8位位元組的二進位制流檔案
- 各個資料按順序緊密的排列,無間隙
- 每個類或介面都單獨佔據一個class檔案
2、class檔案結構的詳解:
一個class檔案,包含下面表格的所有欄位,
型別 | 名稱 | 數量 | 說明 |
---|---|---|---|
u4 | magic | 1 | 魔數,0xCAFEBAB |
u2 | minor_version | 1 | 次版本號 |
u2 | major_version | 1 | 主版本號 |
u2 | constant_pool_count | 1 | 常量池中常量個數 |
cp_info | constant_pool | constant_pool_count-1 | 表型別資料集合,即常量池中每一項常量都是一個表,共有11種結構各不相同的表結構資料 |
u2 | access_flags | 1 | 訪問標誌,用於識別類或介面層次的訪問資訊 |
u2 | this_class | 1 | 類索引,用於確定這個類的全限定名 |
u2 | super_class | 1 | 父類索引,用於確定這個類父類的全限定名 |
u2 | interfaces_count | 1 | 介面索引計數器 |
u2 | interfaces | interfaces_count | 介面索引集合,用來描述這個類實現了哪些介面 |
u2 | fields_count | 1 | 欄位表計數器,即欄位表集合中的欄位表資料個數 |
field_info | fields | fields_count | 欄位表集合,用於描述介面或類中宣告的變數,包括類級別(static)和例項級別變數,不包括在方法內部宣告的變數 |
u2 | methods_count | 1 | 方法表計數器,即方法表集合中的方法表資料個數 |
method_info | methods | methods_count | 方法表集合,方法表結構和欄位表結構一樣 |
u2 | attributes_count | 1 | 屬性訂數器 |
attribute_info | attributes | attributes_count | 在Class檔案、屬性表、方法表中都可以包含自己的屬性表集合,用於描述某些場景的專有資訊 |
1,無符號數,以u1、u2、u4、u8分別代表1個位元組、2個位元組、4個位元組、8個位元組的無符號數 2,表,以“_info”結尾,由多個無符號數或其它表構成的複合資料型別
3、class檔案弊端:
由class檔案結構(第3點)所導致
- 記憶體佔用大,不適合移動端
- 堆疊的載入模式,載入速度慢
- 檔案IO操作多,類查詢慢
基於以上幾個class檔案的特點,又因為移動端運存較小(以當年的移動端手機為標準),class並不適合直接在移動端裝置上執行。
二、dex檔案結構深入
1、dex檔案結構:
- 一種8位位元組的二進位制流檔案
- 各個資料按順序緊密的排列,無間隙
- 整個應用中所有java原始檔都放在一個dex中
2、dex檔案結構的詳解:
dex檔案與class檔案的結構有很大的不同,如下圖所示:
對應的欄位說明如下表所示:
資料名稱 | 解釋 |
---|---|
header | dex檔案頭部,記錄整個dex檔案的相關屬性 |
string_ids | 字串資料索引,記錄了每個字串在資料區的偏移量 |
type_ids | 類似資料索引,記錄了每個型別的字串索引 |
proto_ids | 原型資料索引,記錄了方法宣告的字串,返回型別字串,引數列表 |
field_ids | 欄位資料索引,記錄了所屬類,型別以及方法名 |
method_ids | 類方法索引,記錄方法所屬類名,方法宣告以及方法名等資訊 |
class_defs | 類定義資料索引,記錄指定類各類資訊,包括介面,超類,類資料偏移量 |
data | 資料區,儲存了各個類的真實資料 |
link_data | 連線資料區 |
3、dex標頭檔案
下面是dex標頭檔案中欄位詳解,與class檔案的結構有部分相同的地方,但因為一個dex檔案中包含n個class檔案,在標頭檔案中需要對所有class進行標記及記錄相關資訊,故會多出一些不同的欄位。
欄位名稱 | 偏移值 | 長度 | 說明 |
---|---|---|---|
magic | 0x0 | 8 | 魔數字段,值為"dex\n035\0" |
checksum | 0x8 | 4 | 校驗碼 |
signature | 0xc | 20 | sha-1簽名 |
file_size | 0x20 | 4 | dex檔案總長度 |
header_size | 0x24 | 4 | 檔案頭長度,009版本=0x5c,035版本=0x70 |
endian_tag | 0x28 | 4 | 標示位元組順序的常量 |
link_size | 0x2c | 4 | 連結段的大小,如果為0就是靜態連結 |
link_off | 0x30 | 4 | 連結段的開始位置 |
map_off | 0x34 | 4 | map資料基址 |
string_ids_size | 0x38 | 4 | 字串列表中字串個數 |
string_ids_off | 0x3c | 4 | 字串列表基址 |
type_ids_size | 0x40 | 4 | 類列表裡的型別個數 |
type_ids_off | 0x44 | 4 | 類列表基址 |
proto_ids_size | 0x48 | 4 | 原型列表裡面的原型個數 |
proto_ids_off | 0x4c | 4 | 原型列表基址 |
field_ids_size | 0x50 | 4 | 欄位個數 |
field_ids_off | 0x54 | 4 | 欄位列表基址 |
method_ids_size | 0x58 | 4 | 方法個數 |
method_ids_off | 0x5c | 4 | 方法列表基址 |
class_defs_size | 0x60 | 4 | 類定義標中類的個數 |
class_defs_off | 0x64 | 4 | 類定義列表基址 |
data_size | 0x68 | 4 | 資料段的大小,必須4k對齊 |
data_off | 0x6c | 4 | 資料段基址 |
源自:Dex檔案格式詳解
對於class檔案及dex檔案的結構 都可以使用 “010 editor” 這個神器進行檢視驗證,網上也有相關的文章說明,有興趣的道友可自行百度 或 訪問如下2篇文章進行查閱瞭解,這裡便不再囉嗦:
4、dex檔案的優勢
dex檔案的標頭檔案與索引區部分,儲存了所有類及類中資料的索引,因此,dvm可通過這兩部分快速查詢到對應類及資料,相對於直接執行class檔案而言,效率上提升了不少。
三、dex與class兩者的異同
經過上面對class檔案與dex檔案的結構進行大概的瞭解之後,我們可以得出如下幾個結論:
- 本質上都是一樣的,dex是從class檔案演變而來的。
- class檔案存在許多冗餘資訊,dex檔案會去除冗餘,並整合。
四、其他
1、配置Android及dx環境變數
以mac為例,windows請百度。
- 到使用者目錄下:
cd ~
複製程式碼
- 開啟.bash_profile檔案
open -e .bash_profile
複製程式碼
如果當前使用者目錄下沒有.bash_profile,可以使用 touch .bash_profile 自行建立
export ANDROID_HOME=/Users/lqr/Library/Android/sdk
export PATH=${PATH}:${ANDROID_HOME}/tools
export PATH=${PATH}:${ANDROID_HOME}/platform-tools
export PATH=${PATH}:${ANDROID_HOME}/build-tools/27.0.3
複製程式碼
ANDROID_HOME與build-tools的值需要根據電腦的情況修改。
- 配置生效
source .bash_profile
複製程式碼
- 測試
在終端輸入adb或dx命令看是否有命令反應即可。