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 → 产物”的链路,比记住某个命令更重要。

Android 源代码树与 Soong 构建链路总览 源代码树(Source Tree) frameworks/ · system/ packages/ · apps/ device/ · vendor/ build/ · soong/ external/ · prebuilts/ out/(构建产物) 每个目录下散布 Android.bp / Android.mk 模块声明文件 扫描 构建系统(Soong / Kati) Blueprint 解析 Android.bp 语法树 Soong 模块解析 模块类型 · 属性 · 依赖 Kati 转换 Android.mk → Ninja 生成 build.ninja 构建图 · 依赖关系 Ninja 执行编译 并行 · 增量构建 soong_ui / m / mm / mmm 统一入口命令 产出 构建产物(out/) system.img vendor.img boot.img / ramdisk.img product.img / odm.img APK / JAR / SO 库 target_files.zip 可刷写镜像 · 调试符号 最终交付物 核心链路:Android.bp → Blueprint → Soong → Ninja → out/ 镜像与库文件

三、整体框架:从源码到镜像的四个阶段

把构建过程拆开看,可以归纳为四个阶段,每个阶段都有明确的输入和输出:

  1. 配置阶段:执行 source build/envsetup.sh 与 lunch,确定目标产品、架构、变体(user/userdebug/eng)。这一步决定了后续所有模块的编译参数。
  2. 解析阶段:Soong 扫描全部 Android.bp,Kati 处理 Android.mk,生成统一的构建图与 build.ninja。
  3. 编译阶段:Ninja 按依赖关系并行编译,产出 .o、.so、.jar、.apk 等中间产物。
  4. 打包阶段:把编译产物按分区规则组装成 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/。不同产品的分区配置和模块集合不同,混用会引发“模块找不到”或“镜像内容错乱”。