不曾熟悉过的odex(编译过程)

谈到odex,之前对这个的了解:

1、编译的时候针对user版本会对apk进行处理,将里面的class.dex文件拿出来单独处理为odex,apk文件中只留下一些资源文件

2、第一次开机耗时会比较长,中间有大量的dex2oat的log存在,也是针对每个APK在做dex优化

了解比较粗浅和模糊,没有从编译和代码层面进行过研究。从中对虚拟机相关的这块知识也有所欠缺。略去其他博客中提到的dex和odex相关的科普的知识,主要针对android系统中涉及到跟这方面相关的应用场景来分析。

一、编译篇

Android的编译系统本身就很庞大,弄清楚就很麻烦和吃力。所以这里直接选取一个其中的Makefile文件开始研究,避免其他编译系统知识对这个的影响,避免陷进去。

这里我选取的文件是/build/core/dex_preopt.mk,一个89行的mk文件在编译系统里已经算是很精简了,可以做到一行一行来解读了。读完这个mk文件就基本能印证我提到的第一个粗浅认识了。

1####################################
2# dexpreopt support - typically used on user builds to run dexopt (for Dalvik) or dex2oat (for ART) ahead of time
3#
4####################################
56# list of boot classpath jars for dexpreopt
7DEXPREOPT_BOOT_JARS := $(subst $(space),:,$(PRODUCT_BOOT_JARS))
8DEXPREOPT_BOOT_JARS_MODULES := $(PRODUCT_BOOT_JARS)
9PRODUCT_BOOTCLASSPATH := $(subst $(space),:,$(foreach m,$(DEXPREOPT_BOOT_JARS_MODULES),/system/framework/$(m).jar))
1011PRODUCT_SYSTEM_SERVER_CLASSPATH := $(subst $(space),:,$(foreach m,$(PRODUCT_SYSTEM_SERVER_JARS),/system/framework/$(m).jar))
1213DEXPREOPT_BUILD_DIR := $(OUT_DIR)
14DEXPREOPT_PRODUCT_DIR_FULL_PATH := $(PRODUCT_OUT)/dex_bootjars15DEXPREOPT_PRODUCT_DIR := $(patsubst $(DEXPREOPT_BUILD_DIR)/%,%,$(DEXPREOPT_PRODUCT_DIR_FULL_PATH))
16DEXPREOPT_BOOT_JAR_DIR := system/framework17DEXPREOPT_BOOT_JAR_DIR_FULL_PATH := $(DEXPREOPT_PRODUCT_DIR_FULL_PATH)/$(DEXPREOPT_BOOT_JAR_DIR)
1819# The default value for LOCAL_DEX_PREOPT
20DEX_PREOPT_DEFAULT ?= true2122# The default filter for which files go into the system_other image (if it is
23# being used). To bundle everything one should set this to '%'
24SYSTEM_OTHER_ODEX_FILTER ?= app/% priv-app/%
2526# The default values for pre-opting: always preopt PIC.
27# Conditional to building on linux, as dex2oat currently does not work on darwin.
28ifeq ($(HOST_OS),linux)
29WITH_DEXPREOPT_PIC ?= true30WITH_DEXPREOPT ?= true31# For an eng build only pre-opt the boot image. This gives reasonable performance and still
32# allows a simple workflow: building in frameworks/base and syncing.
33ifeq (eng,$(TARGET_BUILD_VARIANT))
34WITH_DEXPREOPT_BOOT_IMG_ONLY ?= true35endif36# Add mini-debug-info to the boot classpath unless explicitly asked not to.
37ifneq (false,$(WITH_DEXPREOPT_DEBUG_INFO))
38PRODUCT_DEX_PREOPT_BOOT_FLAGS += --generate-mini-debug-info39endif40endif4142GLOBAL_DEXPREOPT_FLAGS :=
43ifeq ($(WITH_DEXPREOPT_PIC),true)
44# Compile boot.oat as position-independent code if WITH_DEXPREOPT_PIC=true
45GLOBAL_DEXPREOPT_FLAGS += --compile-pic46endif4748# $(1): the .jar or .apk to remove classes.dex49definedexpreopt-remove-classes.dex50$(hide) zip --quiet --delete $(1) classes.dex; \
51dex_index=2; \
52whilezip --quiet --delete $(1) classes$${dex_index}.dex > /dev/null; do \
53letdex_index=dex_index+1; \
54done55endef5657# Special rules for building stripped boot jars that override java_library.mk rules
5859# $(1): boot jar module name
60define_dexpreopt-boot-jar-remove-classes.dex61_dbj_jar_no_dex := $(DEXPREOPT_BOOT_JAR_DIR_FULL_PATH)/$(1)_nodex.jar62_dbj_src_jar := $(callintermediates-dir-for,JAVA_LIBRARIES,$(1),,COMMON)/javalib.jar6364$$(_dbj_jar_no_dex) : $$(_dbj_src_jar) | $(ACP)
65	$$(callcopy-file-to-target)
66ifneq ($(DEX_PREOPT_DEFAULT),nostripping)
67	$$(calldexpreopt-remove-classes.dex,$$@)
68endif6970_dbj_jar_no_dex :=
71_dbj_src_jar :=
72endef7374$(foreach b,$(DEXPREOPT_BOOT_JARS_MODULES),$(eval $(call_dexpreopt-boot-jar-remove-classes.dex,$(b))))
7576include $(BUILD_SYSTEM)/dex_preopt_libart.mk7778# Define dexpreopt-one-file based on current default runtime.
79# $(1): the input .jar or .apk file
80# $(2): the output .odex file
81definedexpreopt-one-file82$(calldex2oat-one-file,$(1),$(2))
83endef8485DEXPREOPT_ONE_FILE_DEPENDENCY_TOOLS := $(DEX2OAT_DEPENDENCY)
86DEXPREOPT_ONE_FILE_DEPENDENCY_BUILT_BOOT_PREOPT := $(DEFAULT_DEX_PREOPT_BUILT_IMAGE_FILENAME)
87ifdefTARGET_2ND_ARCH88$(TARGET_2ND_ARCH_VAR_PREFIX)DEXPREOPT_ONE_FILE_DEPENDENCY_BUILT_BOOT_PREOPT := $($(TARGET_2ND_ARCH_VAR_PREFIX)DEFAULT_DEX_PREOPT_BUILT_IMAGE_FILENAME)
89endif  # TARGET_2ND_ARCH
90

