一、概述
ART是Android平臺上的新一代運行時,用來代替dalvik夺巩。它主要采用了AOT的方法驼壶,在apk安裝的時候?qū)alvikbytecode一次性編譯成arm本地指令(但是這種AOT與c語言等還是有本質(zhì)不同的,還是需要虛擬機的環(huán)境支持)簿废,這樣在運行的時候就無需進行任何解釋或編譯便可直接執(zhí)行,節(jié)省了運行時間,提高了效率垮兑,但是在一定程度上使得安裝的時間變長,空間占用變大漱挎。(本文所有時序圖均基于Android M)
二系枪、OAT文件
簡單的說,oat文件是嵌套在一個elf文件的格式中的磕谅。在elf文件的動態(tài)符號表中有三個重要的符號:oatdata私爷、oatexec、oatlastword怜庸,分別表示oat的數(shù)據(jù)區(qū)当犯,oat文件中的native code和結(jié)束位置。
guoqifa@guoqifa:~$ readelf -s system@priv-app@Mms@Mms.apk@classes.dex
Symbol table '.dynsym' contains 4 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
1: 0000000000001000 0x95b000 OBJECT GLOBAL DEFAULT 4 oatdata
2: 000000000095c000 0xb3a4c0 OBJECT GLOBAL DEFAULT 5 oatexec
3: 00000000014964bc 4 OBJECT GLOBAL DEFAULT 5 oatlastword
這些關(guān)系結(jié)構(gòu)在圖中說明的很清楚割疾,簡單理解就是在oatdata中嚎卫,保存有原來的dex文件內(nèi)容,在頭部還保留了尋址到dex文件內(nèi)容的偏移地址和指向?qū)?yīng)的oat class偏移宏榕,oat class中還保存了對應(yīng)的native code的偏移地址拓诸,這樣也就間接的完成了dexbytecode和native code的對應(yīng)關(guān)系。具體請參考:Android ART Oat文件格式簡析(上)麻昼,Android ART Oat文件格式簡析(下)奠支,Android運行時ART加載OAT文件的過程分析. 也可自行分析 art\runtime\Oat_file.cc文件中OatFile::Setup()函數(shù)來進一步學(xué)習(xí)OAT文件。(下圖來源老羅博客)
三抚芦、編譯
apk在安裝時使用dex2oat來編譯倍谜。大概流程如下圖所示迈螟,有兩種后端編譯模式,可選的是所謂快模式(Quick)或優(yōu)化模式(Optimizing)尔崔。如果沒有特別指定的話答毫,編譯鏡像image使用快模式,而編譯一般的應(yīng)用程序則使用優(yōu)化模式季春。
什么是編譯器的后端呢洗搂?其實這是LLVM引入的概念。所有的程序先用前端翻譯成中間表示層载弄,然后進行優(yōu)化耘拇,最后用后端將優(yōu)化過的中間表示層代碼編譯成平臺相關(guān)的代碼。Android雖然沒有直接用LLVM編譯器(以前也確實用過宇攻,但從6.0開始就廢棄掉了)惫叛,但是借鑒了這種編譯器的設(shè)計結(jié)構(gòu)。值得一提的是尺碰,如果使用優(yōu)化模式挣棕,則一定是用PIC模式進行編譯。
更具體的compile時序圖如下(該時序圖只繪出quick編譯模式)亲桥,可自行分析該流程洛心。
四、類加載
ART類的加載通過ClassLinker::DefineClass()函數(shù)來完成题篷。該函數(shù)會分別調(diào)用InsertClass()词身、LoadClass()、LinkClass()來執(zhí)行類的加載番枚。
InsertClass()主要是把該類插入class_table_中法严,方便下次FindClass時直接從LookupClass()中返回結(jié)果。LoadClass()用來從指定的DEX文件中加載指定的類葫笼,并初始化一個Class對象深啤,Class對象包含了一系列的ArtField對象和ArtMethod對象。LinkClass()用來動態(tài)綁定虛函數(shù)和接口函數(shù)路星。
類加載完成后溯街,得到的是一個Class對象。這個Class對象關(guān)聯(lián)有一系列的ArtField對象和ArtMethod對象洋丐。其中呈昔,ArtField對象描述的是成員變量,而ArtMethod對象描述的是成員函數(shù)友绝。對于每一個ArtMethod對象堤尾,它都有一個解釋器入口點和一個本地機器指令入口點。這樣迁客,無論一個類方法是通過解釋器執(zhí)行郭宝,還是直接以本地機器指令執(zhí)行辞槐,我們都可以以統(tǒng)一的方式來進行調(diào)用。
關(guān)于類的加載可參考Android運行時ART加載類和方法的過程分析剩蟀。
五催蝗、方法調(diào)用
Zygote孵化應(yīng)用進程后便會從入口AndroidRuntime.start()進入運行時切威,進入Java世界育特。
ART方法調(diào)用如上圖所示,最終調(diào)用到ArtMethod::Invoke()先朦。ArtMethod正是前面類加載章節(jié)中提到的ArtMethod對象缰冤,“它都有一個解釋器入口點和一個本地機器指令入口點”,Invoke()函數(shù)正是通過art_quick_invoke_stub()或art_quick_invoke_static_stub()來調(diào)用oat文件中的native code的喳魏。art_quick_invoke_stub()是平臺相關(guān)的匯編函數(shù)棉浸,比如我的機器該函數(shù)定義在art/runtime/arch/arm64/quick_entrypoints_arm64.S文件中。更多方法調(diào)用的分析請參考Android運行時ART執(zhí)行類方法的過程分析.
總結(jié):通過查找相關(guān)的oat文件刺彩,得到所需要的類和方法迷郑,并將其對應(yīng)的native code的位置放入ArtMethod結(jié)構(gòu),最后通過Invoke成員完成調(diào)用创倔。
六嗡害、JNI調(diào)用
1. Java調(diào)用native方法
ArtMethod對象與真實執(zhí)行的代碼鏈接的過程主要是通過LinkCode()函數(shù)執(zhí)行的。
void ClassLinker::LinkCode(ArtMethod* method, const OatFile::OatClass* oat_class,
uint32_t class_def_method_index) {
......
if (method->IsNative()) {
// Unregistering restores the dlsym lookup stub.
method->UnregisterNative();
......
}
void ArtMethod::UnregisterNative() {
CHECK(IsNative() && !IsFastNative()) << PrettyMethod(this);
// restore stub to lookup native pointer via dlsym
RegisterNative(GetJniDlsymLookupStub(), false);
}
GetJniDlsymLookupStub()函數(shù)返回平臺相關(guān)的art_jni_dlsym_lookup_stub()匯編函數(shù)指針畦攘,RegisterNative()將該匯編函數(shù)指針注冊到ArtMethod中霸妹,以上是鏈接過程。
待JNI調(diào)用時便會執(zhí)行到匯編art_jni_dlsym_lookup_stub()函數(shù)知押,該函數(shù)會繼續(xù)調(diào)用artFindNativeMethod()函數(shù)叹螟。
extern "C" void* artFindNativeMethod(Thread* self) {
DCHECK_EQ(self, Thread::Current());
#endif
Locks::mutator_lock_->AssertNotHeld(self); // We come here as Native.
ScopedObjectAccess soa(self);
ArtMethod* method = self->GetCurrentMethod(nullptr);
DCHECK(method != nullptr);
// Lookup symbol address for method, on failure we'll return null with an exception set,
// otherwise we return the address of the method we found.
void* native_code = soa.Vm()->FindCodeForNativeMethod(method);
if (native_code == nullptr) {
DCHECK(self->IsExceptionPending());
return nullptr;
} else {
// Register so that future calls don't come here
method->RegisterNative(native_code, false);
return native_code;
}
}
artFindNativeMethod()函數(shù)就是查找到相應(yīng)方法的native code,然后再次注冊到ArtMethod中台盯,這樣以后再執(zhí)行的時候就直接跳到了native code執(zhí)行了罢绽。
2. native調(diào)用Java
native調(diào)用Java是通過JNIEnv->FindClass()、JNIEnv->GetStaticMethodID()静盅、JNIEnv->CallVoidMethod()來查找類良价,得到相應(yīng)的方法的ID,然后通過此ID去調(diào)用温亲。最終如上面第五章方法調(diào)用時序圖所示棚壁,調(diào)用的是ArtMethod::Invoke()。即JNI的這些API其實還是做了一遍ART的類加載和初始化及調(diào)用的過程栈虚。
七袖外、參考文章
1.也來看看Android的ART運行時
2.Android運行時ART簡要介紹和學(xué)習(xí)計劃
3.Android ART Oat文件格式簡析