Android&AOSP历史
构建工具历史
名称由来与含义
工具 | 名称由来与含义 |
|---|---|
Bazel | 是 Google 内部构建工具 Blaze 的变位词(anagram)。Blaze 在 Google 内部使用了近20年,用于构建搜索、Gmail、YouTube 等超大规模单仓代码库。Bazel 是其2015年开源的版本。 |
Soong | 1. 人名致敬:源自 AOSP 构建系统早期核心工程师 Robert “Bob” Soong。 2. 寓意双关:发音类似 “Soon”(更快)和 “Song”(歌曲),寓意 “更快、更优雅的构建系统”,旨在取代缓慢的 Make。 |
CMake | “Cross-platform Make” 的缩写。虽然名字中含有 “make”,但它是一个更高级的跨平台构建配置生成器,并不直接执行构建,而是生成 Makefile 或 Ninja 等底层构建文件。 |
Kati | 1. 日语来源:源自日语 “かち”,意为 “价值”。 2. 人名巧合:也是 Go 语言创始人之一 Rob Pike 女儿的名字。它是 Google 专为 Android 开发的实验性 GNU Make 克隆工具,负责将 Android.mk转换为 Ninja 文件。 |
Make | 指 GNU Make,经典的命令式构建工具。其名称直白地表达了其功能:根据规则“制作”目标文件。 |
Ninja | 源自 “忍者”,形象地体现了其设计哲学:像忍者一样迅捷、安静、专注单一目标(以最快速度执行构建)。 |
核心维度对比
维度 | Bazel | Soong | CMake | Kati | Make (GNU Make) | Ninja |
|---|---|---|---|---|---|---|
核心定位 | 通用、超大规模构建系统(构建系统的构建系统) | AOSP 专用构建系统(Soong + Blueprint) | 跨平台构建配置生成器 | Makefile 到 Ninja 的转换器(胶水层) | 经典命令式构建执行器 | 底层构建执行器(专注速度的“忍者”) |
输入文件 | BUILD/ BUILD.bazel(Starlark) | Android.bp(Blueprint) | CMakeLists.txt(CMake 脚本) | Android.mk(Makefile) | Makefile/ Android.mk | build.ninja(由 Soong/Kati/CMake 生成) |
构建模型 | 纯函数式依赖图,强封闭性(Hermetic) | 声明式依赖图(受 Bazel 影响) | 混合式(命令+声明),生成底层构建文件 | Makefile 语义克隆,转换为 Ninja 规则 | 递归式、命令式,基于时间戳和规则 | 基于依赖图的并行执行(无高级逻辑) |
关键特性 | 远程缓存/执行、沙箱隔离、多语言统一、极致增量 | 为 AOSP 高度优化、与 Kati 协同、逐步取代 Make | 跨平台 IDE 集成、模块化配置、广泛生态支持 | 专注 Makefile 转换、增量支持、过渡性工具 | 简单灵活、生态成熟、但速度慢、难以维护 | 极致速度、低开销、精准增量、完美并行 |
在 Android 生态中的角色 | 未来候选(AOSP 正在迁移)、大型跨平台项目可选 | 当前 AOSP 官方标准(新模块必须用) | 应用层 NDK 官方标准(用于构建 C/C++ 库) | 历史遗留兼容层(转换旧 Android.mk) | 已被取代的旧标准(仅存于历史模块) | Soong/Kati 生成的构建图的执行引擎 |
学习曲线 | 陡峭(需理解 Starlark、规则、依赖图) | 中等(需掌握 Android.bp语法) | 中等(需掌握 CMake 脚本语言) | 低(仅对 Makefile 语法做转换) | 低(基础简单,但精通难) | 低(不直接面向开发者,仅作为后端) |
性能焦点 | 极致速度与可重现性(集群并行、全局缓存) | AOSP 全系统构建速度 | 配置生成速度与跨平台兼容性 | Makefile 解析与转换速度 | 无专门优化,递归解析导致慢 | 执行速度和并行效率(启动快、依赖精准) |
Bazel vs Soong/CMake 核心对比
维度 | Bazel | Soong (Android.bp) | CMake |
|---|---|---|---|
定位 | 通用、超大规模构建系统 | AOSP 专用构建系统 | 跨平台 C/C++ 构建系统(事实标准) |
语法 | 声明式,基于 Python 方言的 Starlark | 声明式,类 JSON 的 Android.bp | 混合式(命令+声明),CMakeLists.txt |
构建模型 | 纯函数式依赖图 | 依赖图(由 Soong 生成 Ninja) | 部分依赖,但非纯函数式 |
缓存 | 远程缓存是其王牌功能,团队共享构建结果 | 仅本地缓存 | 仅本地缓存 |
沙箱执行 | 是,确保构建完全可重现 | 否 | 否 |
学习曲线 | 陡峭 | 中等 | 中等 |
Android 官方地位 | 未来候选,内部和实验性使用 | 当前 AOSP 官方标准 | 当前应用 NDK 官方标准 |
演进关系与现状总结
- 历史路径:
- AOSP 系统构建主线:Make → (Kati) → Soong → (未来) Bazel。
- 应用层 C/C++ 构建主线:Make → CMake。
- 执行引擎演进:Make(自带执行)→ Ninja(专用执行器)。
- Soong 是总规划师,Kati 是旧图纸翻译官,Ninja 是高效施工队。
- 给你的选择建议:
- AOSP 系统开发者:必须掌握 Soong (
Android.bp),理解 Ninja 作为执行引擎的角色,了解 Kati 的过渡作用,并关注 Bazel 的迁移趋势。 - Android 应用开发者:
- 构建 Java/Kotlin:Gradle(应用层绝对主流)。
- 构建 C/C++(NDK):CMake(官方推荐),它常生成 Ninja 文件作为后端以获得更快构建速度。
- 追求极致性能的团队:在大型项目中,无论使用 CMake 还是 Bazel,都可以选择 Ninja 作为底层执行器来获得最快的构建速度。
- AOSP 系统开发者:必须掌握 Soong (
当前 AOSP 构建中的协作关系(核心流程):
你的源码 (Java, C++)
↓
Android.bp (新) 或 Android.mk (旧) // 开发者编写的模块定义
↓
[Soong] 解析所有 .bp 文件 // 生成大部分 Ninja 规则
↓
[Kati] 解析遗留的 .mk 文件 // 翻译成 Ninja 规则
↓
合并成一个巨大的、统一的 `build.ninja` 文件 // 完整的任务依赖图
↓
[Ninja] 执行这个文件 // 并行调用编译器、链接器...
↓
生成的 APK、库、系统镜像App构建历史
发展历程概览
时期 | 主导工具 | 核心特点 | 在 Android 开发中的角色 |
|---|---|---|---|
2008–2012 | Apache Ant(配合 Eclipse ADT) | 基于 XML、灵活但脚本冗长、依赖管理弱 | 早期 Android SDK 和 Eclipse 插件默认的构建系统 |
2012–2013 | Apache Maven(部分项目) | 约定优于配置、强大的依赖管理、POM 模型 | 少数团队引入,但未成为 Android 官方标准 |
2013–至今 | Gradle(Android Gradle Plugin) | 基于 Groovy/Kotlin DSL、增量构建、高度可定制、官方支持 | 2013 年 Google I/O 正式宣布为 Android 官方构建系统,完全取代 Ant |
各阶段详解
1. 史前时代:Apache Ant(2000–2012)
- 背景:Android 最初基于 Java,自然继承了 Java 生态的主流构建工具。Apache Ant(2000 年发布)是当时最流行的构建工具。
- 在 Android 中的应用:早期 Android SDK 提供
android命令工具,配合 Eclipse 的 ADT(Android Development Tools) 插件,底层使用 Ant 脚本进行编译、打包。 - 缺点:XML 脚本冗长复杂、缺乏统一的依赖管理、项目结构维护困难。
2. 过渡探索:Apache Maven(2004–2013)
- 背景:Maven 于 2004 年发布,引入了 POM(Project Object Model) 和“约定优于配置”理念,解决了 Ant 的依赖管理问题。
- 在 Android 中的尝试:部分团队尝试用 Maven 构建 Android 项目,但因其插件机制复杂、灵活性不足,未成为主流。
3. 新时代:Gradle 的崛起与官方化(2007–至今)
Gradle 的诞生
- 2007 年,Gradle 1.0 发布,由 Hans Dockter 创建,旨在结合 Ant 的灵活性和 Maven 的依赖管理优势,并引入基于 Groovy 的 DSL,极大提升了脚本的可读性和可维护性。
- 2012 年,Gradle 1.0 正式推出,引入了更快的依赖解析引擎和插件支持。
成为 Android 官方构建工具
- 2013 年:在 Google I/O 大会上,Google 正式宣布 Android Studio(基于 IntelliJ IDEA)为官方 IDE,并将 Gradle 定为 Android 的官方构建系统。
- 从此,Android Gradle Plugin(AGP) 成为构建 Android 应用的核心组件,提供了资源处理、多渠道打包、代码混淆等全套能力。
Gradle 在 Android 中的关键演进
- 2016 年:Gradle 3.0 默认启用 Gradle Daemon,显著提升构建速度;同时引入 Kotlin DSL 支持,为构建脚本提供类型安全和更好的 IDE 支持。
- 2017 年至今:Gradle 持续优化性能(构建缓存、并行任务),AGP 版本不断迭代,支持新特性(如 ViewBinding、Compose、模块化构建)。
🛠️ 当前状态与核心组件
如今,一个标准的 Android 项目构建体系完全围绕 Gradle + AGP 展开:
- Gradle Wrapper:确保团队使用统一的 Gradle 版本。
- Android Gradle Plugin(AGP):Google 维护的核心插件,定义 Android 特有的构建任务和生命周期。
- Kotlin DSL:越来越多的项目采用
build.gradle.kts替代 Groovy DSL,以获得更好的类型安全和 IDE 辅助。 - Gradle Daemon & 构建缓存:大幅提升增量构建速度。
Native构建历史
发展历程总览
时期 | 系统级构建(AOSP) | 应用级构建(NDK/JNI) | 核心特点与转折点 |
|---|---|---|---|
2008‑2015 | 纯 Make(Android.mk) | ndk‑build(Android.mk) | 早期 Android 系统与应用均基于 GNU Make,语法复杂、构建慢。 |
2016‑2017 | 引入 Soong(Android.bp),与 Make 并存 | CMake 开始官方支持(Android Studio 2.2) | Soong(Go 语言)解决 Make 的并行与速度问题;CMake 成为 NDK 新选择。 |
2018‑2021 | Soong 成为主要构建系统,Android.bp 逐步替代 Android.mk | CMake 成为默认推荐,ndk‑build 进入维护 | Google 强力推动 CMake,因其跨平台、IDE 支持好、与 Gradle 深度集成。 |
2022‑至今 | Android.bp 为主,Android.mk 逐渐废弃;实验性引入 Bazel | CMake 绝对主流,ndk‑build 仅用于遗留项目 | 构建速度、类型安全、跨平台一致性成为核心诉求。 |
各阶段详解
1. 远古时代:Make 与 ndk‑build(2008‑2015)
- 系统构建:AOSP 完全依赖 GNU Make,每个模块需编写
Android.mk文件,定义LOCAL_MODULE、LOCAL_SRC_FILES等变量。构建递归解析,效率低下。 - 应用构建:NDK 提供 ndk‑build 命令,本质是封装了 Make,同样需编写
Android.mk(及可选的Application.mk)。开发者需手动调用ndk‑build生成.so,再集成到 APK。 - 痛点:脚本冗长、构建慢、难以调试、无代码提示。
2. 变革前夜:Soong 与 CMake 的崛起(2016‑2017)
- 系统构建:Android 7.0 引入 Soong 构建系统,使用 Android.bp(Blueprint)文件替代
Android.mk。Soong 用 Go 编写,生成 Ninja 构建图,极大提升并行度与速度。 - 应用构建:Android Studio 2.2(2016)正式支持 CMake 作为 NDK 构建工具。CMakeLists.txt 语法更现代,支持
find_package、FetchContent等高级依赖管理,并与 Gradle 无缝集成。 - 意义:构建工具开始向 声明式、类型安全、高性能 演进。
3. 统一与收敛:CMake 成为标准(2018‑2021)
- 系统构建:Android 8+ 中 Soong 成为默认,Android.bp 快速普及;Android 10 后大部分模块已完成迁移。
- 应用构建:CMake 全面胜出。原因:
- 跨平台:同一份 CMakeLists.txt 可编译 Android/iOS/Linux/macOS。
- IDE 支持:Android Studio 提供代码索引、跳转、补全。
- 依赖管理:内置支持本地库、远程仓库、子模块。
- 官方推荐:Google 文档、模板项目均以 CMake 为准。
- NDK 工具链现代化:GCC 被移除,Clang/LLVM 成为唯一编译器;LLD 替代 GNU ld 为默认链接器;libc++ 替代 gnustl 为默认 C++ 标准库。
4. 现代与未来:Bazel 的实验与生态巩固(2022‑至今)
- 系统构建:Android 13+ 开始实验性引入 Bazel,旨在提供更统一、可伸缩的构建系统。但目前 Android.bp(Soong)仍是生产环境标准。
- 应用构建:CMake 地位稳固,NDK 持续优化对其支持(如 CMake 工具链文件
android.toolchain.cmake的完善)。ndk‑build 仅用于维护老旧项目。 - 趋势:构建速度(缓存、并行)、依赖管理(版本解析、冲突处理)、多平台支持(Kotlin Multiplatform)是持续演进方向。
核心工具对比
工具 | 适用场景 | 配置文件 | 优点 | 缺点 |
|---|---|---|---|---|
ndk‑build | 遗留 NDK 项目维护 | Android.mk、Application.mk | 简单直接,与早期 NDK 兼容 | 语法封闭、无 IDE 支持、生态落后 |
CMake | 现代 NDK 开发标准 | CMakeLists.txt | 跨平台、强大 IDE 支持、先进依赖管理、与 Gradle 深度集成 | 学习曲线稍陡 |
Android.bp | AOSP 系统开发 | Android.bp | 类型安全、构建快、易于并行、Google 维护 | 仅用于系统开发,不适用于普通应用 |
Bazel | 实验性大规模构建 | BUILD.bazel | 高度可伸缩、支持多语言、声明式依赖 | 生态不成熟,在 Android 中尚处实验阶段 |
AOSP介绍
Android 开源项目(AOSP)是一个规模宏大且结构复杂的工程奇迹。对于任何希望定制、移植或仅仅是深入理解 Android 的开发者而言,掌握其构建系统与工程目录结构都是不可或缺的第一步。本报告将围绕 AOSP 14,系统性地剖析其现代构建体系的核心机制、端到端流程、关键目录的职责划分以及日常开发的工程实践要点。
构建系统总览:多工具协同的艺术
AOSP 的构建系统并非单一工具,而是一个由 Soong、Blueprint、Kati、Ninja 以及逐步渗透的 Bazel 共同协作的精密体系。它们各司其职,共同将数千万行代码高效地转化为可在设备上运行的完整系统镜像。

核心组件职责划分
- Soong (与 Blueprint):Soong 是 AOSP 现代构建系统的核心“大脑”。它是一个用 Go 语言编写的工具,负责解析
Android.bp文件。Android.bp文件采用一种名为 Blueprint 的声明式类 JSON 语法,用于描述模块(如库、可执行文件、App)的属性和依赖关系。Soong 的主要职责是理解模块定义、处理依赖关系、并最终生成 Ninja 构建规则。它取代了传统 Make 的大部分配置逻辑,带来了更高的解析速度、更好的可维护性和错误检查能力。 - Kati (与 Make):尽管 Soong 是主流,但 AOSP 中仍存在大量遗留的
Android.mk文件。Kati 是一个专门用于处理这些 Makefile 的工具,它的作用是将 GNU Make 的语法和逻辑翻译成 Ninja 构建规则。Kati 的存在是过渡时期的关键一环,它确保了庞大的存量 Make 模块能够无缝融入新的构建流程,避免了“一刀切”式重构的巨大成本。 - Ninja:Ninja 是一个轻量级、专注于速度的底层构建系统。它本身不包含复杂的逻辑,其核心任务是高效地执行构建命令。它接收由 Soong 和 Kati 生成的
.ninja文件,这些文件精确描述了源文件、目标产物之间的依赖图(DAG)以及执行编译、链接等具体操作的命令。Ninja 能够根据依赖关系以最大并行度执行任务,极大地提升了(尤其是增量)构建的效率。 - Bazel:Bazel 是 Google 内部构建系统 Blaze 的开源版本,以其强大的多语言支持、精确的依赖管理和可复现的构建能力而闻名。Google 曾计划在 AOSP 中逐步用 Bazel 替代 Soong 和 Kati,
Android.bp的语法设计也刻意与 Bazel 的BUILD文件保持相似。然而,截至 Android 14,这一迁移计划已暂停。目前,Bazel 在 AOSP 中的应用主要局限于特定领域,例如构建内核(Kernel),以及一些独立的工具和库,尚未成为构建完整系统的主流路径。开发者应持续关注其演进,但当前的核心工作流仍围绕 Soong/Kati+Ninja。
Android.bp 核心语义
Android.bp 是描述一个模块如何构建的核心文件。其语法简洁且结构化,核心概念包括:
- 模块类型 (Module Types):每个模块都以一个类型声明开始,例如
cc_library(C/C++ 库),java_library(Java 库),android_app(Android 应用) 或apex(APEX 模块)。模块类型决定了构建系统将如何处理该模块的源文件和属性。 - 属性 (Properties):在模块定义内部,通过一系列属性来描述其细节,如
name(模块名,必须唯一),srcs(源文件列表),static_libs/shared_libs(静态/动态依赖),cflags(编译标志) 等。 - 依赖关系 (Dependencies):模块间的依赖通过
static_libs,shared_libs,header_libs等属性声明。Soong 会解析这些声明,确保在编译当前模块前,其依赖的模块已被正确构建。 - 可见性 (Visibility):为了强制执行清晰的架构边界,Soong 引入了可见性控制。默认情况下,一个模块只能被同一
Android.bp文件或其子目录下的其他模块依赖。若要跨目录(尤其是跨顶层目录)依赖,必须通过visibility属性明确授权。这有助于防止意外的跨层依赖,保持代码库的模块化和整洁。 - 默认模块 (Defaults Modules):当多个模块共享大量相同的属性时,可以使用
cc_defaults或java_defaults等模块来定义一个属性模板。其他模块可以通过defaults: ["my_defaults_module"]来继承这些通用配置,减少重复代码。
// 定义一个名为 "libFoo" 的 C++ 共享库
cc_library_shared {
name: "libFoo",
srcs: ["src/foo.cpp"],
shared_libs: ["libBar"],
cflags: ["-Wall"],
visibility: [
"//frameworks/base",
"//packages/apps/MyApp",
],
}
构建流程视角:从命令到镜像
理解 AOSP 构建流程的最佳方式,是从一个开发者的日常命令出发,追踪其在构建系统内部的完整旅程。

端到端构建路径
- 开发者触发构建
- 一切始于终端中的一个简单命令。首先,通过
source build/envsetup.sh初始化构建环境,该脚本会向 Shell 中注入一系列便捷的函数,如lunch,m,mm,mma等。 - 接着,执行
lunch <product_name>-<build_variant>(例如lunch aosp_arm64-userdebug)。lunch命令的核心作用是设置构建目标,将产品配置(如设备型号、包含的模块、系统特性等)写入环境变量,供后续的构建步骤读取。
- 一切始于终端中的一个简单命令。首先,通过
- Soong 解析与构图
- 当执行
m(构建整个系统) 或mm/mma(构建当前目录下的模块) 命令时,构建流程正式启动。首先介入的是 Soong。 - Soong 进程(
soong_ui)会启动,它首先会扫描整个源码树,查找所有的Android.bp文件。 - 它解析这些文件,根据模块定义和依赖关系,在内存中构建一个完整的依赖关系图 (DAG)。在此阶段,Soong 会进行大量的检查,如模块命名冲突、依赖有效性、可见性规则等。
- 同时,Kati 也会被调用,用于解析所有遗留的
Android.mk文件,并将其逻辑转换为一个独立的 Ninja 文件 (out/build-aosp_arm64.ninja的一部分)。
- 当执行
- 生成 Ninja 文件
- Soong 完成解析和构图后,会遍历依赖图,为每个需要执行的构建动作(如编译一个 C++ 文件、打包一个 JAR)生成一条对应的 Ninja 构建规则。
- 所有这些规则最终被写入一个或多个
.ninja文件中,通常位于out/soong/目录下,并最终合并成一个主build.ninja文件(例如out/build-aosp_arm64.ninja)。这个文件是整个构建过程的“执行计划”。
- Ninja 执行编译与打包
- 接下来,控制权交给 Ninja。Ninja 读取最终的
build.ninja文件,分析其中的依赖关系。 - 它根据依赖关系和系统CPU核心数,以最高效的并行方式启动相应的工具链(如 Clang、JavaC、d8、r8)来编译源代码,生成
.o(对象文件),.so(共享库),.dex(Dalvik 可执行文件) 等中间产物。 - 编译完成后,其他工具如
aapt2(资源打包),apksigner(签名),soong_zip等会被调用,将中间产物和资源文件打包成最终的模块,如 APK 或 APEX。
- 接下来,控制权交给 Ninja。Ninja 读取最终的
- 生成系统镜像
- 所有模块构建完毕后,最后一步是生成设备可刷写的系统镜像。
- 一系列打包脚本(如
build/make/tools/build_image.py)会将所有系统库、可执行文件、框架、应用以及配置文件(prop文件)按照预定义的布局,组合成system.img,vendor.img,boot.img等分区镜像。这些镜像最终存放在out/target/product/<device_name>/目录下。
关键开关与问题定位
- 环境变量与 Soong 配置:构建行为受多种配置影响。
lunch设置的TARGET_PRODUCT和TARGET_BUILD_VARIANT是最基础的。此外,还可以通过build/soong/soong.variables文件或环境变量来定义 Soong 配置变量(soong_config_variables),用于在Android.bp中进行条件编译,实现产品间的差异化。 - 日志与产物目录:构建过程中的所有输出都位于
out/目录。out/soong/:存放 Soong 生成的中间文件,包括.ninja文件。当你想知道某个Android.bp模块是如何被转换成 Ninja 规则时,可以检查这里。out/target/product/<device_name>/:存放最终的镜像文件和安装好的模块。例如,编译的libFoo.so会被安装到该目录下的system/lib64/。out/verbose.log/out/error.log:完整的构建日志和错误日志,是排查问题的首要入口。
- 排障线索:
- 编译失败:首先查看终端输出的错误信息,它通常会指明哪个模块、哪个文件、哪行代码出错。对于复杂的失败,查阅
out/error.log获取更详细的上下文。 - 定位失败模块:使用
m <module_name>或在模块目录下mm单独编译该模块,可以快速复现和定位问题。 - 查看生成的 Ninja 规则:要理解某个模块的具体编译命令,可以执行
m -j1 -v <module_name>,-v(verbose) 会打印出 Ninja 执行的具体命令。或者,直接在out/build-*.ninja文件中搜索模块名,找到其构建规则。 - 依赖问题:如果遇到“module not found”或“visibility”相关的错误,首先检查
Android.bp中的name是否正确,以及visibility属性是否已对依赖方开放。
- 编译失败:首先查看终端输出的错误信息,它通常会指明哪个模块、哪个文件、哪行代码出错。对于复杂的失败,查阅
工程目录结构地图:代码的组织逻辑
AOSP 的目录结构遵循“关注点分离”和“约定优于配置”的原则,每个顶层目录都有其明确的职责。理解这种布局是导航代码库、添加新功能或进行设备移植的基础。
顶层与关键子目录职责
build/:构建系统的中枢。build/soong/:包含 Soong 构建系统的核心逻辑、Blueprint 解析器以及内置的模块类型定义。build/make/:包含核心的 Make 构建脚本、产品配置 (product/) 和目标配置 (target/) 的模板。envsetup.sh也在这里。build/bazel/:与 Bazel 集成相关的脚本和配置。
frameworks/:Android 框架层。这是开发者最常接触的部分,提供了应用开发所需的 Java API 和底层的原生 C++ 服务。frameworks/base/:最核心的框架代码,包括 ActivityManager, WindowManager, PackageManager 等系统服务的实现。frameworks/native/:提供原生 C++ API,如 SurfaceFlinger、AudioFlinger 等。frameworks/av/:多媒体框架,包括 Stagefright、Camera 服务等。
packages/:系统捆绑的应用程序。packages/apps/:包含大量核心 AOSP 应用,如 Settings (设置), Dialer (拨号), Contacts (联系人) 等。packages/providers/:系统级的内容提供者,如MediaProvider。packages/services/:一些独立的系统服务,如Telephony服务。
system/:底层的核心系统组件。system/core/:包含系统启动过程 (init)、日志系统 (logd)、属性服务 (property_service) 等基础组件。system/sepolicy/:SELinux 安全策略的定义文件。
device/与vendor/:设备相关代码。device/:通常由 SoC 厂商(如 Google, Qualcomm, MediaTek)维护,包含特定芯片平台的通用配置、驱动和板级支持包(BSP)。例如device/google/pixel。vendor/:通常由 OEM/ODM 厂商维护,用于存放其私有的、闭源的二进制 blob 或特定产品的代码。它与device/的界限有时模糊,但vendor/更侧重于最终产品的实现。
hardware/:硬件抽象层 (HAL) 接口和实现。hardware/interfaces/:定义了 HAL 的 AIDL/HIDL 接口。hardware/libhardware/:HAL 的旧版头文件和加载逻辑。- 其他子目录包含各种 HAL 的通用或参考实现,如
hardware/ril(无线电)。
external/:所有外部引用的开源项目。AOSP 依赖的大量第三方库都存放在这里,例如sqlite,zlib,icu,clang等。每个子目录通常是一个独立的 git 项目。prebuilts/:预编译的工具和库。为了确保构建环境的一致性和速度,AOSP 会自带一个特定版本的工具链(如 Clang/LLVM)、构建工具(如 aapt2)和 SDK。这些都存放在prebuilts/目录下。art/&bionic/&libcore/:Android 运行时与基础库。art/:Android 运行时 (ART) 的完整实现,包括 JIT/AOT 编译器、垃圾回收器和 dex 文件执行器。bionic/:Android 定制的 C 标准库,专门为嵌入式设备优化。libcore/:Java 核心库的实现,是 Java API 的基础。
cts/:兼容性测试套件。一套庞大的测试集,用于验证设备实现是否符合 Android 兼容性定义文档 (CDD) 的要求,是设备上市前必须通过的测试。out/:构建输出目录。这是唯一一个由构建系统生成和写入的顶层目录。所有中间文件 (.o,.dex)、最终模块 (.apk,.so) 和系统镜像 (.img) 都存放在这里。其内部结构与目标系统分区高度对应,例如:out/target/product/<device>/system/对应于设备上的/system分区。out/target/product/<device>/obj/存放所有模块的中间产物。out/soong/存放 Soong 生成的配置和 Ninja 文件。
工程实践要点:定制与排障
对于系统开发者而言,日常工作主要围绕着修改、新增模块以及解决构建问题。以下是一些关键的实践建议。
新增与修改模块
- 创建
Android.bp:在你的模块源代码目录下创建一个Android.bp文件是定义新模块的第一步。选择合适的模块类型(如cc_library,android_app),并填充必要的属性。 - 添加到产品配置:仅仅定义模块是不够的,还需要让构建系统知道在构建特定产品时需要包含它。这通常通过修改
device/<vendor>/<board>/<product>.mk文件完成,在PRODUCT_PACKAGES变量中加入你的模块名。
# In device/my-vendor/my-board/my_product.mk
PRODUCT_PACKAGES += \
MyCustomApp \
libMyNativeLib
android_app {
name: "MyCustomApp",
srcs: ["src/**/*.java"],
sdk_version: "current",
static_libs: ["androidx.appcompat.appcompat"],
// ... 其他属性
}
利用配置进行定制
- Soong 配置 (Soong Config):对于需要在不同产品或架构间有差异的编译行为,可以使用 Soong Config。首先,在产品的 BoardConfig.mk (
device/.../BoardConfig.mk) 中定义一个变量:- 然后,在
Android.bp- 中通过
soong_config_module_type_import- 和
soong_config_get- 来读取和使用这些变量,进行条件编译。
- 产品配置 (Product Config):更常见的定制方式是通过 Make 的产品配置体系。通过继承 (
$(call inherit-product, ...))、添加PRODUCT_PACKAGES、设置PRODUCT_PROPERTY_OVERRIDES(系统属性) 等方式,可以灵活地组合和定制最终产品的形态。
SOONG_CONFIG_NAMESPACES += myGlobalVars
SOONG_CONFIG_myGlobalVars += \
feature_a_enabled \
feature_b_type
SOONG_CONFIG_myGlobalVars_feature_a_enabled := true
SOONG_CONFIG_myGlobalVars_feature_b_type := "advanced"
依赖与可见性管理
- 坚持最小权限原则:当你的模块需要被其他模块依赖时,不要直接设置为全局可见 (
//visibility:public)。应在visibility属性中精确列出需要访问它的模块或目录。 - 使用
vendor_available或product_available:如果你的模块位于system分区,但需要被vendor或product分区的模块访问,必须在Android.bp中设置为vendor_available: true或product_available: true。这会触发构建系统进行额外的 ABI/API 稳定性检查。
常用构建命令与加速技巧
- 全量构建:
m - 构建指定模块:
m <module_name1> <module_name2> - 在当前目录构建模块:
mm(所有模块) /mma(所有模块及其依赖) - 安装到设备: 在构建命令后追加
install,如m install - 并行构建:
-j参数控制并行任务数,如m -j32。通常无需手动指定,构建系统会自动使用所有可用的 CPU 核心。 - 增量构建: Ninja 的核心优势。在修改少量代码后,再次执行构建命令,Ninja 会只重新编译受影响的部分。
- 使用 ccache:在环境中启用
export USE_CCACHE=1并设置ccache缓存目录后,C/C++ 的编译结果会被缓存。对于需要频繁make clean或切换分支的场景,ccache能极大地节约编译时间。 - 使用预编译产物: 对于大型的外部依赖,如果其不常变动,可以考虑将其编译为预编译库 (
cc_prebuilt_library_shared等),并提交到prebuilts/目录,从而跳过对其的重复编译。
总结与展望
AOSP 的构建系统是一个经过多年演进、平衡了性能、可维护性和向后兼容性的复杂工程体系。理解 Soong/Blueprint 作为声明式配置的核心,Kati 作为 Make 的兼容层,以及 Ninja 作为高效执行引擎的协同工作模式,是进行任何 AOSP 开发的基础。同时,熟悉 AOSP 的目录结构,不仅能帮助开发者快速定位代码,更能让人理解其背后的架构设计哲学。
维护建议
- 优先使用
Android.bp:对于所有新模块,应始终使用Android.bp进行定义。对于现有Android.mk的重大修改,应考虑将其迁移到Android.bp。 - 善用
defaults和soong_config:通过这些机制来管理通用配置和产品差异,保持Android.bp文件的简洁与清晰。 - 严格管理
visibility:将可见性控制作为代码架构评审的一部分,避免“图方便”而随意开放模块的依赖边界。
后续可能更新点
- Bazel 的回归或演进:尽管目前已暂停,但 Bazel 在大型项目管理上的优势依然显著。未来 Google 可能会以新的形式重新启动或部分引入 Bazel 的能力,例如用于特定的子系统或提供可选的构建路径。开发者应关注官方博客和 AOSP 邮件列表的相关动态。
- Soong 的功能增强:Soong 自身也在不断演进,可能会引入新的模块类型、属性或更高级的配置能力,以更好地支持新的 Android 特性(如模块化系统 Mainline/APEX)和新的编程语言(如 Rust)。
- 构建分析工具:随着构建系统复杂度的增加,AOSP 可能会提供更多官方工具来帮助分析构建性能瓶颈、依赖关系可视化以及构建产物分析,例如
bpmodify和bptidy等工具的功能会持续增强。