1. 课程导入
Android 并不是一个“下载下来就能直接编译”的普通项目。它由数百个 Git 仓库、上千个模块、多种编程语言和一套自研构建系统共同组成。理解 Android 源代码树的结构,以及 Soong 构建工程如何把这些模块组织成可刷写的镜像,是深入 Android 系统开发、定制 ROM、排查编译问题的第一道门槛。本课程的目标,就是带你从“看得懂目录”走到“改得动构建”。
一、学习目标:从目录认知到构建掌控
本课程围绕三条主线展开:源代码树的空间结构、Soong 构建系统的描述语言与执行流程、从模块定义到镜像产出的完整链路。学完之后,你应当能够:
- 说清
aosp/根目录下各一级目录的职责边界,例如frameworks/、system/、packages/、device/、vendor/、build/、out/; - 读懂
Android.bp中的模块声明,理解cc_library、java_library、android_app等模块类型的差异; - 掌握
m、mm、mmm、soong_ui等命令背后的构建阶段划分; - 能够定位“模块没被编译”“依赖找不到”“镜像里没有我的文件”这类典型问题。
二、核心概念:源代码树、模块与构建图
Android 源代码树(Source Tree)是源码、配置、脚本和工具按约定组织的目录集合。它最显著的特征是“分层 + 分仓”:平台代码、设备代码、厂商代码、外部开源库各自有明确位置。构建系统并不直接“编译整个目录”,而是先扫描所有 Android.bp 与 Android.mk,把每个模块解析成构建图中的节点,再根据依赖关系决定编译顺序。
Soong 是 Android 7.0 之后逐步取代 Make 的构建系统,其核心是:用 Android.bp 描述模块,用 Blueprint 解析语法,用 Soong 生成 Ninja 文件,最终由 Ninja 执行实际编译。Kati 则负责把遗留的 Android.mk 转换为 Ninja,使新旧构建方式可以共存。理解这条“bp → blueprint → soong → ninja → 产物”的链路,比记住某个命令更重要。
三、整体框架:从源码到镜像的四个阶段
把构建过程拆开看,可以归纳为四个阶段,每个阶段都有明确的输入和输出:
- 配置阶段:执行
source build/envsetup.sh与lunch,确定目标产品、架构、变体(user/userdebug/eng)。这一步决定了后续所有模块的编译参数。 - 解析阶段:Soong 扫描全部
Android.bp,Kati 处理Android.mk,生成统一的构建图与build.ninja。 - 编译阶段:Ninja 按依赖关系并行编译,产出
.o、.so、.jar、.apk等中间产物。 - 打包阶段:把编译产物按分区规则组装成
system.img、vendor.img、boot.img等可刷写镜像。
这四个阶段对应着不同的排错入口:配置错会导致 lunch 失败,解析错会报 Android.bp 语法或依赖缺失,编译错通常是源码或工具链问题,打包错则多与分区配置、文件列表有关。
四、方法流程:一条命令背后的完整路径
以最常见的单模块编译为例,命令与背后的动作如下:
# 1. 初始化构建环境
source build/envsetup.sh
# 2. 选择目标产品
lunch aosp_arm64-userdebug
# 3. 编译指定模块(示例:Settings)
mmm packages/apps/Settings
# 4. 或使用 Soong 统一入口
m Settings
# 5. 查看模块产物路径
# out/target/product/<device>/system/priv-app/Settings/Settings.apk
其中 mmm 会先解析指定目录下的模块,再触发其依赖链;m 则从全局构建图出发,按目标名查找模块。两者最终都落到 Ninja 执行。理解这一点,就能明白为什么“只改了一个文件,却编译了很久”——因为依赖链上的模块被重新触发了。
五、案例与场景:一个模块为什么没进镜像
假设你在 device/<vendor>/<product>/ 下新增了一个 Android.bp,声明了 cc_binary,但刷机后发现 /system/bin/ 下没有这个可执行文件。常见原因有三类:
| 现象 | 可能原因 | 排查入口 |
|---|---|---|
| 模块未被编译 | 没有加入 PRODUCT_PACKAGES |
检查 device.mk / product.mk |
| 编译了但没进镜像 | 缺少 installable 或分区归属错误 |
查看 Android.bp 的 installable、vendor 属性 |
| 依赖缺失导致失败 | shared_libs / static_libs 未声明 |
查看 Soong 报错中的模块名与依赖路径 |
| 增量构建未生效 | Ninja 缓存或 out/ 状态异常 |
尝试 m clean 或删除对应模块产物 |
六、常见误区与复盘
初学者最容易踩的坑,是把 Android 构建当成普通 Makefile 工程。实际上,Soong 的模块声明是“声明式”的:你描述模块是什么、依赖谁、装到哪里,而不是写编译命令。另一个误区是忽视 out/ 目录的状态性——它既是产物目录,也是增量构建的缓存,随意删除或混用不同 lunch 目标的 out/,会导致难以解释的链接错误。
lunch 目标而不清理 out/。不同产品的分区配置和模块集合不同,混用会引发“模块找不到”或“镜像内容错乱”。