技术标签: Android-framework android编译原 apk生成原理 apk编译原理 aapt 编译系统
本文基于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: [email protected]
$(jack-java-to-dex)
rule8 规则
@build/core/package_internal.apk
@echo "target Package: $(PRIVATE_MODULE) ([email protected])"
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),[email protected]),
$(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),[email protected]),\
$(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,[email protected]))
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 [email protected] $(dir [email protected])package.dex.apk
endif # BUILD_PLATFORM_ZIP
ifneq (nostripping,$(LOCAL_DEX_PREOPT))
$(call dexpreopt-remove-classes.dex,[email protected])
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 [email protected] ]; then \
echo 'No [email protected] 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: [email protected]"
$(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。
问题环境是springboot2.1.8+flyway6.0.4,尝试编译出错,异常堆栈的信息是找不到flywaycallback类解决过程1、flywaycallback找不到,导致org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration$FlywayConfiguration这个bean创建失败...
lua语言介绍 Lua[1] 是一个小巧的脚本语言。是巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro)里的一个研究小组,由Roberto Ierusalimschy、Waldemar Celes 和 Luiz Henrique de Figueiredo所组成并于1993年开发。 其设计目的是为了嵌入
砍价小程序日渐火爆,被越来越多的商家所利用以此来获得更多的用户和产品的盈利。如此好的优质的营销工具,要如何去经营呢?接下来就给大家介绍一下关于微信砍价小程序运营新玩法?感兴趣的可以和木鱼小铺(https://www.muyu007.cn)一起来看看。 1.内容营销预热,推广店铺 微信公众号是小程序推广引流的方式之一,主要是内容营销。所以商家可以结合微信公众号的文章内容来为小程序商城砍价活动进行预热,让用户可以通过内容来了解到线下店铺的环境、商品以及购买的优惠力度,吸引客户进入
python-matplotlib折线图及柱状图绘制一、折线图绘制二、柱状图绘制一、折线图绘制# 折线图: matplotlib.plot(横坐标,纵坐标,颜色,lw=线条宽度)# 1.导入numpy,matplotlib模块import numpy as npimport matplotlib.pyplot as plt# 2.定义各个点的横纵坐标x = np.array([1,2,3,4,5,6,7,8])y = np.array([3,5,9,2,7,6,3,8])# 3.将参数传入
1、创建表create table(id int,name char(10))2、删除表中数据:delete tablename; 直接删除 数据库 数据、结构、日志 drop database; 删除表 drop tablename3、把表数据插入到另一个表 3.1:如果要插入目标表不存在: select * into 目标表 from 表 where … 3....
锁相环是最常用的IP核之一,其性能强大,可以对输入到FPGA的时钟信号进行任意分频、倍频、相位调整、占空比调整,从而输出一个期望时钟。其基本原理如下:
package com.bandweaver.tunnel.common.platform.util;import java.math.BigDecimal;import javafx.geometry.Point2D;public class GPSUtil { private static final double EARTH_RADIUS = 6378137;// 赤道半...
部分基础篇章Python语言基础/21.0引子/21.1工欲善其事,必先利其器(安装Python)/31.2学跑得先学走(语法基础)/91.3程序结构/111.3.1HelloWorld!/111.3.2运算符介绍/121.3.3顺序结构/141.3.4判断结构/171.3.5循环结构/181.3.6异常/201.4函数/241.4.1基本函数结构/241.4.2参数结构/251.4.3回调函数/...
很多深圳大学新生报到之前都有很多疑问,其中一个疑问就是深圳大学有几个校区,大一新生在哪个校区。本文主要为大家介绍关于2020年深圳大学的介绍,深圳大学招生各个专业目录、深圳大学新生在哪个校区的知识。一、深圳大学校区介绍深圳大学现有粤海、沧海、丽湖、罗湖四个校区,校园总面积2.72平方公里。学校教学资源丰富,科研设施齐备。校园总建筑面积146.9万平方米,教学、科研仪器设备总值28亿元。图书馆馆舍5...
I tried to find this bug, but don't know how to solve it.I kept getting error message "The SECRET_KEY setting must not be empty." when executing populate_rango.pyI have checked on settings.py and the ...
md5sum命令采用MD5报文摘要算法(128位)计算和检查文件的校验和。MD5算法常常被用来验证网络文件传输的完整性,防止文件被人篡改。MD5全称是报文摘要算法(Message-Digest Algorithm 5),此算法对任意长度的信息逐位进行计算,产生一个二进制长度为128位(十六进制长度就是32位)的“指纹”(或称“报文摘要”),不同的文件产生相 同的报文摘要的可能性是非常非常之小的。
https://www.jb51.net/html5/586989.htmlhttps://segmentfault.com/a/1190000011973904Fetch概念fetch身为H5中的一个新对象,他的诞生,是为了取代ajax的存在而出现,主要目的仅仅只是为了结合ServiceWorkers,来达到以下优化:优化离线体验 保持可扩展性当然如果ServiceWorke...