第2章 编译结构和各种构建
2.1 Android的系统构建
↘ 2.1.1 编译环境要求
Android的编译需要在Linux主机准备以下几个方面的支持。
● 目标机编译工具(交叉编译工具链)。
● 主机编译工具(x86主机的编译工具)。
● Java环境(包括Java编译和运行环境)。
● 脚本运行环境。
Android的源代码包中自带了主要的编译工具,也就是目标机的编译工具,路径通常为:prebuilt/linux-x86/toolchain/。
其中Linux-x86指的是Linux环境的x86主机环境(64位的机器对应目录为linux-x86_64),toolchain目录中的内容是GCC交叉编译工具链。由此,Android对目标机内容的编译,使用的就是这些工具,不需要使用主机自己安装的工具。
Android编译针对主机环境的要求有Linux主机和JDK两个方面,如表2-1所示。
表2-1 Android编译针对主机环境的要求
在Android系统的编译中,实际上只有Android 2.3及之后对JDK1.6的要求是强制性的,如果不具有JDK1.6环境,将无法成功编译一些Java代码。对于其他的限制,去掉编译中由错误引起的中断,在一定程度上可以绕过编译的限制。
↘ 2.1.2 构建流程
Android的构建流程从根目录下直接执行make命令来完成一个基本的编译。实际上这是从Android开源代码的根目录(TOP)中的Makefile文件开始,是Linux中使用make机制的标准流程。
Android系统编译流程的片断如下所示:
$ make ============================================ PLATFORM_VERSION_CODENAME=REL PLATFORM_VERSION=2.3.4 TARGET_PRODUCT=generic TARGET_BUILD_VARIANT=eng TARGET_SIMULATOR= TARGET_BUILD_TYPE=release TARGET_BUILD_APPS= TARGET_ARCH=arm HOST_ARCH=x86 HOST_OS=linux HOST_BUILD_TYPE=release BUILD_ID=GRJ22 ============================================ # ……编译过程 Finding NOTICE files: out/target/product/generic/obj/NOTICE_FILES/hash- timestamp Combining NOTICE files: out/target/product/generic/obj/NOTICE.html Target system fs image: out/target/product/generic/obj/PACKAGING/ systemimage_intermediates/system.img Install system fs image: out/target/product/generic/system.img Installed file list: out/target/product/generic/installed-files.txt
在编译开始的过程中,TARGET_PRODUCT是编译的主要环境变量。在开始编译的时候,TARGET_PRODUCT,TARGET_ARCH等内容是从环境变量得到的,如果需要更改它们的内容,可以直接使用export导出环境变量,但正规的方式是通过Android的编译系统进行配置。
Android TOP目录中的Makefile内容如下所示:
### DO NOT EDIT THIS FILE ### include build/core/main.mk ### DO NOT EDIT THIS FILE ###
TOP目录中的Makefile也就是build/core/root.mk。在repo sync的阶段,它被复制成TOP目录的Makefile。这是因为在Android的代码仓库管理文件.repo/manifest.xml中包含了如下的内容:
<project path="build" name="platform/build"> <copyfile src="core/root.mk" dest="Makefile" /> </project>
此时表示的就是将build/core/root.mk复制为TOP目录的Makefile。
根据这个Makefile的内容,实际上使用的编译文件是build/core/目录中的main.mk文件,其中的一个片断如下所示:
.PHONY: droid DEFAULT_GOAL := droid $(DEFAULT_GOAL):
这是基于Linux标准的Make机制,droid实际上就是默认情况的“Make目标”,直接进行Make的时候,使用的就是droid目标。
在main.mk文件中,还定义另外几个重要的目标,使用这些目标可以获得一些额外效果,例如make docs表示生成文档。
其他的目标如下所示。
● ramdisk:生成内存盘映像。
● bootimage:生成启动映像。
● systemimage:生成系统映像。
● userdataimage:生成用户数据映像。
● apps_only:仅生成应用程序。
● docs:生成文档,生成的文档和SDK中的文档类似。
● clean:清除目标,实际上只是删除out目录。
除此之外,showcommands也是一个有用的辅助目标,可以列出编译时详细执行的各个命令。
● 提示:showcommands和编译目标结合使用,获得具体一个编译过程中执行的命令,然后可以在命令行单独运行这些命令。
如果在32位的主机上编译Android 2.3以后的系统,在编译的开始将不能通过编译工具的检查,错误如下所示:
Checking build tools versions... build/core/main.mk:76: **************************************************** build/core/main.mk:77: You are attempting to build on a 32-bit system. build/core/main.mk:78: Only 64-bit build environments are supported beyond froyo/2.2. build/core/main.mk:79: **************************************************** build/core/main.mk:80: *** stop. Stop.
此时实际上进行了一个强行的中断,实际的错误没有严重到不能进行编译的程度。更改build/core/main.mk,注释掉引发stop的一行,就可以进行编译了。
除此之外,在源代码中,有较少的部分和主机的位数(32位或64位)相关。在Android 2.3中,external/clearsilver/工程中的几个Android.mk有相关的定义,其中的几个LOCAL_CFLAGS和LOCAL_LDFLAGS被增加了-m64定义。将这几个定义去除,就可以在32位环境中编译通过了。
手动在目标文件系统中添加了内容,需要通过手动制作得到文件系统映像。例如,默认情况下制作yaffs2格式的映像文件的方法如下所示:
$./out/host/linux-x86/bin/mkyaffs2image -f out/target/product/generic/system out/target/product/generic/system.img $./out/host/linux-x86/bin/mkyaffs2image -f out/target/product/generic/data out/target/product/generic/userdata.img
↘ 2.1.3 环境设置
默认情况下的Make不一定需要环境设置也可以编译。但是设置环境可以方便开发和有选择地进行编译,方法如下所示:
$ . build/envsetup.sh
环境设置脚本envsetup执行后,可以直接在终端使用一些命令,如下所示。
● croot:切换到Android源代码根目录(TOP)。
● m:全系统编译。
● mm:在一个目录中编译这个工程。
● mmm:编译某个工程,参数为某个工程的路径。
● cgrep:格式化查找C文件。
● jgrep:格式化查找Java文件。
● resgrep:格式化查找资源(xml)文件。
● godir:到包含某个文件的目录。
设置完成环境变量后,几个grep命令可以方便开发过程中的查找,它们实际上都是find和grep命令的结合。例如,格式化查找资源文件的方法如下:
$ resgrep "android:icon"
此时,将会列出包含“android:icon”字串的所有xml文件的相关行,并且用彩色表示行号和“android:icon”字串本身。
cgrep和jgrep的使用方法类似,前者将在扩展名为.c,.cc,.cpp和.h的文件中进行查找;后者在扩展名为.java的文件中进行查找。
↘ 2.1.4 系统构建结果
Android系统构建的结果全部在其根目录的out目录中,原始的各个工程的目录不会改动,也不会生成内容。默认情况下生成的是名称为generic的产品,表示“通用”的产品,可以用之运行仿真器。
Android编译的结果包含以下的内容。
● 主机工具和其依赖的内容(out/host/)。
● 目标机程序(out/target/)。
● 目标机映像文件(out/target/product/<产品名称>)。
out目录的结构如下所示(带有[]表示是目录):
out/ |-- host [主机内容] | |-- common [主机的通用内容] | | `-- obj | `-- linux-x86 [编译所生成的主机Linux运行的工具] | |-- bin | |-- framework | |-- lib | `-- obj `-- target [目标机内容] |-- common [目标机的通用内容] | |-- R | |-- docs | `-- obj `-- product [目标机的产品目录] `-- <TARGET_PRODUCT>
其中out/target/product目录是目标产品的目录,编译的产品名称由TARGET_PRODUCT宏表示。在默认的情况下使用generic作为目标产品的名称。产品目录结构如下所示(带有[]表示是目录):
out/target/product/generic/ |-- android-info.txt |-- clean_steps.mk |-- data [数据目录] |-- installed-files.txt |-- obj [中间目标文件目录] | |-- APPS [apk应用程序包] | |-- ETC [运行时配置文件] | |-- EXECUTABLES [可执行程序] | |-- KEYCHARS | |-- NOTICE.html | |-- NOTICE.html.gz | |-- NOTICE_FILES | |-- PACKAGING | |-- SHARED_LIBRARIES [动态库(共享库)] | |-- STATIC_LIBRARIES [静态库(归档文件)] | |-- include | `-- lib |-- previous_build_config.mk |-- ramdisk.img 根文件系统映像 |-- root [根文件系统目录] |-- symbols [符号的目录] |-- system [主文件系统目录] |-- system.img 主文件系统映像 |-- userdata-qemu.img QEMU的数据文件系统映像 `-- userdata.img 数据文件系统映像
系统构建的第一个步骤是“编译”,将生成目录obj中的内容,是目标机的各个目标。其中,EXECUTABLES为可执行程序目录,SHARED_LIBRARIES为动态库(*.so)目录,STATIC_LIBRARIES为静态库(*.a)目录,APPS为Android中的应用程序包(*.apk)目录。
系统构建的第二个步骤是“安装”,将生成root、system、data这3个目录,分别是目标根文件系统、主文件系统和数据文件系统的目录。
系统构建的第三个步骤是“生成映像”,将根据3 个文件系统目录生成ramdisk.img、system.img和userdata.img。“生成映像”的步骤是一个简单而机械的动作,只是根据3 个目录生成3个映像,这样目录中的内容确定,映像文件的内容不会有变化。