Android&AOSP历史

Android&AOSP历史
Photo by Denny Müller / Unsplash

构建工具历史

名称由来与含义

工具
名称由来与含义
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 官方标准

演进关系与现状总结

  1. 历史路径
    • AOSP 系统构建主线Make → (Kati) → Soong → (未来) Bazel
    • 应用层 C/C++ 构建主线Make → CMake
    • 执行引擎演进Make(自带执行)→ Ninja(专用执行器)
    • Soong​ 是总规划师,Kati​ 是旧图纸翻译官,Ninja​ 是高效施工队。
  2. 给你的选择建议
    • AOSP 系统开发者:必须掌握 Soong (Android.bp),理解 Ninja​ 作为执行引擎的角色,了解 Kati​ 的过渡作用,并关注 Bazel​ 的迁移趋势。
    • Android 应用开发者
      • 构建 Java/Kotlin:Gradle(应用层绝对主流)。
      • 构建 C/C++(NDK):CMake(官方推荐),它常生成 Ninja​ 文件作为后端以获得更快构建速度。
    • 追求极致性能的团队:在大型项目中,无论使用 CMake​ 还是 Bazel,都可以选择 Ninja​ 作为底层执行器来获得最快的构建速度。

当前 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_MODULELOCAL_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_packageFetchContent等高级依赖管理,并与 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.mkApplication.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_defaultsjava_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 构建流程的最佳方式,是从一个开发者的日常命令出发,追踪其在构建系统内部的完整旅程。

端到端构建路径

  1. 开发者触发构建
    1. 一切始于终端中的一个简单命令。首先,通过 source build/envsetup.sh 初始化构建环境,该脚本会向 Shell 中注入一系列便捷的函数,如 lunch, m, mm, mma 等。
    2. 接着,执行 lunch <product_name>-<build_variant> (例如 lunch aosp_arm64-userdebug)。lunch 命令的核心作用是设置构建目标,将产品配置(如设备型号、包含的模块、系统特性等)写入环境变量,供后续的构建步骤读取。
  2. Soong 解析与构图
    1. 当执行 m (构建整个系统) 或 mm/mma (构建当前目录下的模块) 命令时,构建流程正式启动。首先介入的是 Soong。
    2. Soong 进程(soong_ui)会启动,它首先会扫描整个源码树,查找所有的 Android.bp 文件。
    3. 它解析这些文件,根据模块定义和依赖关系,在内存中构建一个完整的依赖关系图 (DAG)。在此阶段,Soong 会进行大量的检查,如模块命名冲突、依赖有效性、可见性规则等。
    4. 同时,Kati 也会被调用,用于解析所有遗留的 Android.mk 文件,并将其逻辑转换为一个独立的 Ninja 文件 (out/build-aosp_arm64.ninja 的一部分)。
  3. 生成 Ninja 文件
    1. Soong 完成解析和构图后,会遍历依赖图,为每个需要执行的构建动作(如编译一个 C++ 文件、打包一个 JAR)生成一条对应的 Ninja 构建规则。
    2. 所有这些规则最终被写入一个或多个 .ninja 文件中,通常位于 out/soong/ 目录下,并最终合并成一个主 build.ninja 文件(例如 out/build-aosp_arm64.ninja)。这个文件是整个构建过程的“执行计划”。
  4. Ninja 执行编译与打包
    1. 接下来,控制权交给 Ninja。Ninja 读取最终的 build.ninja 文件,分析其中的依赖关系。
    2. 它根据依赖关系和系统CPU核心数,以最高效的并行方式启动相应的工具链(如 Clang、JavaC、d8、r8)来编译源代码,生成 .o (对象文件), .so (共享库), .dex (Dalvik 可执行文件) 等中间产物。
    3. 编译完成后,其他工具如 aapt2 (资源打包), apksigner (签名), soong_zip 等会被调用,将中间产物和资源文件打包成最终的模块,如 APK 或 APEX。
  5. 生成系统镜像
    1. 所有模块构建完毕后,最后一步是生成设备可刷写的系统镜像。
    2. 一系列打包脚本(如 build/make/tools/build_image.py)会将所有系统库、可执行文件、框架、应用以及配置文件(prop 文件)按照预定义的布局,组合成 system.img, vendor.img, boot.img 等分区镜像。这些镜像最终存放在 out/target/product/<device_name>/ 目录下。

关键开关与问题定位

  • 环境变量与 Soong 配置:构建行为受多种配置影响。lunch 设置的 TARGET_PRODUCTTARGET_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_availableproduct_available:如果你的模块位于 system 分区,但需要被 vendorproduct 分区的模块访问,必须在 Android.bp 中设置为 vendor_available: trueproduct_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
  • 善用 defaultssoong_config:通过这些机制来管理通用配置和产品差异,保持 Android.bp 文件的简洁与清晰。
  • 严格管理 visibility:将可见性控制作为代码架构评审的一部分,避免“图方便”而随意开放模块的依赖边界。

后续可能更新点

  • Bazel 的回归或演进:尽管目前已暂停,但 Bazel 在大型项目管理上的优势依然显著。未来 Google 可能会以新的形式重新启动或部分引入 Bazel 的能力,例如用于特定的子系统或提供可选的构建路径。开发者应关注官方博客和 AOSP 邮件列表的相关动态。
  • Soong 的功能增强:Soong 自身也在不断演进,可能会引入新的模块类型、属性或更高级的配置能力,以更好地支持新的 Android 特性(如模块化系统 Mainline/APEX)和新的编程语言(如 Rust)。
  • 构建分析工具:随着构建系统复杂度的增加,AOSP 可能会提供更多官方工具来帮助分析构建性能瓶颈、依赖关系可视化以及构建产物分析,例如 bpmodifybptidy 等工具的功能会持续增强。