AOSP源码28个核心目录实战指南:从代码树布局到真实构建调试的完整地图
打开AOSP(Android Open Source Project,安卓开源项目)源码的那一刻,大多数人第一反应不是兴奋,而是懵。几万个文件、几十个G的代码,git仓库里随便一翻就是上亿行历史,摆在面前的根本不是一份源码,而是一座迷宫。我当初接手第一个系统定制项目时,光是搞清楚该改哪个目录、哪个模块的产物会进到system.img,就整整绕了一周的弯路。后来把28个核心目录逐个摸过一遍,才真正体会到:AOSP的目录结构本质上就是整个Android系统的架构图,看懂它,你就等于拿到了从驱动到应用层的完整地图。
这篇内容,我就按实战视角把这28个核心目录一个个拆开讲清楚。每个目录负责什么、构建时会产出什么、日常开发中什么场景会动它、踩坑点在哪,都尽量说透。适合刚接触AOSP的开发者、要做系统定制或移植的工程师,以及想搞明白“Android到底是怎么跑起来的”的进阶学习者。
1. 顶层视野:为什么AOSP目录顺序藏着系统分层逻辑
1.1 从根目录开始的第一印象
AOSP根目录下排在最前面的几个目录,其实是有讲究的。整体顺序大致对应了Android系统的分层关系:最底层硬件适配(hardware、kernel)、操作系统核心(system、frameworks)、运行时(art、dalvik)、上层应用框架(packages、vendor)、以及支撑整个构建流程的工具链(build、prebuilts、external)。
我自己的习惯是拿到一份新源码先跑一遍ls -F,把带/符号的目录按字母序看一遍,然后重点挑带Android.bp或Android.mk的目录看。原因很简单:凡是有构建文件的目录,才是真正会产出模块的地方;纯文档目录(比如docs)虽然也有价值,但初次上手不用花太多时间。
源码树的顶层布局大致可以分成五批:
- 第一批是
art、bionic、bootable、build——负责系统启动、运行库和编译工具链。 - 第二批是
cts、dalvik、developers、development、device、docs——涉及兼容性测试、开发者工具和设备配置。 - 第三批是
external、frameworks、hardware——外部开源组件、Android框架层、硬件接口层,这是最大头的一块。 - 第四批是
kernel、libcore、libnativehelper、packages、pdk——内核、Java核心库、原生辅助库、系统应用和平台开发套件。 - 第五批是
platform_testing、prebuilts、sdk、system、toolchain、tools、vendor——测试、预编译工具链、SDK、系统服务、编译工具链、开发工具和厂商定制。
1.2 构建系统如何“看见”这些目录
很多人第一次跑source build/envsetup.sh && lunch之后,会好奇lunch到底怎么知道有哪些平台目标可选。答案就在build/make/target/product目录下的AndroidProducts.mk里,它会枚举所有产品配置文件;而每个产品配置又会通过PRODUCT_PACKAGES变量指定要编哪些模块,模块名则要在各目录的Android.bp或Android.mk中能找到。
这就是我强调“先看构建文件再读代码”的原因。AOSP不是让你从头到尾顺序阅读的教科书,它是一个庞大的可构建系统。你要改某个功能,目标应该是定位到具体模块,而不是把整个目录都吃透。后面每一节讲目录时,我都会顺带点出“这里的产物去哪了”“要改这个目录上层会有什么反应”,就是为了让你在读代码和实际编译之间建立起直接联系。
2. 构建与工具链目录:跑通编译是读懂源码的第一步
2.1build/目录:整个系统的“工程总控”
build/不是单纯的脚本集合,它分成build/soong和build/make两块,对应两代构建系统。新版AOSP已经全面切到Soong(使用Android.bp),但build/make仍然承载了大量lunch、envsetup、product配置逻辑。这两个子目录的分工我建议这样理解:build/make管“编译什么产品、选哪些模块”,build/soong管“每个模块具体怎么编、依赖怎么对齐”。
初次编译系统镜像时,几乎一定会遇到两个问题:一是磁盘空间不够(建议至少预留200GB,编译产物加源码轻松超过100GB);二是内存不足(建议16GB起步,make线程数别贪多,-j$(nproc)在高配机器上也可能把内存打满)。我在某台16GB内存的机器上试过-j16,结果直接OOM,后来老老实实限制到-j4才稳定。这里的经验是:先看prebuilts里提供的预编译工具链版本,再决定用什么编译参数,比自己乱调要安全得多。
2.2prebuilts/与toolchain/目录:预编译工具链和交叉编译器
prebuilts/目录里存放的是Google预先编译好的工具,包括Clang编译器、SDK工具、Gradle等。它的存在价值很直接:不让你从头编编译器,省掉大把时间。问题是这里的工具版本是跟AOSP版本强绑定的,手动改系统默认编译器版本会导致各种匪夷所思的编译错误。
toolchain/目录则包含一些核心的编译工具源码,比如Clang、LLVM、GCC的AOSP维护分支。一般应用开发者基本不用碰这里,但如果你做ROM开发,需要自己给Soong写工具链规则,就得回来翻它的配置。实际操作中我更多会用external/目录里那些独立工具来辅助开发,比如external/boringssl的证书工具、external/sqlite的数据库工具,这些在调试阶段非常有用。
2.3development/、platform_testing/与cts/目录:开发者工具和测试套件
development/目录包含一些开发者工具和示例代码,比如development/samples里有经典的ApiDemos,适合拿来当学习素材。platform_testing/则是平台层测试代码的家,想给系统组件写单元测试或集成测试,很多基础设施都在这里找。cts/是兼容性测试套件(Compatibility Test Suite),不想让设备过不了CTS认证的话,改框架代码之前最好先跑一下对应模块的CTS用例。
我自己的习惯是改frameworks/base里的系统服务时,先把cts/hostsidetests里跟这个服务相关的测试用例拉出来看一遍。这样做的好处是能快速明白官方对某个接口行为有什么硬性约束,避免自己实现的功能跟测试预期打架。
3. 平台核心目录:真正决定“Android是谁”的地方
3.1frameworks/目录:框架层的巨型仓库
frameworks/base是绝大多数系统开发者的主战场。它内部又分core(Java框架核心)、services(系统服务)、apps(系统应用框架代码)等子模块。你在ActivityManager、PackageManager、WindowManager这些名字里看到的关键逻辑,基本都在frameworks/base/services下。系统属性、Binder通信、权限管理这些机制,核心实现也在这一层。
我举一个实战例子:要修改“开机默认显示系统字体大小”,通常不是去改设置应用,而是去frameworks/base/packages/SettingsProvider里修改config_defaultFontSize之类的默认值。因为SettingsProvider负责向系统提供默认配置,设置应用只是读取和写入这一配置的客户端。这种改动最后会编进framework.jar或SettingsProvider.apk,刷机后所有依赖该配置的模块都会跟着变。
frameworks/native则是框架的原生层,包含SurfaceFlinger(图形合成)和input(输入系统)的关键实现。如果你调UI卡顿或触摸响应,这里才是真正的底层;Java层那些接口只是它的封装。frameworks/av负责多媒体,音频、视频、相机相关服务都在这。
3.2system/目录:系统底层服务的核心
system/目录里存放的是Android操作系统的核心守护进程与服务。最典型的有:
system/core:包含init进程(Android系统的1号进程)、logd(日志系统)、adbd(ADB守护进程)、property系统属性等。这一块是系统启动和调试的生命线。system/server:以前归在frameworks/base/services里,现在部分代码会分散到这边,负责运行各种系统服务。system/sepolicy:SELinux安全策略的定义,改系统服务必须同步检查有无selinux权限限制。
我印象最深的一次排错经历是自定义init脚本不生效。明明在system/core/init里加了rc文件,开机后就是不执行。后来排查半天发现是SELinux策略拦截,只加了rc文件却没在system/sepolicy里补充对应的file_context和neverallow规则。那次之后我就提醒自己:动系统层代码,第一件事就要想“SELinux会不会拦我”。
3.3libcore/、bionic/与art/目录:Java核心库、C运行库与运行时
libcore/是Java核心库的AOSP实现,对应OpenJDK中java.*、javax.*等包。App写的String、HashMap实际上都来自这里。如果你觉得某个Java基础类性能有问题,想从更底层优化,这里就是你要动的地方。
bionic/是Android的C/C++运行库,相当于标准C库。它跟桌面Linux的glibc差异很大,专门为移动端做了优化。做NDK开发踩过的“Android平台不支持某些POSIX接口”问题,根子就在bionic的实现取舍上。假如你要给系统加一个原生守护进程,涉及内存分配、文件IO等基础能力,一定是链到bionic,不会用桌面版glibc。
art/目录就是Android运行时本身,负责应用进程的启动、字节码解释、JIT/AOT编译、垃圾回收等核心工作。改ART代码是重型操作,一般开发者不建议碰;但理解它的结构对排查启动速度、内存占用问题有很大帮助。比如你看到某个应用首启慢,知道ART的profile引导编译机制是怎么工作的,就明白为什么“多打开几次就会变快”。
3.4dalvik/目录:历史遗产与现代并存的说明
AOSP是从Dalvik虚拟机时代走过来的。虽然现在的运行时已经变成ART,但dalvik/目录仍然保留了不少历史代码和工具,尤其是跟dex文件格式相关的部分。对普通开发者来说,这个目录更多是“了解一下曾经怎么设计的”;不过分析dex格式问题时,它的注释和文档仍然非常值得翻。
4. 硬件适配与启动链路:从bootloader到开机画面的每一步
4.1bootable/目录:启动引导与刷机模式
bootable/下面主要有两块内容:bootable/bootloader存放引导加载程序相关代码和工具,bootable/recovery则是Recovery模式的实现。Recovery模式对刷机流程的重要性不用多说,制作OTA升级包、双系统切换、工厂复位都跟它有关。
如果你想给设备加一个自定义Recovery界面,或是在恢复模式里做一些诊断操作,主要就是改bootable/recovery的GUI和脚本逻辑。要注意的是,Recovery分区镜像一般不是通过普通make直接生成到system.img里的,而是单独出recovery.img,刷写时要单独用fastboot flash recovery之类的命令。
4.2device/与vendor/目录:设备配置与厂商适配
device/目录存放的是“参考设备”的板级配置,比如device/google、device/generic等。每个设备目录下会有device.mk、BoardConfig.mk、AndroidProducts.mk,定义了这个设备用哪些SoC、有哪些分区、集成什么模块。做产品定制时,通常不是在device/里直接改参考设备,而是创建新设备目录并在lunch菜单中加入自己的产品名。
vendor/目录本质上是厂商私有适配层的家。Google原生AOSP里vendor/内容很少,但芯片厂商和OEM会把专有驱动、闭源库、HAL实现放在这里。刷完机后看到的/vendor分区,就对应这个目录的构建产物。这里有个关键原则:vendor/里的模块可以依赖system/,但system/默认不反向依赖vendor/,否则会破坏系统与厂商分区的强隔离。
4.3hardware/目录:HAL抽象层接口
hardware/目录是Android硬件抽象层的接口定义所在,比如hardware/interfaces下定义了Camera、Audio、Sensors、GNSS等一系列HIDL/AIDL接口。用大白话说,它把“硬件长什么样”包装成“系统服务能调用什么接口”,让上层完全不用关心具体芯片方案。
想做相机或音频相关的深度定制,一般是这么分工:在hardware/interfaces里新增或修改接口定义,在vendor/里实现对应的HAL库,在frameworks/av里调用这些接口。如果只是接口签名变了,但HAL实现没有跟着改,轻则相关功能不能用,重则系统服务崩溃。我吃过这个亏,所以现在每改一次HIDL接口,都会同步搜索所有实现文件,确认没有遗漏调用方。
4.4kernel/、pdk/与sdk/目录:内核、平台开发包与SDK
kernel/目录在AOSP里通常不是完整内核源码,而是公共的内核配置与补丁集合。真正完整的内核源码一般由SoC厂商单独提供。做底层驱动的开发者在AOSP里最关注的是一个叫kernel/configs的子目录,里面是内核编译的推荐配置片段,用来保证内核功能符合Android兼容性要求。
pdk/(Platform Development Kit)是平台开发套件的入口,提供了面向部分系统定制场景的额外工具和文档。普通ROM开发者会用到它的机会不多,但如果你是做量产设备方案,值得去翻一圈。sdk/则保存着构建平台SDK、系统镜像的脚本和描述文件。平时我们说的platform版本API Level,就跟这里的定义紧密相关。
5. 应用与外部组件:AOSP不只是操作系统的“内核”
5.1packages/目录:系统应用与默认组件
packages/里有两大类。一是packages/apps,包含各种预置应用,比如Settings(设置)、Launcher3(桌面)、Messaging(短信)、Camera2(相机)等;二是packages/providers,包含各种内容提供者,典型是ContactsProvider(联系人存储)、DownloadProvider(下载管理)、MediaProvider(媒体库)。
做“精简ROM”时大家最爱动的就是packages/apps,把不需要的应用移除或替换。这里有个细节要注意:很多应用虽然看着独立,但系统服务里对它有隐式依赖。比如直接把Settings拿掉,系统可能会因为缺少默认设置项而变得异常;直接删Provision(开机引导应用),可能导致首次开机无法完成系统初始化。所以精简不是“删了就完事”,要逐个确认调用链。
5.2external/目录:集成第三方开源世界
external/是AOSP里最“包罗万象”的目录,几乎你能想到的开源库它都收了一份:external/sqlite(数据库)、external/boringssl(加密)、external/protobuf(序列化)、external/zlib(压缩)、external/curl(网络)、external/libpng(图片)等。AOSP把这些库固定版本后纳入统一构建,目的是保证系统内部组件和App使用的库版本一致。
实战中遇到“某个App在开源版正常,在AOSP定制机上崩溃”的情况,十有八九是App用到了external/里某个库的旧接口或非标准行为。排查思路是先在external/里找到对应库,再对比App调用的接口与AOSP维护的版本差异。此外,external/里很多库都有OWNERS文件,写着维护者是谁,想改库代码前先看看这个文件能少走弯路。
5.3docs/、developers/与tools/目录:文档、示例与辅助工具
docs/虽然不参与工程构建,但源码树内API说明、平台架构文档都在这。特别是docs/source.android.com这套站点内容,很多问题比外部博客解释得更准确。developers/里有面向开发者的示例代码和演示项目;tools/则是各种辅助工具的集合,像tools/adt/idea里能找到Android Studio插件的部分源码,tools/metalava负责API签名检查和离线API生成。
我写系统代码前有一个习惯:先在docs/里搜一下要改的接口有没有官方说明,再去看tests/里对应模块的测试代码。这个习惯帮我少写了很多错误代码,因为官方测试往往能揭示接口真正的行为约束。
6. 其他关键目录与跨目录依赖:别忽略这些“暗线”
6.1test/与compatibility/相关目录:质量与兼容的多层防线
AOSP目录里有一个容易被忽略的事实:跟测试相关的目录非常多,除了cts/、platform_testing/,还有test/根目录下涵盖vts(Vendor Test Suite,供应商测试套件)等子模块。vts主要面向HAL和vendor实现,做设备适配时经常要跑。它跟CTS的分工可以这样记:CTS保“App跟系统兼容”,VTS保“系统跟硬件接口兼容”。
6.2.repo/目录与git工程管理的关系
严格说.repo/是repo工具创建的元数据目录,不算AOSP源码的正式组成部分,但每一个用repo sync拉下来的AOSP工程都少不了它。.repo/manifests里有我们平时说的default.xml,它定义了每个子目录对应的git仓库和分支版本。如果你想了解某个目录到底依赖了哪些子仓库,看这个文件比翻代码快得多。
很多新人会遇到的repo sync失败问题,大多数是网络传输或磁盘问题。我自己的做法是同步完成后马上检查.repo/目录完整性,再跑一次repo status确认所有子模块都处于干净状态,避免后期编译时出现莫名其妙的“文件不存在”错误。
6.3 跨目录依赖的现实:成千上万个模块的静态关联
AOSP本质上是一张巨大的模块依赖网。以system/与frameworks/为例,init要启动servicemanager,servicemanager要注册各种system server,system server又依赖frameworks/base里的框架代码。一旦你在某个模块里新增了一个类,最好先问自己:“这个类会被谁引用?有没有跨system/和frameworks/的边界?”
实际操作中,我会用build/soong生成的out/soong/.bootstrap/build.ninja等中间文件来查看模块间的依赖关系。如果有两个模块互相引用但不在同一个依赖层级,Soong会在编译时报循环依赖或找不到符号的错误。这时候不要硬改代码绕过,应该先理清依赖方向,再决定是否要把公共代码下沉到external/或libcore/等更基础的目录。
7. 实战地图:如何根据任务快速定位到正确的目录
7.1 28个目录的功能速查表
下面这张表是我自己整理的高频速查清单。它不是全量说明,而是“遇到什么任务第一反应去哪里”的索引。表格里每个目录都是AOSP顶层核心目录,点选使用起来非常方便。
| 目录名 | 核心职责 | 典型产物 | 常见操作场景 |
|---|---|---|---|
art/ | Android运行时 | art.jar、编译产物 | 启动性能优化、GC调优 |
bionic/ | C/C++运行库 | libc.so、libm.so | NDK问题排查、系统调用适配 |
bootable/ | 启动与恢复 | boot.img相关组件、recovery.img | 自定义Recovery、OTA |
build/ | 构建系统 | 编译产物、镜像文件 | lunch、make、模块增加 |
cts/ | 兼容性测试 | CTS测试套件 | 过认证、框架行为验证 |
dalvik/ | 历史运行时 | dex工具与文档 | dex格式分析、老代码阅读 |
developers/ | 开发者示例 | 示例代码 | 学习API用法 |
development/ | 开发工具 | SDK工具相关源码 | 工具定制、示例参考 |
device/ | 设备配置 | 产品配置、内核配置 | 新设备适配、lunch定制 |
docs/ | 平台文档 | 源码树文档 | 查接口设计、架构说明 |
external/ | 第三方开源组件 | 各类开源库 | 引入新库、修库缺陷 |
frameworks/ | 应用框架与系统服务 | framework.jar、系统服务 | 系统UI、服务、多媒体定制 |
hardware/ | 硬件抽象接口 | HIDL/AIDL接口 | 新硬件适配、HAL开发 |
kernel/ | 内核配置 | 内核config片段 | 内核特性裁剪 |
libcore/ | Java核心库 | core-oj.jar等 | Java基础类修改 |
libnativehelper/ | 原生辅助库 | JNI辅助代码 | JNI桥接开发 |
packages/ | 系统应用与应用数据 | 系统App、Provider | 预装/精简/定制App |
pdk/ | 平台开发套件 | 平台开发文档/工具 | 量产平台定制 |
platform_testing/ | 平台测试代码 | 测试用例 | 系统组件测试 |
prebuilts/ | 预编译工具 | 编译器、SDK工具 | 编译环境准备 |
sdk/ | SDK构建脚本 | SDK产物 | API Level管理 |
system/ | 系统核心服务 | init、logd、adbd | 开机流程、日志、安全策略 |
toolchain/ | 工具链源码 | Clang/LLVM/GCC | 编译工具定制 |
tools/ | 辅助开发工具 | IDE插件、API监控 | IDE集成、API检查 |
vendor/ | 厂商私有适配 | 固件库、HAL实现 | 厂商驱动集成 |
test/ | 通用测试集合 | VTS等测试组件 | 设备验收、HAL验证 |
.repo/ | repo仓库元数据 | 各git仓库描述 | 分支管理、同步校验 |
kernel/configs(归入kernel/) | 内核选项基线 | 内核编译器参数配置 | 对齐Android内核规范 |
7.2 新手最应该优先读的三个目录
如果时间有限,我不会建议你先啃frameworks/base的源码。我建议从这三个方向入门:
build/:搞明白整个工程是怎么被构建出来的。哪怕不看代码,光是把lunch菜单里的产品选项对照到device/和vendor/目录,你就能建立起“配置驱动编译”的直觉。system/core:读init.rc如何组织系统启动流程。一条条去查它启动了哪些服务,这些服务又来自哪些目录,你会自然地把整棵树串起来。packages/apps:挑一个规模不大的系统应用,比如Settings或DeskClock,从Manifest开始跟到关键Activity,再反查到它调用了哪些系统API,接着去frameworks/base里看这些API背后是谁在实现。这条路最贴近App开发者已有的知识结构,平滑度最高。
7.3 经验技巧:给“想动手改代码”的你的几条插件式建议
第一,不要在master分支上直接改。AOSP切换分支成本不低,我都是先repo init -b android-xx.0.0_rX切到稳定发布分支,然后用repo start开本地分支再改。这样出了问题可以随时git revert,不会被几十个子仓库的脏状态拖死。
第二,每次改动之后,优先编单模块而不是整包镜像。比如改了packages/apps/Settings,就用make Settings验证,编完再adb push到模拟器或设备上测试。等所有模块都稳定了,最后才make systemimage整包出镜像。整包编译即慢又容易把错误混在一起,单模块迭代排查效率高得多。
第三,善用m、mm、mmm这几个lunch环境里的快捷命令。m是全树编译,mm是编译当前目录模块,mmm是编译指定目录模块。初次跑之前,一定要先确认自己已经source过build/envsetup.sh并lunch过目标产品,否则这些命令根本不存在。
第四,学会看构建日志而非只看报错。out/soong.log、.ninja_log里记录了详细的执行过程。遇到“找不到符号”“头文件不存在”这类错误,先把日志里相关模块的编译命令找出来,看看是哪个Include路径缺失,比盲目加文件靠谱得多。
第五,所有改动都要记得同步检查SELinux策略。尤其是新增了系统服务、init脚本、设备节点访问权限,只改代码不补sepolicy,开机后大概率会出现权限拒绝的日志,而且这种问题非常难定位,因为表面现象往往不是“权限不足”,而是某个服务悄悄挂了或功能静默失效。
8. 把目录地图变成自己的排错直觉
AOSP的28个核心目录看着多,但真正用顺了之后,你会发现它们之间是有“逻辑手感”的。应用出问题往packages/追,框架行为异常往frameworks/追,底层服务崩溃往system/追,硬件适配有问题就往hardware/和vendor/追。一次完整的系统级排错,经常就是沿着这棵树从上往下或从下往上跑一遍。
我印象里最有代表性的一个排查案例是这样的:某台定制设备开机后应用一直收不到网络定位结果。整个链路查下来,先是怀疑应用APK,后来怀疑packages/providers里的LocationProvider,又怀疑frameworks/av里定位相关的服务接口。最后真正的问题出在hardware/interfaces里GNSS HAL的实现返回了超时,又被system/sepolicy的权限规则挡了一下。那一次跑完整条链路,把packages、frameworks、system、hardware、vendor翻了个遍,才对“目录即架构”这句话有了切身体会。
所以我一直觉得,AOSP源码最好的阅读方式不是从头读到尾,而是带着任务去翻。每一次定位问题、每一次改功能,都是在给大脑更新一张动态地图。等你习惯了从目录名直接联想到模块职责和构建产物,再看到“我要在开机向导里加一个隐私协议弹窗”这类需求,脑子里会自动浮现出答案:UI逻辑在packages/apps/Provision,配置项默认值在frameworks/base/packages/SettingsProvider,涉及权限声明就要检查frameworks/base/core/res的权限定义和system/sepolicy。这条路走顺了,AOSP对你就不再是迷宫。
最后再分享一个小技巧:最好在自己的机器上给源码树建一个TAGS索引文件,用ctags或cscope定期更新。跨目录查函数定义时,这个索引能帮你节省大量时间。我实测下来,在大型改动之前先跑一次全局索引,整个项目期的搜索效率能提升一个档次。源码树很大,索引建立可能需要几分钟,但这点时间花得值得。