本專題梳理下熱修插件相關知識點向瓷,后續(xù)也好翻閱回顧。
開篇先簡單梳理一些相關基本概念箩绍。
一截碴、編譯相關文件介紹
libdvm.so
dalvik庫文件梳侨。android 5.0之前使用,5.0及之后切換為libart.so
libart.so
art庫文件日丹。zygote啟動時main方法中執(zhí)行AndroidRuntime::start走哺,加載libart.so。
dex2oat
/system/bin目錄下的編譯腳本哲虾。
.dex
符合android虛擬機規(guī)范的字節(jié)碼文件丙躏,編譯的源文件。
.vdex
存儲預先驗證的dex文件束凑,在有效期內(nèi)能減少不必要的dex文件驗證彼哼。
.odex
包含了完整的dex文件和編譯后的機器碼。在有效期內(nèi)可以直接執(zhí)行編譯好的機器碼湘今。.oat文件已被.odex取代
.art
Image文件,記錄應用啟動熱點函數(shù)相關地址剪菱,方便尋址摩瞎。在speed-profile模式下根據(jù)熱點函數(shù)進行類的預加載,預加載后的類可以直接map到內(nèi)存中使用孝常,不需要從dex中加載解析旗们。
文件所在目錄:
主apk包
:
/data/app/cn.com.sina.sports--15rQHIm3VJ-9s1v4EUDfQ== # ls
base.apk lib oat
編譯后生成的文件目錄
:一般應用安裝后就會生成這三個文件
/data/app/cn.com.sina.sports--15rQHIm3VJ-9s1v4EUDfQ==/oat/arm or arm64 # ls
base.art base.odex base.vdex
歷史演變:
版本 | 特點 |
---|---|
<=6.0 | 輸出只有odex文件,其中包含了完整的dex文件和編譯后的機器碼构灸。 |
7.0 | 輸出文件有odex和art文件上渴。 |
8.0 | 輸出文件有odex、art和vdex文件。 |
簡單說:.vdex就是優(yōu)化文件驗證稠氮,.art就是優(yōu)化類加載曹阔。
二、 編譯方式
歷史演變:
版本 | 原理 | 優(yōu)缺點 |
---|---|---|
Android 4.x(Interpreter + JIT) | 代碼默認走解釋器隔披,但熱點函數(shù)會執(zhí)行JIT進行即時編譯 | 優(yōu)點:占用內(nèi)存少 缺點:退出app下次啟動需要重復編譯赃份。 |
Android 5.0/5.1/6.0(interpreter + AOT) | 在AOT模式下,App在安裝過程時奢米, 就會完成所有編譯抓韩。 | 優(yōu)點:性能好 缺點:App安裝時間長,占用存儲空間多鬓长。 |
Android 7.0之后(Interpreter + JIT + AOT) | 代碼默認走解釋器谒拴,但熱點函數(shù)會執(zhí)行JIT進行即時編譯,在特定場景下會按compile-filter進行AOT編譯 | 比較于純AOT模式:安裝時間縮短涉波,占用存儲空間相對少些英上。比較于純JIT,性能更好怠蹂。 |
編譯方式
:具有JIT(Just-In-Time)
和AOT(Ahead-of-Time)
兩種編譯方式善延。
執(zhí)行方式
:解釋器執(zhí)行 和 執(zhí)行編譯后的機器碼 兩種執(zhí)行方式。
機器碼生成方式:JIT生成的機器碼緩存在內(nèi)存中城侧,優(yōu)化解釋模式的執(zhí)行易遣,屬于運行時優(yōu)化。而OAT生成的機器碼緩存為文件嫌佑,屬于持久化優(yōu)化豆茫,每次編譯會更新文件,但是更新頻率并不高屋摇。
使用dex2oat進行AOT編譯的compile filter:
-
verify
:只運行 DEX 代碼驗證揩魂。 -
quicken
:運行 DEX 代碼驗證,并優(yōu)化一些 DEX 指令炮温,以獲得更好的解釋器性能火脉。 -
speed-profile
:運行 DEX 代碼驗證,并對配置文件中列出的方法進行 AOT 編譯柒啤。 -
speed
:運行 DEX 代碼驗證倦挂,并對所有方法進行 AOT 編譯。
三担巩、系統(tǒng)觸發(fā)OAT編譯的時機
路徑 | 描述 | 編譯方式 | 編譯內(nèi)容 |
---|---|---|---|
Install | 應用安裝觸發(fā)的編譯 | speed-profile | 主apk |
OTA升級 | 系統(tǒng)升級觸發(fā)的編譯 | verify | 主apk |
load dexFile | 動態(tài)加載插件觸發(fā)的編譯 | quicken | 插件 |
postboot | 開機1分鐘后方援,jobService觸發(fā)的編譯 | verify | 主apk |
idle&charge | 同時滿足charge、idle狀態(tài)且24小時內(nèi)只觸發(fā)一次涛癌,主apk通過installd觸發(fā)編譯,插件通過虛擬機觸發(fā)編譯 | speed-profile | 主apk 和 插件 |
注:OTA編譯是通過dex2oat腳本執(zhí)行的犯戏。
四送火、源碼中觸發(fā)dex2oat編譯的兩條路徑
4.1 由Installd觸發(fā)
核心方法:
frameworks/base/services/core/java/com/android/server/pm/PackageDexOptimizer.java
private int dexOptPath(PackageParser.Package pkg, String path, String isa,
String compilerFilter, boolean profileUpdated, String classLoaderContext,
int dexoptFlags, int uid, CompilerStats.PackageStats packageStats, boolean downgrade,
String profileName, String dexMetadataPath, int compilationReason) {
//判斷是否主要做dex2oat編譯
int dexoptNeeded = getDexoptNeeded(path, isa, compilerFilter, classLoaderContext,
profileUpdated, downgrade);
if (Math.abs(dexoptNeeded) == DexFile.NO_DEXOPT_NEEDED) {
return DEX_OPT_SKIPPED;
}
...
//通過installd走dex2oat編譯
mInstaller.dexopt(path, uid, pkg.packageName, isa, dexoptNeeded, oatDir, dexoptFlags,
compilerFilter, pkg.volumeUuid, classLoaderContext, pkg.applicationInfo.seInfo,
false /* downgrade*/, pkg.applicationInfo.targetSdkVersion,
profileName, dexMetadataPath,
getAugmentedReasonName(compilationReason, dexMetadataPath != null));
...
}
這個方法主要就干了兩件事:判斷是否需要做dex2oat 和 通過Installer binder call給installd(7.0及之前它與Installer進行socket通信) 去執(zhí)行dexopt操作。
4.2 加載dex流程觸發(fā)
編譯的核心方法在oat_file_manager.cc中的OpenDexFilesFromOat,這里通過oat_file_assistant.cc執(zhí)行isUpToDate判斷dex文件是否需要編譯先匪,如果需要走它的MakeUpToDate方法种吸,執(zhí)行編譯。MakeUpToDate中如果需要執(zhí)行編譯會走GenerateOatFileNoChecks胚鸯,最終調(diào)用其Dex2Oat方法骨稿,調(diào)整好參數(shù)傳給dex2oat這個執(zhí)行文件去Exec。
參考:
https://source.android.com/devices/tech/dalvik/jit-compiler
https://source.android.com/devices/tech/dalvik/configure
https://www.zhihu.com/question/55652975
https://www.zhihu.com/question/275955357