一、概述
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ù)來進一步學習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簡要介紹和學習計劃
3.Android ART Oat文件格式簡析