Android核心原理与系统级应用高效开发
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

第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个映像,这样目录中的内容确定,映像文件的内容不会有变化。