1.Android預優化的原理java
先來回顧一下Android的發展史,在2014年的Google I/O大會上,Google隆重的發佈了Android 4.4操做系統,其中有一個環節着重介紹了ART(Android runtime),也就是虛擬機,也就是運行APP的環境,也就是運行Java代碼的虛擬機。雖然Android 4.4版本默認仍然用DVM(Dalvik VM),但到Android 5.0時ART就變成了默認模式,自此,DVM時代一去再也不復返。
DVM和ART和Android的預優化有什麼關係?先了解Java的幾種虛擬機基本的工做機制:android
(1)JVM:JVM虛擬機運行的是java字節碼。Java文件到JVM的過程是:java -> java bytecode(class) -> java bytecode(jar)。
(2)DVM:DVM虛擬機解析執行的dex字節碼。Java文件到DVM的過程是:java -> java bytecode(class) -> dalvik bytecode(dex)。
(3)ART:ART虛擬機執行本地機器碼。Java文件到ART的過程是:java -> java bytecode(class) -> dalvik bytecode(dex) -> optimized android runtime machine code(oat)。
在Android 4.4(包括)以前用的是DVM,Android 4.4以後用的是ART,從java文件到虛擬機執行代碼,ART比DVM多了oat的過程,ART所使用的AOT(Ahead-Of-Time)編譯,在應用首次安裝時,字節碼預編譯成機器碼存在本地,而DVM是典型的JIT(Just-In-Time),在此模式下,應用每次運行的時候,字節碼都須要即時編譯器轉換爲機器碼再執行,ART模式相對於DVM省去了解析字節碼的過程,佔用內存也相應減小,進而提升APP的運行效率,因此Andorid 5.0發佈時,Google號稱Android系統能夠跑在512 M的內存的機器上,ART發揮了舉足輕重的做用。緩存
2. ART 的運做方式服務器
ART 使用預先 (AOT) 編譯,而且從 Android 7.0(代號 Nougat,簡稱 N)開始結合使用 AOT、即時 (JIT) 編譯和配置文件引導型編譯。全部這些編譯模式的組合都可配置,咱們將在本部分中對此進行介紹。例如,Pixel 設備配置瞭如下編譯流程:app
最初安裝應用時不進行任何 AOT 編譯。應用前幾回運行時,系統會對其進行解譯,並對常常執行的方法進行 JIT 編譯。
當設備閒置和充電時,編譯守護進程會運行,以便根據在應用前幾回運行期間生成的配置文件對經常使用代碼進行 AOT 編譯。
下一次從新啓動應用時將會使用配置文件引導型代碼,並避免在運行時對已通過編譯的方法進行 JIT 編譯。在應用後續運行期間進行了 JIT 編譯的方法將會被添加到配置文件中,而後編譯守護進程將會對這些方法進行 AOT 編譯。
ART 包括一個編譯器(dex2oat 工具)和一個爲啓動 Zygote 而加載的運行時 (libart.so)。dex2oat 工具接受一個 APK 文件,並生成一個或多個編譯工件文件,而後運行時將會加載這些文件。文件的個數、擴展名和名稱會因版本而異,但在 Android O 版本中,將會生成如下文件:框架
.vdex:其中包含 APK 的未壓縮 DEX 代碼,另外還有一些旨在加快驗證速度的元數據。
.odex:其中包含 APK 中已通過 AOT 編譯的方法代碼。
.art (optional):其中包含 APK 中列出的某些字符串和類的 ART 內部表示,用於加快應用啓動速度。
ide
詳細參考:https://source.android.google.cn/devices/tech/dalvik/configure?hl=zh-cn
工具
3.Android 優化的過程
Zygote進程在啓動的過程當中,建立ART虛擬機。在ART中,打包在APK裏面的Dex字節碼是經過LLVM翻譯成本地機器指令的。LLVM是一個用來快速開發本身的編譯器的框架系統,在Dalvik運行時中,APK在安裝的時候,安裝服務PackageManagerService會經過守護進程installd調用一個工具 dexopt對打包在APK裏面包含有Dex字節碼的classes.dex進行優化,優化獲得的文件保存在/data/dalvik-cache目錄 中,而且以.odex爲後綴名,表示這是一個優化過的Dex文件。在ART運行時中,APK在安裝的時候,一樣安裝服務 PackageManager Service會經過守護進程installd調用另一個工具dex2oat對打包在APK裏面包含有Dex字節碼進翻譯。翻譯後獲得的是一個ELF格式的oat文件,這個oat文件一樣是以.odex後綴結束,而且也是保存在/data/dalvik-cache目錄中。測試
Android在首次啓動和首次安裝應用時,須要將字節碼翻譯成機器碼,這樣Android系統的啓動速度將會大大減慢,若是沒有預優化,APP的運行速度也會加上翻譯所須要的時間。因此,這個翻譯的工做須要轉移到編譯上面來,也就是所,在編譯APK文件時,將會預先對APK進行翻譯的優化,而後再打包到系統裏面去,這樣Android系統在首次啓動時,就再也不須要花費大量的時間去翻譯APK的字節碼。優化
4. Mstar 648 平臺配置ART優化
(1)device/mstar/almond/BoardConfig.mk 中配置:
WITH_DEXPREOPT := true
看一下 WITH_DEXPREOPT 控制流程:build/core/dex_preopt_odex_install.mk
# Setting LOCAL_DEX_PREOPT based on WITH_DEXPREOPT, LOCAL_DEX_PREOPT, etc LOCAL_DEX_PREOPT := $(strip $(LOCAL_DEX_PREOPT)) ifneq (true,$(WITH_DEXPREOPT)) LOCAL_DEX_PREOPT := else # WITH_DEXPREOPT=true ifeq (,$(TARGET_BUILD_APPS)) # TARGET_BUILD_APPS empty ifndef LOCAL_DEX_PREOPT # LOCAL_DEX_PREOPT undefined ifneq ($(filter $(TARGET_OUT)/%,$(my_module_path)),) # Installed to system.img. ifeq (,$(LOCAL_APK_LIBRARIES)) # LOCAL_APK_LIBRARIES empty # If we have product-specific config for this module? ifeq (disable,$(DEXPREOPT.$(TARGET_PRODUCT).$(LOCAL_MODULE).CONFIG)) LOCAL_DEX_PREOPT := false else LOCAL_DEX_PREOPT := $(DEX_PREOPT_DEFAULT) endif else # LOCAL_APK_LIBRARIES not empty LOCAL_DEX_PREOPT := nostripping endif # LOCAL_APK_LIBRARIES not empty endif # Installed to system.img. endif # LOCAL_DEX_PREOPT undefined endif # TARGET_BUILD_APPS empty endif # WITH_DEXPREOPT=true
以上邏輯過濾各類條件,會執行LOCAL_DEX_PREOPT := $(DEX_PREOPT_DEFAULT)語句。
其中 LOCAL_DEX_PREOPT 的賦值 :nostripping - 優化但不剝離apk中的classes.dex true - 優化並剝離APK中的classes.dex false - 不進行優化。
(2)DEX_PREOPT_DEFAULT的定義在 device/mstar/almond/BoardConfigCommon.mk 中:
DEX_PREOPT_DEFAULT := true
LOCAL_DEX_PREOPT爲true時將會按照build/core/dex_preopt.mk文件相應的規則優化APK文件。
#################################### # dexpreopt support - typically used on user builds to run dexopt (for Dalvik) or dex2oat (for ART) ahead of time # #################################### # list of boot classpath jars for dexpreopt DEXPREOPT_BOOT_JARS := $(subst $(space),:,$(PRODUCT_BOOT_JARS)) DEXPREOPT_BOOT_JARS_MODULES := $(PRODUCT_BOOT_JARS) PRODUCT_BOOTCLASSPATH := $(subst $(space),:,$(foreach m,$(DEXPREOPT_BOOT_JARS_MODULES),/system/framework/$(m).jar)) PRODUCT_SYSTEM_SERVER_CLASSPATH := $(subst $(space),:,$(foreach m,$(PRODUCT_SYSTEM_SERVER_JARS),/system/framework/$(m).jar)) DEXPREOPT_BUILD_DIR := $(OUT_DIR) DEXPREOPT_PRODUCT_DIR_FULL_PATH := $(PRODUCT_OUT)/dex_bootjars DEXPREOPT_PRODUCT_DIR := $(patsubst $(DEXPREOPT_BUILD_DIR)/%,%,$(DEXPREOPT_PRODUCT_DIR_FULL_PATH)) DEXPREOPT_BOOT_JAR_DIR := system/framework DEXPREOPT_BOOT_JAR_DIR_FULL_PATH := $(DEXPREOPT_PRODUCT_DIR_FULL_PATH)/$(DEXPREOPT_BOOT_JAR_DIR) # The default value for LOCAL_DEX_PREOPT DEX_PREOPT_DEFAULT ?= true #若是以前沒有被賦值則=true # $(1): the .jar or .apk to remove classes.dex define dexpreopt-remove-classes.dex $(hide) zip --quiet --delete $(1) classes.dex; \ dex_index=2; \ while zip --quiet --delete $(1) classes$${dex_index}.dex > /dev/null; do \ let dex_index=dex_index+1; \ done endef ...
(3)若是想跳過指定的APK不進行優化,則在 build/core/dex_preopt_odex_install.mk 修改規則,如跳過 QuickApp.apk 不進行優化:
# Skip QuickPic DEOEX avoid "Didn't find class "com.alensw.PicFolder.QuickApp" ifeq ($(LOCAL_MODULE),QuickPic) LOCAL_DEX_PREOPT:= endif
(4) 若是編譯jar包不想進行優化,能夠修改 build/core/java_library.mk 中 LOCAL_DEX_PREOPT := false ,所有從新編譯後就能夠mm單獨編譯jar包進行調試。
(5) 若是發現 LOCAL_DEX_PREOPT:=true時 apk中的.dex沒法剔除出來,須要關注另外一個參數 LOCAL_CERTIFICATE,做用是指定用於哪一個key簽名,未指定的默認用testkey:
LOCAL_CERTIFICATE := testkey #普通APK,默認狀況下使用。 LOCAL_CERTIFICATE := platform #該APK完成一些系統的核心功能,通過對系統中存在的文件夾的訪問測試,這種方式編譯出來的APK所在進程的UID爲system。 LOCAL_CERTIFICATE := shared #該APK須要和home/contacts進程共享數據。 LOCAL_CERTIFICATE := media #該APK是media/download系統中的一環。
LOCAL_CERTIFICATE := PRESIGNED #表示apk已經簽過名了,系統不須要再次簽名,爲了保持第三方的簽名有效,不會剝離.dex文件。
5.項目中執行的效果
優化前:
優化後:
對比可發現,沒有預優化的apk文件有個classes.dex文件,而預優化的沒有這個文件了,java代碼(classes.dex)到了oat子目錄中。所以,在user環境下編譯,生成的apk文件是不能直接上傳到服務器的,由於這個apk已經沒有java的代碼了。因此在編譯apk時,把mk文件中添加LOCAL_DEX_PREOPT := false把預編譯關閉。
若是在user下編譯的apk,push到daily的系統中,apk依然可以運行,不是沒有classes.dex文件了嗎?java代碼都沒有了,爲何還能運行?在第三節可知道,在android系統啓動是會優化系統的app,因此daily中的APK的java代碼被緩存到手機本地了,push user下編譯的apk運行時依然運行的是daily版中apk緩存下來的code。