第1到11行:

开头的注释也说明了这个主要是针对user版本编译,提前使用dexopt(针对dalvik虚拟机做dex2odex)和dex2oat(针对art虚拟机做dexoat)对apk和jar包做优化。

DEXPREOPT_BOOT_JARS := $(subst $(space),:,$(PRODUCT_BOOT_JARS))

将PRODUCT_BOOT_JARS中的空格替换为:

boot jars应该是指开机就会加载的jar包,zygote在做preload的时候就会将这些包中的class load进去,这些jar的定义在/build/target/product/core_minimal.mk

89PRODUCT_BOOT_JARS := \
90core-oj \
91core-libart \
92conscrypt \
93okhttp \
94core-junit \
95bouncycastle \
96ext \
97framework \
98telephony-common \
99voip-common \
100ims-common \
101apache-xml \
102org.apache.http.legacy.boot

这些jar包都是在机器的system/framework下的jar包,DEXPREOPT_BOOT_JARS就变为:

core-oj:core-libart:conscrypt:…

PRODUCT_BOOTCLASSPATH := $(subst $(space),:,$(foreach m,$(DEXPREOPT_BOOT_JARS_MODULES),/system/framework/$(m).jar))

得到上面的jar包在image包中的路径

PRODUCT_BOOTCLASSPATH为/system/framework/core-oj.jar:/system/framework/core-libart.jar:….

PRODUCT_SYSTEM_SERVER_CLASSPATH := $(subst $(space),:,$(foreach m,$(PRODUCT_SYSTEM_SERVER_JARS),/system/framework/$(m).jar))

这个是系统中承载service的jar包路径 /system/framework/services.jar:/system/framework/ethernet-service.jar:/system/framework/wifi-service.jar

13到17行

设定了一些文件夹路径

DEXPREOPT_BUILD_DIR := out
DEXPREOPT_PRODUCT_DIR_FULL_PATH := out/target/product/xxx/dex_bootjars
DEXPREOPT_PRODUCT_DIR :=target/product/xxx/dex_bootjars
DEXPREOPT_BOOT_JAR_DIR := system/framework
DEXPREOPT_BOOT_JAR_DIR_FULL_PATH := out/target/product/xxx/dex_bootjars/system/framework

在编译出来结果的时候可以去最后一个文件夹路径去看看里面有什么东东

19到24行

默认DEX_PREOPT_DEFAULT为true

SYSTEM_OTHER_ODEX_FILTER默认为app/和priv-app/,还会进入到system分区的两个文件夹

26到40行

针对编译主机是linux的情况,给下面两个变量默认为true

WITH_DEXPREOPT_PIC ?= true
WITH_DEXPREOPT ?= true

如果编译的是eng版本,也就是在lunch的时候得到的TARGET_BUILD_VARIANT是eng的话,就默认变量WITH_DEXPREOPT_BOOT_IMG_ONLY为true

42到46行

暂时略过

48到55行

定义的一个makefile函数:dexpreopt-remove-classes.dex注释中也很明白,去掉jar包和apk包中的classes.dex文件。这里有个dex_index,主要是针对有些apk项目过大,需要做dex分包的情况,将所有的dex全部移除掉

57行到72行

定义函数_dexpreopt-boot-jar-remove-classes.dex

入参是boot jar的module名,例如module就是framework

那么_dbj_jar_no_dex为out/target/product/xxx/dex_bootjars/system/framework/framework_nodex.jar

这里还涉及到一个makefile函数 intermediatesdir-for,计算编译中间文件夹

_dbj_src_jar为这些jar包在out下的中间编译目录下的javalib.jar文件

然后将这些javalib.jar文件拷贝到out/target/product/xxx/dex_bootjars/system/framework/xxx_nodex.jar

只要DEX_PREOPT_DEFAULT不为nostripping的话都会调用签名定义的函数,将此时这些jar包中的dex都抽离出来,使其名副其实的成为nodex.jar

74行

对所有的boot jar从中间文件夹中拷出来然后,抽离dex

/*****************************************************************/

到此,这些boot jar包的空壳子都已经就位了,但是真正的dex们都没了还玩毛线啊

接下来肯定会把这些jar包中的所有dex再从中间目录中抽出来,整合为一个大的oat格式的文件

/*****************************************************************/

76行

include了一个另外的mk文件/build/core/dex_preopt_libart.mk其内部还会include一个文件dex_preopt_libart_boot.mk,完成的就是将各个jar包的dex合成一个oat包。分析类似于这里,读者可自行分析

78行到83行

定义dexpreopt-one-file优化一个jar包或者apk文件

85到89行

关于64/32位编译相关,暂时不考虑

到此就了解针对所有版本的image中的对boot jar进行odex化的操作基本原理,后续会分析针对apk的odex的预先编译优化和开机自动优化两种情况,以及apk启动时的相关加载方面的原理知识。

    原文作者:Kelvin wu
    原文地址: https://zhuanlan.zhihu.com/p/25633916
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