第一次完整同步 AOSP 源码后我盯着根目录下的那一片文件夹愣了很久。接近三十个名称各异的目录每个名字都好像在哪见过真到编译报错、改系统应用、适配新硬件时又不知道应该往哪个文件夹里钻。后来被构建失败、开机崩溃、测试不通过这些事反复折腾才算把 AOSP 这套目录体系摸出了门道。这篇就把实战中最常用到的 28 个目录一次性讲清楚从构建入口到运行核心从第三方依赖到测试验证整体过一遍。1. 先建立整体认知28个核心目录是怎么分组的1.1 为什么先看地图而不是先看代码AOSP 不是一个普通单仓库它是用 repo 工具管理起来的成百上千个 git 仓库的集合根目录下每个一级目录基本都有自己的独立历史和构建单元。不同目录之间没有严格的调用约束像是“微服务”式的代码组织某几个目录负责生成镜像某几个目录负责运行时某几个目录专门提供测试工具。新手最常犯的错是拿到源码直接钻进 frameworks/base然后从某个类开始往上读。结果读了半小时发现一个类依赖了几层 native、一个系统服务又牵扯到 init、property 和 SELinux policy最后晕头转向。正确做法是先建立一张目录地图明白每个目录属于哪条线遇到问题该去哪里找代码而不是试图一次性读完所有东西。1.2 28个核心目录速查表我把这 28 个目录分成四条线构建与产物、运行核心与框架、第三方依赖与工具、测试交付与应用。严格来说根目录下同步下来的源码目录是 27 个还有一个 out 目录是编译自动生成的但它几乎贯穿你所有实战操作所以我把它也纳入这个“实战二十八目录”的范畴。构建与产物线分类目录核心职责实战要点构建与产物build构建系统引擎包含 Soong、Blueprint、make 封装、产品打包脚本所有编译命令基本都是从 build/envsetup.sh 进来先摸底构建与产物device设备与产品定义包括 BoardConfig、Product 配置、内核配置新增设备第一站产品名在这里注册构建与产物vendor厂商私有模块、预编译库、设备专有特性开源和闭源的分水岭闭源驱动基本都落这里构建与产物kernel内核源码和默认内核配置改驱动、调内核开关时关注实际厂商常用独立内核分支构建与产物bootablerecovery、bootloader 相关代码做 OTA、刷机方案、改 recovery UI 时重点看构建与产物prebuilts预编译工具链、构建工具、SDK 镜像更新工具链版本时最容易踩坑版本必须匹配构建与产物out所有构建中间产物和最终镜像不是源码但排查 80% 的编译问题要先看这里运行核心与框架线分类目录核心职责实战要点运行核心bionic安卓 C 库、数学库、动态链接器底层崩溃、链接错误排查时绕不开运行核心libcoreJava 核心类库的 Android 实现要改 java.、javax.行为时使用运行核心libnativehelperJNI 辅助代码与桥接层做系统级 JNI 时看这里运行核心art应用运行时解释器、JIT/AOT、GC性能调优、dex2oat 相关改动影响巨大运行核心dalvik旧虚拟机实现看老代码、兼容历史系统属性时用到运行核心frameworks框架层与核心服务包含 base/av/native系统服务、窗口、媒体、应用框架全在这里运行核心systeminit、属性服务、守护进程、媒体组件开机流程和底层调试必看运行核心hardwareHAL 接口定义与硬件适配新设备适配、传感器/音视频 HAL 修改的主战场第三方依赖、工具链与文档线分类目录核心职责实战要点第三方工具external第三方开源库源码例如 sqlite、protobuf、zlib升级依赖库或打补丁时使用第三方工具abiC/C ABI 兼容规则声明换工具链、调整 libc 版本时需要核对第三方工具toolchain编译器工具链源码一般直接使用 prebuilts源码很少主动去改第三方工具tools开发辅助脚本和工具实现翻自动化脚本、资源处理工具时能挖到宝第三方工具sdkSDK 接口定义、API level 配置文件改系统 API 后做兼容性检查时使用第三方工具docs系统源码文档与配图查 API 对照、内部资料时好用第三方工具developers官方开发者示例和 sample 工程学习标准用法最快的入口第三方工具development开发脚本、测试脚本和辅助工具编译辅助、模拟器调试常用测试、交付与应用线分类目录核心职责实战要点测试交付cts兼容性测试套件过 Google 认证必跑判断系统兼容性测试交付test测试框架与 VTS 等通用测试定义做驱动/内核级别验证时关注 test/vts测试交付platform_testing平台级自动化测试与基准测试改完 framework 后跑回归的好去处测试交付pdk平台开发套件提供 BSP/产品集成参考做厂商 SDK 或硬件 BSP 时翻阅测试交付packages系统应用、桌面、设置、输入法等定制 ROM 界面和预装应用的主战场这张表先给你一个索引。接下来几章我把每条线里的关键目录放到真实操作场景里展开讲。2. 构建与产物线你每天都在进出的大门2.1 build、device、vendor 之间的三角关系接触 AOSP 编译的第一条命令一定是source build/envsetup.sh。这个脚本把build/下的构建工具注册到当前 shell 里后面的lunch、m、mm、mma、mmm、mma全都是它在背后兜底。很多人只知道照敲命令不知道lunch选中的产品定义其实来自device/目录。以新增一台设备为例你要在device/厂商/机型/下创建三个关键文件AndroidProducts.mk注册产品名机型.mk定义继承关系和PRODUCT_PACKAGESBoardConfig.mk定义板级参数和内核配置。然后再在vendorsetup.sh里加上一句add_lunch_combo 厂商-机型-userdebug重新执行 envsetup 就能 lunch 到你自己的产品了。vendor/目录则是厂商私有模块的家。很多芯片厂商的闭源驱动、摄像头算法库、私有 HAL 实现都会落在vendor/厂商/。它们和device/通常是配套的device 里写配置、声明依赖vendor 里放真正编译出来的二进制或未开源源码。可以用一个生活类比来描述build/是中央厨房device/是菜单vendor/是私房调料包。菜单上写清食材PRODUCT_PACKAGES调料包决定口味vendor 私有库中央厨房根据菜单和配方出菜。有一个经验当你需要把一个新应用打进系统镜像时不要只在 packages 下看一眼目录就以为完事了。真正生效的勾选动作通常在产品的.mk文件里你需要在PRODUCT_PACKAGES中加上模块名。这个模块名来自对应源码目录里的Android.mk或Android.bp中的LOCAL_MODULE/module_name。很多初学者发现“代码明明存在但编译出来的 system.img 里没有”原因就是产品配置里没注册。2.2 kernel、bootable、prebuilts、out 的真正角色kernel/在 AOSP 根目录里更多是一个“默认内核树”主要用于模拟器、公共参考板。真实设备厂商基本都会维护独立的内核分支但你在 AOSP 里做的内核配置、defconfig、dtb 打包和device/厂商/机型/BoardConfig.mk里的KERNEL_DEFCONFIG、BOARD_KERNEL_IMAGE_NAME有直接关系。改内核后只编 boot.img 时通常执行m bootimage产物会落到out/target/product/机型/boot.img。bootable/目录里最常碰的是bootable/recovery。恢复模式不只是刷机用的界面它是 OTA 升级中的关键一环校验签名、执行 update 脚本、回滚异常状态。改 recovery 的 UI、按键逻辑、升级流程都要在这套代码里动。注意 recovery 是独立进内核启动模式的它依赖的库和主系统不完全相同别在 recovery 里直接引用了一个只编进 system 的库否则链接和运行时都会很难看。prebuilts/的存在一开始会让人困惑为什么源码树里要塞这么多二进制答案是构建性能和稳定。AOSP 依赖 GCC、Clang、OpenJDK、Go 工具链、Rust 工具链等一大堆外部工具如果每次都从源码编译整套工具链一次全量构建的时间会失控。所以官方直接提供经过验证的预编译工具链构建系统默认使用它们。实战中真正需要手动动 prebuilts 的场景不多但偶尔会遇到工具链版本和源码不匹配的情况比如某次拉取了最新主干代码但 prebuilts 没有同步更新链接阶段报一堆奇怪错误这时第一反应应该是检查 repo 的 prebuilts 目录是否更新完整而不是怀疑自己的代码。out/目录则是每个编译者的“事故现场”。常见路径是out/target/product/机型/里面放着system.img、vendor.img、boot.img、obj中间文件、symbol 表等。另一个关键路径是out/host/里面是跑在开发机上的工具比如 aapt、adb、fastboot 的 host 版本。比如你改了 framework 层代码只编m framework然后可以直接在out/target/product/机型/system/framework/framework.jar看到产物。很多“明明编了但没生效”的问题都是因为没看产物路径、没有重新打包镜像或者没有 push 到设备。3. 运行核心线从程序启动到界面绘制的关键路径3.1 各种“库”和“运行时”的层级安卓应用进程从启动到跑起来要穿越的目录其实是一条固定路线。init 进程在system/core/init/里起来解析 init.rc 后启动 zygote。zygote 代码在frameworks/base/core/java/com/android/internal/os/ZygoteInit.java附近。它 fork 出应用进程后应用进程运行在art/提供的虚拟机上加载 DEX 字节码、执行 JIT/AOT 编译、管理对象生命周期。bionic/是这一整套体系的底层地基。它代替了 glibc提供 libc、libm、libdl 和动态链接器 linker。应用里所有 Java native 层调用的标准 C 库函数最后都会落到 bionic。修改 bionic 中的动态链接器直接影响所有 so 的加载策略这个目录的改动属于“地震级别”没有充分的兼容性测试不要轻易碰。libcore/则是 Java 核心库的 Android 实现。你在 Java 里用的java.lang、java.util、java.io在 Android 上不是直接照搬 OpenJDK而是经过适配和裁剪的。遇到java.*类行为异常应该在libcore/里查问题。比如某个String方法的行为不符合预期十有八九能在libcore/ojluni或libcore/ojluni/src/main/java找到对应实现。libnativehelper/稍小但它是 Java 和 native 之间的桥梁提供 JNI 相关的辅助结构比JNIEnv、JavaVM的更上层封装。系统级 JNI 开发、检查 so 里的全局引用都会和这个目录相关。还有dalvik/目录它已经是历史遗留但很多系统属性、权限名、日志 TAG 仍保留着dalvik.或dalvikvm前缀。如果你在 logcat 里看到dalvik.vm.heapgrowthlimit之类的字段不要以为还是 old Dalvik实际实现已经迁移到art/只是属性命名为了兼容性没改。从职责上理解bionic管进程的底料art管执行引擎libcore管 Java 标准层libnativehelper管 Java/native 的接口缝dalvik是旧引擎的墓碑这五个目录共同撑起一个 Java 应用运行的最小闭环。3.2 frameworks 和 system 是你动手改得最多的地方frameworks/是 AOSP 里体量最大的目录细分下来有三个必须知道的子目录frameworks/base是框架核心四大组件、WindowManager、ActivityManager、PackageManager、资源系统都在这里frameworks/av是音视频、相机、媒体服务的老巢frameworks/native是 native 服务与 Surface、Input、Sensor 等服务端实现。系统 UI、开机动画、应用安装、权限管理只要你做 ROM 定制几乎每天都要在这个目录里进出。举个例子要定位SystemServer到底起了哪些服务直接去frameworks/base/services/java/com/android/server/SystemServer.java。想改开机动画逻辑去frameworks/base/cmds/bootanimation/。想改通知栏快速设置去frameworks/base/packages/SystemUI/。这些路径都不是靠背而是靠一个思路先找模块再找服务再找类。编译时用mmm或mma定位到对应模块编译后到out/对应路径找产物逻辑就顺了。system/目录看起来和 frameworks 很像但它更接近“底层系统组件”。system/core里有 init、property 服务、logd、lmkd、adbd 等核心守护进程system/netd管网络权限与路由system/media管音频策略和媒体相关服务。如果你想分析手机开机的第一个用户进程是怎么来的看system/core/init和frameworks/base/core/java/com/android/internal/os/ZygoteInit.java的配合。如果你想查某个日志标签是怎么被过滤的去system/core/logd。hardware/处于系统和设备之间的夹层。HAL 的接口定义大量集中在hardware/interfaces/既有 HIDL 也有 AIDL 接口。新硬件接入时要在这里新增或继承接口同时要去device/和vendor/配合实现。这个目录的接口改动影响是全局的一旦改了接口签名所有依赖该 HAL 的服务和 native 模块都必须同步更新所以我在做新设备适配时会先看hardware/interfaces/下面的现有接口能否复用而不是轻易发明新接口。4. 第三方依赖、工具链和文档线4.1 external 与 abi升级依赖前先看兼容性external/是把第三方开源项目源码放进 AOSP 的集合地。常见的有 sqlite、protobuf、zlib、libpng、expat、googletest 等。它们以独立源码目录存在每个子项目通常有自己的Android.bp、Android.mk、OWNERS文件。修改这些库时要特别注意你并不只是在改“自己的库”你改的是所有依赖它的模块共同使用的基础设施。比如升级 protobuf 版本可能影响 binder 传输、演播室、安全补丁等多个模块。abi/这个目录很多人一辈子没打开过。它不是运行时代码而是描述 C/C ABI 规则的文件集合。当你使用不同工具链版本、修改 libc 的普通函数签名、改变结构体布局时需要核对 ABI 是否还一致。安卓系统高度依赖 prebuilt 二进制库如果 ABI 发生了变化原先用旧工具链编出来的库就可能直接崩。我个人的习惯是但凡要动external/里会被大量 native 模块引用到的库先跑一遍该目录下的测试再看abi/里有没有对应符号的检查清单。另外要提醒一点external/目录下的代码不一定是“完全开源”。有些子目录只提供头文件或 stubs真正的实现可能在 vendor 的预编译库里。别看到目录名就默认能编译出东西先看它的Android.bp和Android.mk中是否有prebuilt属性。4.2 toolchain、tools、sdk 这些“非业务”目录怎么用toolchain/存放编译器源码但它很少被直接编译。日常构建使用的 GCC、Clang 来自prebuilts/不是从toolchain/现场编出来的。需要动 toolchain 的场景基本是做编译器二次开发、为特定架构增加编译参数、研究内建函数行为。普通 ROM 定制三个月都不会碰它一次但遇到“一模一样的代码在我机器上编译不过”这种诡异问题时偶尔要翻一下toolchain/里的配置了解工具链默认行为。tools/则是一个更杂的“工具箱”里面很多脚本是构建流程的辅助。比如一些资源处理、打包校验、自动化测试辅助代码。有时候你会在这里找到意想不到的上层工具实现因为它们不像 frameworks 一样有清晰的业务边界。我的经验是先按名字找找不到就用grep很多工具类代码比文本文档更能说明一个行业惯例是怎么落地的。sdk/目录维护 API 接口定义。Android 的 API level、权限组定义、接口白名单都从这里来。做一个 SDK 定制 ROM 时你需要修改sdk/下的 API 配置文件否则系统里新增的接口不会被外部应用识别。不过在动手之前先想清楚绝大多数第三方应用会通过SystemApi或公开 SDK 来访问系统能力直接改 API 配置牵一发动全身通常是厂商系统集成里最吃力不讨好的部分。docs/、developers/、development/是三类容易被忽略但很有用的目录。docs/存开发文档和配图适合查系统架构图、接口变更说明developers/里有官方示例工程想快速验证一个系统 API 的用法直接把 sample 拿到本地编译比翻老半天源码更高效development/更像“开发者工具集”包含构建辅助脚本、模拟器调试工具、代码检查钩子。很多“我改了源码但 IDE 不认”的工程问题都可以从 development 目录里找到脚本方案。5. 测试、交付与应用层5.1 cts、test、platform_testing 的分工做 ROM 定制的人最容易忽略测试目录但真正上量产时测试目录就是你的“安全网”。cts/是 Google 官方兼容性测试套件主要验证第三方应用运行时的系统行为是否符合 Android 兼容性定义。过不了 CTSGoogle 认证就免谈。它测试的是应用层视角官方 API 的行为、权限规则、组件生命周期都要和规范一致。test/目录比 cts 更底层。test/vts是 Vendor Test Suite专门测 HAL、内核、系统库对设备厂商实现的要求。如果你改了hardware/或kernel/请务必跑 VTS 相关项。test/下还有构建测试框架、测试工具和 infrastructure 代码系统中的自动化测试基于它运行。拿它做快速冒烟测试很合适。platform_testing/则是平台内部的白盒测试。它不像 CTS 那样从外部黑盒验证而是让平台开发者自己给 framework、system service、perf 行为写测试用例。改完frameworks/base某个服务后去platform_testing/tests/下找对应测试模块用atest跑相关测试比手动刷机验证快得多。pdk/是 Platform Development Kit翻译是“平台开发套件”。它包含了很多与产品集成相关的参考文档、模板和工具。厂商做 BSP、做硬件适配、做系统定制时pdk 里通常有最佳实践和配置模板。它不完全算测试目录但和“交付给设备厂商”这件事绑定得很紧。如果你要负责把一个 AOSP 系统适配到量产的硬件平台pdk 就是你绕不开的参考资料。5.2 packages定制 ROM 的主战场packages/是另一个“数量爆炸”的地方但比 frameworks 简单得多。它主要是系统应用和用户可直接感知的组件packages/apps/Settings、packages/apps/SystemUI、packages/apps/Launcher3、packages/apps/Messaging、packages/inputmethods等。定制界面、预装应用、修改桌面布局、改设置项基本都从这里入手。实际操作中改 SystemUI 的典型场景是这样的先改packages/apps/SystemUI/src/下的代码然后在源码根目录执行source build/envsetup.sh lunch 你的产品接着执行m SystemUI或者mmm packages/apps/SystemUI编译完在out/target/product/机型/system/priv-app/SystemUI/下找到新 APK。如果用模拟器可以adb root后adb remount再 push APK 过去最后重启 SystemUI 进程。Source tree 中的Android.bp决定了模块名和依赖关系改完不要忘了检查PRODUCT_PACKAGES里是否已经包含该模块。还有一点容易出错一些系统应用是用priv-app权限运行的packages/apps/里很多模块在Android.bp中会声明privileged: true或使用privapp-permissions。盲目把 Application 目录挪到/system/app/可能会导致权限丢失、功能异常。定制预装应用时尽量沿用原有 product 配置中的安装路径不要手动改目录位置。6. 定制和裁剪时哪些目录能碰、哪些建议少碰6.1 适合大胆改的地方从产品定制角度看device/、vendor/、packages/、hardware/和frameworks/中的部分模块是改动收益最高的地方。改开机动画、系统应用、产品配置、开机脚本基本都集中在这几个目录。它们互相之间有比较清晰的接口修改后验证路径也直接编镜像、刷机、看日志。比如做一个轻量定制系统可以为了减少系统镜像体积从device/厂商/机型/下的PRODUCT_PACKAGES中移除一些不必要的 app 或模块再配合PRODUCT_PACKAGES_REMOVE把公共 base 中包含的模块删掉。这种做法比直接删packages/apps/下的源码目录安全得多因为你只是让某个模块不进入系统镜像AOSP 源码树仍然完整后续更新源码也不会因为“目录不存在”而出问题。6.2 尽量少动的敏感地带abi/、bionic/、libcore/、art/、dalvik/这五个目录是“全系统地基”。只要动其中一个风险评估指数直接翻倍。比如改bionic的 malloc 实现可能影响所有 native 进程改art的 GC 策略可能让某些应用出现莫名的抖动改libcore的 String 实现可能引起所有 Java 代码行为变化。不是不能改而是你必须做好全量测试、兼容性测试、压测的准备。build/目录也不建议轻易大改。Soong / Blueprint 的构建逻辑非常庞杂改一个变量可能影响成千上万个模块的编译顺序。普通定制不用碰它最多在外层产品配置里调整变量。真正的构建开发者才需要深入build/soong普通人进去容易迷失。frameworks/base也要注意。目录可以动但要遵循“增量优先、覆盖优先”的原则。比如你要改某个系统资源的默认值不要直接修改frameworks/base/core/res/res/values/下面的资源文件而是利用resource overlay机制在device/厂商/机型/overlay/中创建同名资源覆盖。这样后续 AOSP 更新时你的定制不会每次都跟上游冲突又可以保证改动集中在一个自己能控制的地方。6.3 定制节奏的建议我的经验是第一轮先只改device/和vendor/把产品跑起来第二轮改packages/和frameworks/做可见的功能与界面定制第三轮再根据性能和兼容性要求去评估是否要动art/、bionic/这些底层目录。大部分商业 ROM 的真正差异化都在应用层和框架层底层改动更多是为了性能调优和内核安全投入产出比完全不同。7. 常见问题与排查索引编译失败时先翻哪个目录整理几个我实际踩过、也经常看到同行踩的坑给你一个快速定位的思路。第一个最典型编译时提示No module named SystemUI或类似模块找不到。这时候不要先去盯packages/apps/SystemUI先去看当前 lunch 的产品定义里有没有把模块加入PRODUCT_PACKAGES。模块编码在Android.bp里是存在的但产品没有引用就不会编进 system.img。修改位置通常在device/厂商/机型/产品.mk。第二个常见问题链接阶段报一堆undefined symbol。第一反应应该搜索这个 symbol 到底在哪个模块里用grep -rn symbol frameworks/ system/ external/ hardware/之类的方式找出定义。找到定义后看看当前编译的模块有没有在Android.bp中声明依赖。AOSP 构建系统不允许隐式依赖少声明一个shared_libs或者header_libs链接就挂。这个问题和源码逻辑关系不大几乎全在模块依赖配置里。第三个问题是开机阶段反复重启卡在第二屏。先看out/下的构建产物是否完整尤其确认boot.img、system.img、vendor.img是否都是同一套产物。很多时候是只编译了 system但内核和 vendor 分区还是旧的硬件接口对不上导致 init 起不来。然后把logcat和dmesg拿过来重点看system/core/init相关的 fatal 日志以及art的运行时崩溃信息。第四个问题是修改了 framework 服务后应用拉起来直接闪退。先确认系统服务有没有成功注册方法是在frameworks/base/services/java/com/android/server/SystemServer.java里看你新增的服务是否放进了 start 流程再看服务调用时用的 Binder 接口有没有在frameworks/base/core/java里同步更新。AIDL 接口改动后需要重新编译生成 stub 文件否则客户端和服务端接口对不上就会出现ServiceConnection异常。还有一个和prebuilts相关的经典坑某次整编之前可能更新过 repo但 prebuilts 目录没有同步完整构建时 Clang 报版本不匹配。这种情况没有必要去改本地编译器回到repo sync把 prebuilts 同步到官方对应 commit再清理一下out/里的中间产物重新编译通常能恢复。结语真正理解 AOSP 目录不需要背下每个子项目。我个人的经验是先看懂 build、device、vendor 这条构建线再摸熟 frameworks、system、packages 这条产品线其他目录在遇到具体问题时再查。等你在 out 目录里翻过几次日志踩过几次链接错误这 28 个目录就真正变成你手边工具箱了。下一次遇到“这个改动从哪里下手”的问题你会很自然地在心里打开这张地图而不需要再像我当初那样在根目录发呆。