本文基于AOSP-7.1.1-R9源码分析,源码可以参见build/+/android-7.1.1_r9。
在Android系统中,所有的应用都是以apk的形式存在,那这个apk是如何生成的呢?对于刚开始接触系统开发的开发者来说,经常会使用如下命令编译apk或者系统固件。
source build/envsetup.sh;
lunch
make -j8
or
mmm packages/app/Settings
这些命令背后的原理是什么呢?最终为什么会生成一个apk
文件?接下来的文章将会对相关原理进行剖析。
本文使用AOSP frameworks/base/tests/Split/
的源码来进行解析。由于关注点在apk
的生成原理,所以重点分析和apk
相关的编译原理,对于生成系统固件的原理不是本文讨论的重点,当了解了apk
的生成原理之后,其他的编译原理也就好理解了。
接下来我们将分析Android编译系统里面用来生成apk
的原理。大致会分为如下几个步骤:
step1. 通过source 命令,读入envsetup.sh里面定义的各种命令,比如mm、mma、mmm、godir、croot等,方便我们在当前终端进行相关的命令输入。
step2.lunch
将要编译的产品,生成产品相关的参数配置。本文不做深入讲解。
source build/envsetup.sh;
lunch
step3. 开始执行编译命令,生成目标的依赖关系。
mmm frameworks/base/tests/Split
查看build/envsetup.sh
的源码,可以看到,当我们执行如上命令的时候,实际上执行的是如下的命令:
ONE_SHOT_MAKEFILE= frameworks/base/tests/Split/Android.mk make -C /home/tanfuhai/data/code/opengrok/android_n -f build/core/main.mk all_modules
接下来就直接进入到Android编译系统,可以看到入口makefile
文件是main.mk
,下面对引用的makefile文件一一讲解:
main.mk
开始,Android默认的make target
是droid
。droid
依赖于droid_targets
,Makefile里面定义了各种依赖,当执行make的时候,可以编译出我们需要的所有image,droid
的依赖关系如下图: .PHONY: droid
DEFAULT_GOAL := droid
$(DEFAULT_GOAL): droid_targets
.PHONY: droid_targets
droid_targets:
droid_targets: apps_only
droid_targets: droidcore dist_files
本文主要讲解apk
生成的原理,当我们执行mmm
的时候,此时的target是all_modules
。make的过程中,首先会读取所有的makefile文件。由于我们传入了ONE_SHOT_MAKEFILE= frameworks/base/tests/Split/Android.mk
,所以此时会include这个ONE_SHOT_MAKEFILE
。all_modules
的依赖定义如下:
# phony target that include any targets in $(ALL_MODULES)
.PHONY: all_modules
ifndef BUILD_MODULES_IN_PATHS
all_modules: $(ALL_MODULES)
else
引入 ONE_SHOT_MAKEFILE文件,代码如下:
ifneq ($(ONE_SHOT_MAKEFILE),)
# We've probably been invoked by the "mm" shell function
# with a subdirectory's makefile.
include $(ONE_SHOT_MAKEFILE)
由于Android.mk
里面定义了include $(BUILD_PACKAGE)
,在引用BUILD_PACKAGE
对应的makefile
时,会依次引用到如下的makefile
文件。
include $(BUILD_PACKAGE)
,入口。Android.mk
里面定义的LOCAL_32_BIT_ONLY
或者LOCAL_MULTILIB
的值,并且赋值给my_module_multilib
,,方便下面的makefile使用。build/core/package_internal.mk 编译apk定义的规则。会收集我们在Android.mk里面定义的各种变量。比如当前的模块名、资源文件路径等。这个mk
定义了很多生成apk
的规则和依赖。
LOCAL_PACKAGE_OVERRIDES
指定覆盖其他的包,比如系统现在同时编译了Launcher1和Launcher2,我们只需要Launcher2,那么我们可以在Launcher2的Android.mk里面定义LOCAL_PACKAGE_OVERRIDES := Launcher1。LOCAL_PACKAGE_NAME
最后生成的apk的名字.LOCAL_MODULE_SUFFIX
模块的后缀,默认是apk。LOCAL_MODULE
模块名,和LOCAL_PACKAGE_NAME一样,只能定义一个,比如Split
。LOCAL_MODULE_CLASS
模块类型,默认是APPSLOCAL_ASSET_DIR
asset目录,默认是assetsLOCAL_RESOURCE_DIR
资源文件目录,默认是resPRODUCT_PACKAGE_OVERLAYS
DEVICE_PACKAGE_OVERLAYS
资源覆盖目录,资源覆盖机制,DEVICE_PACKAGE_OVERLAYS指定的优先级低。LOCAL_PROGUARD_ENABLED
代码混淆是不是生效。LOCAL_CERTIFICATE
签名文件,产品自己用PRODUCT_DEFAULT_DEV_CERTIFICATE指定,如果也没有指定,默认build/target/product/security/testkey。比如我们在设置模块指定了LOCAL_CERTIFICATE := platform
,那么最终使用的签名文件就是build/target/product/security/下面的platform.x509.pem和platform.pk8,同理如果LOCAL_CERTIFICATE := media
,那么就用media.x509.pem和media.pk8。LOCAL_PACKAGE_SPLITS
是否定义了apk分包。并且定义分包的相关规则。LOCAL_BUILT_MODULE
定义编译apk的所有文件依赖关系。比如aapt
编译生成package.apk、R.stamp、签名依赖,可以说编译apk的整个依赖完全是由这个宏定义生成。 build/core/configure_local_jack.mk 定义是否用jack编译,在Android O上,jack已经被弃用。如果定义了,定义jack编译的规则。
build/core/android_manifest.mk 主要做两件事情。
LOCAL_MANIFEST_FILE
宏的值,默认是AndroidManifest.xml
文件,应用可以通过LOCAL_FULL_MANIFEST_FILE
指定自己的AndroidManifest.xml
文件。LOCAL_STATIC_JAVA_AAR_LIBRARIES
指定引用的aar包,和jar相比,aar里面包含有相关的资源。build/core/java.mk
built_dex_intermediate
的依赖关系。build/core/base_rules.mk 主要作用:
LOCAL_MODULE
,LOCAL_MODULE_TAGS
、LOCAL_MODULE_CLASS
是否唯一、判断当前模块是否有init.rc文件。ALL_MODULES LOCAL_BUILT_MODULE LOCAL_INSTALLED_MODULE
的依赖。展开build/core/base_rules.mk
之后,我们前面在main.mk
里面的依赖就和对应的模块名Split
联系起来了,相当于all_modules
依赖于Split
。Split
最终依赖于LOCAL_BUILT_MODULE
和LOCAL_INSTALLED_MODULE
。依赖规则如下: ALL_MODULES += $(my_register_name)
ifdef OVERRIDE_BUILT_MODULE_PATH
ifneq ($(LOCAL_MODULE_CLASS),SHARED_LIBRARIES)
$(error $(LOCAL_PATH): Illegal use of OVERRIDE_BUILT_MODULE_PATH)
endif
built_module_path := $(OVERRIDE_BUILT_MODULE_PATH)
else
built_module_path := $(intermediates)
endif
LOCAL_BUILT_MODULE := $(built_module_path)/$(my_built_module_stem)
built_module_path
: 最终为out/target/product/generic_x86_64/obj/APPS/Split_intermediatesmy_built_module_stem
: 默认是 package.apkmy_module_path
: 等于out/target/product/generic_x86_64/data/app/Splitmy_installed_module_stem
: Split.apk$(LOCAL_BUILT_MODULE)
: out/target/product/generic_x86_64/obj/APPS/Split_intermediates/package.apk$(LOCAL_INSTALLED_MODULE)
: out/target/product/generic_x86_64/data/app/Split/Split.apk,build/core/configure_module_stem.mk 获取编译模块的中间件名字。
my_module_stem
,如果没有定义LOCAL_MODULE_STEM,那么就是模块名,比如Split
。my_built_module_stem
对于apk来说通常都是package.apk。my_installed_module_stem
最终生成的apk名字,比如 Split.apkbuild/core/java_common.mk
LOCAL_JAVA_RESOURCE_DIRS
指定的资源目录下的文件,收集LOCAL_JAVA_RESOURCE_FILES
指定的资源文件。$(LOCAL_INTERMEDIATE_TARGETS): PRIVATE_ASSET_DIR
。PRIVATE_STATIC_JACK_LIBRARIES
等。build/core/dex_preopt_odex_install.mk 定义安装odex的一些规则。比如是否要进行odex优化,一般产品会自己定义WITH_DEXPREOPT = true
。
build/core/install_jni_libs.mk 定义如何安装apk需要的jn库。
经过以上的步骤,生成目标Split
的依赖就清晰了,如下图所示:
我们可以看到,有如下规则:
aapt
工具把所有的资源文件和AndroidManifes.xml
文件一起编译生成out/target/common/obj/APPS/Split_intermediates/src/R.stamp
rule12 jack
工具会把当前的java
文件,以及依赖的相关系统库文件,比如out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.dex.toc
一起编译生成out/target/common/obj/APPS/Split_intermediates/with-local/classes.dex
rule9 zip
命令打包out/target/common/obj/APPS/Split_intermediates/with-local/classes.dex
和aapt
生成的资源文件out/target/product/generic_x86_64/obj/APPS/Split_intermediates/package.apk
生成新的out/target/product/generic_x86_64/obj/APPS/Split_intermediates/package.apk
,然后通过签名工具进行签名。
acp
命令把out/target/product/generic_x86_64/obj/APPS/Split_intermediates/package.apk
拷贝到out/target/product/generic_x86_64/data/app/Split/Split.apk
至此apk就生成出来了。附rule规则对应的代码:
rule12 规则
$(built_dex_intermediate): $(jack_all_deps) | setup-jack-server
@echo Building with Jack: $@
$(jack-java-to-dex)
rule8 规则
@build/core/package_internal.apk
@echo "target Package: $(PRIVATE_MODULE) ($@)"
ifdef LOCAL_USE_AAPT2
ifdef LOCAL_JACK_ENABLED
$(call copy-file-to-new-target)
else
@# TODO: implement merge-two-packages.
$(if $(PRIVATE_SOURCE_ARCHIVE),\
$(call merge-two-packages,$(PRIVATE_RES_PACKAGE) $(PRIVATE_SOURCE_ARCHIVE),$@),
$(call copy-file-to-new-target))
endif
else # LOCAL_USE_AAPT2
ifdef LOCAL_JACK_ENABLED
$(create-empty-package)
else
$(if $(PRIVATE_SOURCE_ARCHIVE),\
$(call initialize-package-file,$(PRIVATE_SOURCE_ARCHIVE),$@),\
$(create-empty-package))
endif
$(add-assets-to-package)
endif # LOCAL_USE_AAPT2
ifneq ($(jni_shared_libraries),)
$(add-jni-shared-libs-to-package)
endif
ifeq ($(full_classes_jar),)
# We don't build jar, need to add the Java resources here.
$(if $(PRIVATE_EXTRA_JAR_ARGS),$(call add-java-resources-to,$@))
else # full_classes_jar
$(add-dex-to-package)
endif # full_classes_jar
ifdef LOCAL_JACK_ENABLED
$(add-carried-jack-resources)
endif
ifdef LOCAL_DEX_PREOPT
ifneq ($(BUILD_PLATFORM_ZIP),)
@# Keep a copy of apk with classes.dex unstripped
$(hide) cp -f $@ $(dir $@)package.dex.apk
endif # BUILD_PLATFORM_ZIP
ifneq (nostripping,$(LOCAL_DEX_PREOPT))
$(call dexpreopt-remove-classes.dex,$@)
endif
endif
$(sign-package)
rule15 17 19 21 规则
@build/core/package_internal.apk
## APK splits
ifdef LOCAL_PACKAGE_SPLITS
# LOCAL_PACKAGE_SPLITS is a list of resource labels.
# aapt will convert comma inside resource lable to underscore in the file names.
my_split_suffixes := $(subst $(comma),_,$(LOCAL_PACKAGE_SPLITS))
built_apk_splits := $(foreach s,$(my_split_suffixes),$(built_module_path)/package_$(s).apk)
installed_apk_splits := $(foreach s,$(my_split_suffixes),$(my_module_path)/$(LOCAL_MODULE)_$(s).apk)
# The splits should have been built in the same command building the base apk.
# This rule just runs signing.
# Note that we explicily check the existence of the split apk and remove the
# built base apk if the split apk isn't there.
# That way the build system will rerun the aapt after the user changes the splitting parameters.
$(built_apk_splits): PRIVATE_PRIVATE_KEY := $(private_key)
$(built_apk_splits): PRIVATE_CERTIFICATE := $(certificate)
$(built_apk_splits) : $(built_module_path)/%.apk : $(LOCAL_BUILT_MODULE)
$(hide) if [ ! -f $@ ]; then \
echo 'No $@ generated, check your apk splitting parameters.' 1>&2; \
rm $<; exit 1; \
fi
$(sign-package)
# Rules to install the splits
$(installed_apk_splits) : $(my_module_path)/$(LOCAL_MODULE)_%.apk : $(built_module_path)/package_%.apk | $(ACP)
@echo "Install: $@"
$(copy-file-to-new-target)
当我们编译frameworks/base/tests/Split/的时候,最终会生成out/target/product/generic_x86_64/data/app/Split/Split.apk
,从文件的角度来看,有如下的解析图。
package.apk
和分包文件,比如package_hdpi-v4.apk,然后通过签名工具进行签名。Split.apk
。apk
的编译原理就分析完成了,后面会写一篇文章讲解aapt是如何生成R.java和package.apk。