《Android内核剖析》读书笔记 第18章 Android编译系统
- Android编译系统的核心仍然是第1章末尾介绍的Make,基于此建立了一个适合于Android自身的编译框架,该编译框架由各种.mk文件、shell脚本、python脚本共同组成;通过该框架各个系统模块可以单独编译或打包、也可以根据一定的规则进行整合打包输出;
框架的核心设计理论有如下几点:- 建立在Make之上,定义好各类target;并适当引入python、shell脚本;
- 基于COC(规则胜于配置)思想强制每个子项目使用Android.mk文件描述,为了降低该文件的编写难度,编译系统默认定义好了相关变量以及各种通用函数库;
- 利用python脚本自动扫描所有包括Android.mk文件的子目录,并将文件中的设置内容转换为Make命令;
- 整个编译系统的源码在./build/目录下,绝大部分都是.mk文件,理解该编译系统的本质实际上就是分析这些脚本文件之间的关系;
整个编译系统由编译中枢、子项目、输出路径三部分组成,参见下图:
- 编译中枢:包括各种.mk文件,这些文件将遍历系统所有的子项目,并生成相对应的target,从而当执行make时能够知道应该执行什么具体的编译或打包命令;
- 子项目:就是整个Android源码系统中的各个功能模块,每个功能模块的目录中必须包含一个Android.mk文件,这是基于COC的设计,不允许自定义文件名。该文件中描述的就是包括哪些源文件,该项目的输出目标是什么类型,比如是Jar包、apk包、so包等等;在实际编写该文件的过程中,编译中枢已经定义好很多常用的变量,对这些变量进行赋值即可;
- 输出路径:编译过程中产生的各种临时文件、以及最终目标文件;系统默认定义为out目录,下面又分2个子目录host/target;
- out/host/:对应的是在PC上进行编译过程中所需要的各种工具和中间产出,利用这些产出才能编译出最终所需的目标程序;
- out/target/:对应的是可用于手机上部署的最终输出文件,比如各类img文件;
- 各编译脚本之间的关系
- 编译时执行make命令会查找android源码顶级目录中的MakeFile文件,该文件直接跳转至./build/core/main.mk;
- main.mk就是整个编译中枢的开始,使用.PHONY定义了默认的target名称为droid,这样当执行make命令时是可以不用任何参数的;
- config.mk:核心是定义各类常用的变量、以及编译命令宏;里面具体会依赖于pathmap.mk/executable.mk/buildspec.mk/envsetup.mk/product_config.mk等;
- definitions.mk:定义编译过程中需要用到的各种脚本函数,比如all-java-files-under就是返回指定目录下所有的java文件;
- ./build/tools/findleaves.py:用来扫描指定目录下所有的Android.mk文件,可以用参数排除掉某些不必要的目录;
扫描出来的结果其实就是上面提到的子项目列表;
- product/variant/tag这三者之间的关系
- product:产品的名称,可简单理解为我们的编译目标代号,体现在源码编译目标值-分割后的第一个参数,比如lunch full-eng中的full;
他的值可以是任意的,但必须在编译打包脚本中定义,本质上他就是一组信息组合,不同的产品可以包括不同的子项目,比如我们可以自己新建一个product,只包括我们自己需要的几个子项目;但也可以不强制指定,而完全通过variant子类型来自动判断;
一个product中具体包括哪些子项目可以查看对应mk文件设置的PRODUCT_PACKAGES/PRODUCT_COPY_FILES两个变量值;
相关的脚本可参见./build/target/product/目录下的mk文件;比如常见的full.mk中未指定PRODUCT_PACKAGES变量值,而sdk.mk中就指定了该变量值,明确指出包括哪些子项目; - variant:产品子类型,与product搭配使用,是一组系统定义好的默认值,包括eng/user/userdebug/tests,体现在源码编译目标值-分割后的第二个参数,比如lunch full-eng中的eng,eng代表的意思是engineer;
设置variant参数有何作用呢,他与接下来要介绍的tag有着密切的mapping关系; - tag:每个子项目为自己设置的标签,可以在子项目下的Android.mk文件中的LOCAL_MODULE_TAGS变量设置,包括debug/eng/tests/optional/samples/shell_ash/shell_mksh,不能设置为其他值,系统编译时会在base_rules.mk中进行校验,校验不通过的话会出现警告,并不会将该子项目添加到任何product中;
从上可以看出variant和tag在很多可选值上面都是相同的,的确他们之间是有mapping关系的,也就是说当编译命令中指定variant后,具体执行过程中脚本会根据variant的值与每个子项目中设置的tag值进行比对,若匹配则编译该子项目并加入到product的输出路径中,否则不进行编译;
注意:tag和variant不是完全一一对应的,比如:- optional代表的是可选,即他不是android系统运行所必须的,但只要在product中PRODUCT_PACKAGES变量包括了该子项目,那该子项目就会被编译并添加到product中;
- samples代表的是实例性项目,多为独立子项目,一般无需添加到product中;
./development/samples/目录下所有的子项目都是该类型的; - shell_ash只有在system/core/sh/子项目中用到;
- shell_mksh只有在external/mksh/子项目中用到;
- product:产品的名称,可简单理解为我们的编译目标代号,体现在源码编译目标值-分割后的第一个参数,比如lunch full-eng中的full;
- Android标准应用apk包编译全过程,参见下图,理解这些原理对自己写批量打包脚本时非常重要:
- Framework/android.jar的编译(书中这段的描述个人觉得条理不太清晰,部分描述也不准确)
- 首先我们直接看sdk中的android.jar包中核心包括如下几个顶级package:
- java/javax:主要是Java语言的基础类库,比如lang/io/sql/util/xml/net等等;
- dalvik:里面类不多,主要包括dalvik虚拟机特有的DexClassLoader以及相关ByteCode类;
- android:大家都超级熟悉了吧,View/Activity/Handler/...都在这里了;
- asset/res:一些系统功能中会用到的各种资源文件;
- org/com:主要是一些通用性很强的第三方包,比如httpclient、json等;
- 以上的内容可以分成3类,分别来源于不同的jar包,包括:
- core.jar:Dalvik虚拟机运行所需要的基础类包,其功能主要是完成dex文件到标准class文件的转换与加载,在其之上可以正常运行标准java应用;对应于android.jar中java/javax/dalvik/这3个顶级package;
源码位于./libcore/;编译目标在./libcore/Android.mk中定义;编译完成后的输出位于./out/target/common/obj/JAVA_LIBRARIES/core_intermediates - framework.jar:基于Dalvik虚拟机之上用于开发真正android手机应用的框架包,比如视图的绘制、管理,页面的跳转与管理等;对应于android.jar中android/assets/res/这3个顶级package;
源码位于./frameworks/base;编译目标在./frameworks/base/Android.mk中定义;编译完成后的输出位于./out/target/common/obj/JAVA_LIBRARIES/framework_intermediates - ext.jar:扩展类库,集成一些通用性较高、已经被业界广泛接受并使用的工具包;对应于android.jar中org/com/这2个顶级package;
源码位于./external/;编译目标在./frameworks/base/Android.mk中定义;编译完成后的输出位于./out/target/common/obj/JAVA_LIBRARIES/ext_intermediates
- core.jar:Dalvik虚拟机运行所需要的基础类包,其功能主要是完成dex文件到标准class文件的转换与加载,在其之上可以正常运行标准java应用;对应于android.jar中java/javax/dalvik/这3个顶级package;
- 但是否core.jar/framework.jar/ext.jar中的所有内容都放在android.jar中呢?答案是否定的;原因是为了保持应用开发api的稳定性和向后兼容,google把源文件分为了“公有”和“私有”两类,明显私有的一些源文件代表稳定性不够未来很可能会变更,所以这部分不稳定源码是不会放进android.jar中的,经常看源码的同学会经常看到@hide这个标签,这也是一种私有表现;
android.jar编译完成后的输出位于./out/target/common/obj/JAVA_LIBRARIES/sdk_v${version}_intermediates
其中${version}代表的是SDK的具体版本号;
- 首先我们直接看sdk中的android.jar包中核心包括如下几个顶级package: