Android源码阅读与理解(二):Android系统Build流程详解
个人认为,成为一个靠谱的Android系统工程师,必须具备以下技能:
- 翻墙
- 会使用Linux系统
- 阅读英文资料
翻墙技能就不用说了,因为不翻墙,Android源码难以获取,更不用谈体验墙外精彩的世界了。关于第二点,最好的Android系统开发环境就是Linux(推荐ubuntu),如果能够熟练使用Linux系统可以节省不少折腾环境的时间。最后英文阅读能力也很关键,因为碰到问题Google, StackOverflow非常有用,上面的回答有不少是用英文写的。
在这里略过下载Android源码,最好的方法是参考官方文档。简单说一下我的环境:
- Ubuntu 14.04 LTS 64位
- Android 5.1.1源码
- Sublime Text 2
- Intel® Core™ i7-2630QM CPU @ 2.00GHz, 8G RAM, 1TB hd
配备一台Nexus 4/5也是极好的,这个上淘宝,几百元就能搞定,可以省下不少时间(用模拟器也可以,但是比较慢,对主机要求也比较高)。Nexus系列作为google的亲儿子,通常都会得到最新的更新,最适合折腾系统了。
源码下载后,执行如下命令即可编译Android系统(后面的文章将使用$ANDROID_SRC指代Android源码所在目录,如非特别指明,假设命令行在Android源码目录下执行):
- source build/envsetup.sh
- lunch
- make -j4
下面就解析以上三个步骤分别做了什么事情。
设置环境变量
打开$ANDROID_SRC/build/envsetup.sh文件,我们可以看到文件中定义了一些shell函数,其作用是初始化编译环境,并引入一些辅助的Shell函数,这其中就包括第二步使用lunch函数。这些shell函数可以看做shell命令,方便我们进行一些build任务,比如常用的mm。下面就列出一些常用的函数:
表 1. build/envsetup.sh 中定义的常用函数
名称 | 说明 |
---|---|
lunch | 选择产品,格式为lunch BUILDID-BUILDTYPE,如果不指定,会弹出一个列表选择 |
tapas | build未打包到image的app,命令行后可以跟一个或者多个app的名称。用法为: tapas [ |
croot | 切换到源码树的根目录 |
m | 在源码树的根目录执行 make |
mm | Build 当前目录下的模块 |
mmm | Build 指定目录下的模块 |
mma | Build 当前目录下的模块及所有依赖的模块 |
mmma | Build 指定目录下的模块及所有依赖的模块 |
cgrep | 在所有 C/C++ 文件上执行 grep |
ggrep | 在所有 Gradle 文件上执行 grep |
jgrep | 在所有 Java 文件上执行 grep |
resgrep | 在所有 res/*.xml 文件上执行 grep |
sgrep | 在所有源码上执行grep |
godir | 转到包含某个文件的目录路径 |
printconfig | 显示当前 Build 的配置信息 |
add_lunch_combo | 在 lunch 函数的菜单中添加一个条目 |
除了定义辅助Shell函数,在脚本的结尾处,还会遍历device和vendor目录下的vendorsetup.sh文件,vendorsetup.sh文件定义与设备相关的环境变量,比如/device/lge/hammerhead/vendorsetup.sh就定义了如下:
add_lunch_combo aosp_hammerhead-userdebug
这会在lunch菜单中增加一个选项:aosp_hammerhead-userdebug
lunch函数详解
Android支持不同的硬件,比如不同的CPU架构(ARM、MIPS,X86),不同厂商的外设(如蓝牙、wifi、基带等),另外可能还需要分Debug版本和Release版本,这都是在lunch中进行选择的。lunch命令的格式为lunch BUILDID-BUILDTYPE,其中BUILDTYPE取值如下:
表 2. BUILDTYPE说明
BUILDTYPE | 说明 |
---|---|
user | 相当于Release版本,无root权限,不打包调试工具,适合最后的产品发布 |
userdebug | 和user类似,但多了root权限,可以调试,适合于产品调试 |
eng | 开发阶段的配置,另外还打包了许多调试工具,适合于产品初期阶段 |
BUILDID则是一组特性集合的代码,这个通常与具体的平台相关。除了Android源码预置的选项外,还可以通过add_lunch_combo命令添加自定义的组合。当我们要开发一款新的Android产品时,首先就需要在Build系统中添加对于该产品的定义。
产品定义文件通常位于device目录下,vendor目录已经不建议使用(nexus系列的产品定义就是放在vendor目录)。device目录下通常根据公司名及产品名分为二级目录,比如lge的目录结构如下:
1 2 3 4 5 6 |
|
其中mako和hammerhead分别为nexus4和nexus5的产品代号。通常一个产品定义至少包含4个文件:AndroidProducts.mk, BoardConfig.mk, vendorsetup.sh及版本定义文件(文件名不固定)。关于如何添加产品定义,请参考<<理解 Android Build 系统>>一文的添加新的产品一节。
build Android系统
最后一步执行make -j4才开始真正的进行编译操作,j后面的数字代表同时编译的job数,这个数字可根据CPU核心线程数而定,通常可指定为CPU核心线程的1~2倍,数字越大,代表同时进行编译的任务越多,整个编译过程也会越快,但需要注意过犹不及。如果不指定目标,默认就会使用“droid”目标,build出完整的Android系统镜像。
Android build系统使用了GNU make,这点很让人意外,因为GNU Makefile实在太难编写了,也不易阅读。不过Google的天才工程师进行了模块化、写了很多实用函数,修改/添加起来还算容易。
Build输出目录结构
所有的编译产物都将位于 out 目录下,该目录下主要有以下几个子目录:
- out/host/:该目录下包含了host端的工具和库,例如:emulator,adb,aapt等。
- out/target/common/:该目录下包含了针对设备的共用的编译产物,主要是 Java 应用代码和 Java 库。
- out/target/product/product_name/:包含了针对特定设备的编译结果以及平台相关的 C/C++ 库和二进制文件。其中,product_name是具体目标设备的名称。
Build生成的镜像文件
Build 的产物中最重要的是三个镜像文件,它们都位于 out/target/product/product_name/ 目录下。这三个文件是:
- system.img:包含了 Android OS 的系统文件,库,可执行文件以及预置的应用程序,将被挂载为根分区。
- ramdisk.img:在启动时将被 Linux 内核挂载为只读分区,它包含了 /init 文件和一些配置文件。它用来挂载其他系统镜像并启动 init 进程。
- userdata.img:将被挂载为 /data,包含了应用程序相关的数据以及和用户相关的数据。
另外还有cache.img,启动模拟器时可使用-cache参数来挂载该文件,指定/cache内容。一般来说我们不直接使用userdata.img,而是使用userdata_qemu.data,用来存放用户数据,可读写,android启动后mount到 /data。只有使用-wipe-data参数启动模拟器是时候才会用到userdata.img,它会使用userdata.img的内容覆盖userdata-qemu.img。