AOSP源码详细解剖:目录结构、编译链路与定制指南
做Android开发久了多多少少都会听过AOSP这个名字。全称Android Open Source Project安卓开源项目简单说就是Google开出来的一套完整移动操作系统源码。很多人平时写App用的是SDK只接触到API和框架层从来没想过源码长什么样。可真等到你要定制ROM、做系统级优化、或者只是想知道“状态栏什么时候更新的”你就必须面对AOSP源码。而打开AOSP的第一眼就是那一大片密密麻麻、名字千奇百怪的目录。别慌这其实就像看一份家谱只要理清了祖宗辈分后面就好办了。这篇文章我会从一个实际编译、改过源码、也被各种目录搞得头皮发麻的开发者视角把AOSP的顶层目录、build.sh的角色、vendor的定位、还有framework和SystemUI这些主力目录一次讲清楚。不管你是刚准备下载源码的学生还是要接手定制系统项目的工程师看完都能对Android源码的“家谱”有个整体认识至少不会再对着目录列表发呆。如果你现在正对着几百GB源码不知道从哪看起或者已经开了源码却搞不清vendor和system的区别这篇文章就是写给你的。1. AOSP到底是什么为什么要啃这块“硬骨头”1.1 先弄懂“源码”和“SDK”的关系很多人一开始会把Android源码和SDK混为一谈。SDK是给App开发者用的工具包里面有.jar库、API接口文档、模拟器镜像这些是“零件说明书”。AOSP则是零件本身的生产图纸是一整套操作系统级别的代码包括内核、硬件抽象、系统服务、App框架、内置应用。换句话说SDK教你“如何调用手机的功能”AOSP教你“手机的功能是怎么被写出来的”。平时我们写App时用到的Activity、Service、ContentResolver在AOSP里都有对应的Java源码。我们看到的ActivityManagerService、PackageManagerService这类系统服务全都躺在frameworks/base/services下面。AOSP除了代码还包含了完整的构建系统。你可以用一套命令把几百MB的源码编译成可以烧到手机里的镜像文件比如system.img、vendor.img、boot.img。这些镜像组合起来就是一台手机跑的操作系统本体。理解了这一点你就不难明白为什么搞AOSP的人会被叫做“底层开发”了。1.2 目录结构是Android的“家谱”读懂了它你就掌握了主线如果你把AOSP根目录ls一下会看到art、bionic、build、dalvik、device、frameworks、hardware、kernel、packages、platform_testing、system、vendor这一大堆。第一次看确实头大感觉每个目录都认识但合起来不知道是什么。其实Android源码的目录组织有很强的逻辑它就是按照操作系统的分层架构来划分的。最底下是内核和硬件抽象往上是运行库和虚拟机再往上是系统框架最顶上才是内置应用。目录名基本都是按这个层次对应的。比如bionic是C库art和dalvik是虚拟机hardware是硬件抽象层frameworks是框架层packages是应用层。把目录当成“家谱”来看就清晰多了。每个目录相当于家族里的一个分支有的分支管“传宗接代”的底层逻辑有的分支管“门面”的应用展示。只要抓住主线前端App调框架框架调系统服务系统服务通过HAL访问硬件再看目录时就能自动对号入座了。2. 从根目录看起AOSP顶层目录的“家族图谱”2.1 根目录下那些“脸熟”的目录和文件AOSP根目录里的第一印象是“怎么这么多隐藏文件夹”。最显眼的.repo目录是整个源码仓库的管理核心。AOSP是由几百个Git仓库组成的Google用repo工具统一管理它们。你执行repo sync同步代码时它会按照manifest脚本把所有仓库拉到本地。所以下载AOSP不是说git clone一个仓库就完事而是先安装repo再repo init、repo sync。这个流程本身就是新人第一道坎。根目录里还有几个脚本和文件值得留意build.sh有的发行版叫build/envsetup.sh、Makefile、README等。这里要敲个黑板很多第三方发行版或者厂商会提供一个独立的build.sh放在根目录用于一键设置环境并编译。AOSP官方其实没有一个叫build.sh的脚本直接在顶层而是把编译入口放在build/目录下。所以你看到标题里“从build.sh到vendor”指的是一种常见的实际操作习惯早期的AOSP版本或者很多二次定制项目会封装一个build.sh帮你执行source build/envsetup.sh、lunch、make这一串命令。后文我会专门讲它。其余顶层目录我就不按字母序逐个报了挑最关键的几个解释你理解后其他目录就能触类旁通。2.2 “家谱”里的关键分支build、frameworks、system、packages、vendorbuild这是AOSP构建系统的“大脑”。里面包含envsetup.sh、make相关的核心构建规则、各种产品配置模板。你要编译哪个产品、生成哪些镜像、如何打包ota包控制逻辑都在这里。source build/envsetup.sh这行命令是所有AOSP编译的第一步。frameworks这是整个AOSP里最庞大的目录之一也是做系统开发最常泡的地方。它分为base、av、native、opt等子目录。其中frameworks/base包含了Android框架层的核心代码像ActivityManagerService、WindowManagerService、SystemUI的父类、资源管理框架都在这里。我们平时写App用的SDK中的很多类在frameworks/base/core/java下都能找到对应源码。想改系统行为十有八九要改这里。system这里存放的是系统底层相关的组件比如core下的init进程系统的第一个用户态进程、logd日志守护进程、vold存储挂载、sepolicy安全策略等。它是连接内核和上层框架之间的基础系统服务层。如果你对“系统如何开机”感兴趣system/core/init是必看目录。packages内置应用的“老窝”。packages/apps下面有Settings设置、Launcher3桌面、Dialer拨号、Camera2相机等核心应用源码。注意这里的应用不是普通App它们是系统应用编译后会放进system.img或product.img里拥有普通App没有的权限。vendor这个目录最特别它是留给硬件厂商放私有代码的。芯片厂商高通、联发科等和整机厂商小米、三星等会把硬件驱动、HAL实现、私有库、权限配置文件放到这里。AOSP本身可以没有vendor目录但如果你要跑在一台真机上没有vendor补全驱动系统基本起不来。这部分值得单开一章。hardware硬件抽象层HAL的公共接口和参考实现。比如hardware/libhardware定义了HAL的规范接口hardware/ril是射频接口层。vendor里的驱动一般会实现hardware中定义的标准接口这样上层framework就不需要关心具体硬件是什么牌子了。device存放具体设备的板级配置。你经常看到的device/google/walleye这类路径就是针对Pixel 2的设备配置目录里面包含设备树、内核config、产品mk文件等。一个完整的AOSP项目通常会有若干device目录标识支持哪些设备。art、bionic、dalvik这些属于运行环境和基础库。art是Android RunTimeAndroid运行时负责App编译和垃圾回收bionic是Android专用的C库不是标准glibcdalvik是早期版本的Dalvik虚拟机现在主要保留一些历史代码和辅助工具。这些目录和普通应用开发者关系不大但如果你要深挖JVM层、优化性能绕不开它们。external、libcore、prebuiltsexternal存放了很多开源第三方组件比如sqlite、zlib、protobuflibcore是Java核心库的Android实现prebuilts存放编译工具链、预编译的二进制工具等。这三个目录更像是“补给仓库”不是主战场但缺乏它们任何编译都进行不了。abi、bootable、cts、developers、docs、test这些是兼容性适配和测试相关。bootable里有recovery恢复模式的源码cts是Google兼容性测试套件docs是文档。一般做产品定制时cts和bootable偶尔会用到。3. build.sh编译入口的“指挥棒”一文讲透怎么用3.1 build.sh到底是谁脚本位置与作用严格说AOSP官方仓库里找不到一个根目录的build.sh但你想快速编译又不想每次都手动敲那三行命令很多人就会自己写一个。很多第三方发行版也这么干把整个编译流程封装成一个脚本傻瓜式运行。以我习惯的方式典型的build.sh内容大概是这样的#!/bin/bash set -e source build/envsetup.sh lunch aosp_arm64-userdebug make -j$(nproc)这个脚本做了三件事加载编译环境、选择产品配置、开始全量编译。如果你下载的是某些基于AOSP深度定制的分支比如一些GSI或类原生项目作者会提供一个更复杂的build.sh里面可能包含了repo sync、选择版本号、自动打补丁、生成刷机包等步骤。所以你拿到一个项目第一件事就是打开build.sh看看它到底封装了什么。理解build.sh的关键不是背命令而是理解它背后那条编译链路。下面我把这条链路拆开。3.2 一条编译命令的背后envsetup、lunch、make的完整链路第一步source build/envsetup.sh这一步设置环境变量定义了各种便捷函数比如lunch、mm、mmm、croot等。必须用source执行因为要在当前shell里留驻这些函数如果直接运行子shell结束后啥都没了。第二步lunch参数通常是产品名-构建类型比如aosp_x86_64-eng、aosp_arm64-userdebug。lunch会做几件重要的事设置TARGET_PRODUCT、TARGET_BUILD_VARIANT、TARGET_ARCH等环境变量加载对应产品的AndroidProduct.mk还会检查编译环境的Python/Java版本等。构建类型有三种eng是工程师版权限最放开userdebug是带调试权限的用户版user是接近正式发布的版本很多调试工具在user下会被禁用。第三步make这里就是真正的构建了。-j指定并行任务数-j$(nproc)就是拿满所有CPU核心。没有特殊需求直接make就行产物默认输出到out/target/product/产品名/下能看到system.img、vendor.img、boot.img等镜像文件。如果不想全量编译只想编某个模块可以用mma、mmm或者make 模块名比如mmm frameworks/base/packages/SystemUI编完之后make snod可以快速把修改的系统应用打包进镜像省去重新全编的时间。这些命令都是AOSP开发日常的“保命技能”。3.3 编译产物与常见构建参数含表格编译产物的目录结构同样有着一套约定。out/是编译输出总目录out/host是宿主机工具比如aidl、protocout/target/product/设备名才是你最终要看的。里面常用内容如下产物/目录说明system.img系统分区镜像包含framework、系统应用、系统库vendor.img厂商分区镜像包含硬件驱动、HAL、私有so库boot.img内核ramdisk负责引导开机recovery.img恢复模式镜像system/bin编译好的可执行程序比如service、dumpsyssystem/frameworkframework的jar包和odexsymbols/带符号表的调试文件用于Crash栈回溯构建参数方面除了-j还有几个高频率参数make clean/make clobber全清输出目录一般换分支或改架构后用。make bootimage systemimage vendorimage只编指定镜像减少等待时间。dist生成可发布的完整包打包进out/dist/。TARGET_BUILD_VARIANTuserdebug/eng可以通过环境变量指定效果等同于lunch时的选项。我踩过的坑之一是改了一个模块后直接make全编等了半个多小时才发现其实只需单独编SystemUI。后续我会再分享几个省时间的招数。4. vendor目录设备厂商的“私有领地”也是新手最容易懵的地方4.1 vendor目录里到底放了什么在AOSP的语境里vendor专门用来存放设备相关的、不能开源的二进制文件或私有实现。因为Google不能把高通的GPU驱动源码、基带固件这些都开源所以这些东东以预编译二进制形式放在vendor里。vendor目录的常见子目录包括vendor/厂商名/设备名/具体设备的配置和驱动比如vendor/qcom/walleye。vendor/厂商名/modules/内核模块.ko文件和HAL实现库。vendor/厂商名/proprietary/厂商私有blob比如相机算法库、指纹识别库。vendor/etc/厂商配置文件比如audio_policy_configuration.xml、permissions/下面会放一些私有权限定义。这个目录的特点就是“只可意会不可编译”。很多代码都是二进制.so或.jar但这些二进制必须和AOSP上层框架的接口严格对应否则运行时会动态库找不到符号、或者接口版本不匹配导致崩溃。我们刷第三方ROM时报错“Fingerprint HAL is not supported”多半就是vendor里的指纹库和framework版本没对上。4.2 vendor与system、product分区的分工协作现在Android设备普遍采用动态分区物理分区被逻辑分区取代但概念上system、vendor、product仍然是三个独立区域编译时也是三种镜像。它们的职责很清晰system放Android框架、系统应用、系统库主要来自AOSP公共代码。vendor放设备厂商的硬件相关代码属于“私有土地”。product放产品定制化功能比如某个品牌预装的商城里、运营商定制App。上层framework通过HAL接口访问底层硬件。HAL接口定义在hardware/interfaces下而真正实现这些接口的so文件一般在vendor/lib64/hw/里。拿相机举例子App调用CameraManagerframework通过CameraService调用HIDL/AIDL到vendor实现的android.hardware.camera.provider2.x.so最终才驱动底层传感器。如果vendor缺失或者不匹配framework就算加载了也找不到底层实现表现为相机黑屏、无法打开或直接crash。4.3 如何判断你的源码里vendor是否完整很多人从网上下载了一个AOSP源码包编译后刷进设备结果卡在了开机动画。排除内核原因十有八九是vendor没有刷进去或者vendor分区里的驱动和内核不匹配。判断vendor是否完整有几个实用方法查看vendor/下是否有对应设备厂商的目录。如果只有一个空目录或者完全不存在说明这个AOSP只包含开源部分跑真机基本没戏。编译完成后检查out/target/product/设备/vendor.img是否生成。如果没生成就是没有执行make vendorimage或者产品mk里没有配置vendor分区。刷机后进入系统执行adb shell ls /vendor看内容是否和编译产物一致。还有种特殊情况很多基于AOSP做的“类原生”项目会把vendor直接打进system里所谓system-as-root这时没有单独的vendor分区但源码里仍然会有vendor目录编译逻辑会把内容合并到system中。看源码时别只凭名字下结论要看产品mk里对分区的定义。5. 深挖源码里的“主力部队”framework、SystemUI和内置应用5.1 frameworks/base所有App的地基frameworks/base是AOSP源码里信息密度最高的目录没有之一。这里既有Java层框架也有部分native实现。子目录非常多我按理解顺序帮你划重点core/javaAndroid Java框架的“核心库”包含android.app.Activity、android.content.Context、android.os.Binder等所有开发者耳熟能详的类的实现。严格说SDK里的android.jar只是API空壳真正实现在这里。services系统服务实现其中core/java/com/android/server下几乎都是各种*Service.java比如ActivityManagerService、WindowManagerService、PackageManagerService等。系统开机的过程本质就是SystemServer启动这些服务。packages/SystemUI看路径名也知道这是SystemUI源码所在。严格说SystemUI本来是frameworks下的一个特殊应用现在很多分支里把SystemUI挪到了packages/apps/SystemUI不过老分支仍在frameworks/base/packages/SystemUI。libs一些系统库比如libandroidfw资源管理实现、libhwbinder等。media媒体框架包括音频、视频、DRM相关Java层和native层。如果你想知道startActivity到底经历了什么可以从Activity类的startActivity一直追到ActivityTaskManagerService再追到ActivityStarter、TaskSnapshotManager这条链路能让你对App启动机制有极其深的理解。平时我们调试性能问题也主要看这些代码。5.2 SystemUI状态栏、通知栏这些“表面功夫”怎么改SystemUI承载了状态栏、导航栏、通知面板、锁屏、截图、音量面板、录屏等等“界面上的系统功能”。改成它就是改ROM里最常见的需求比如修改手机顶部状态栏时间居中、微信通知气泡样式、下拉快捷开关数量等。SystemUI的代码结构里最常改的是src/com/android/systemui/statusbar/状态栏和通知栏相关。src/com/android/systemui/qs/快速设置磁贴下拉开关。src/com/android/systemui/navigationbar/三大金刚键导航栏。res/layout/布局文件想动UI布局基本都在这里改。修改SystemUI后编译不会重新生成系统镜像但可以快速验证。我常这么干source build/envsetup.sh lunch aosp_arm64-userdebug mmm packages/apps/SystemUI adb root adb remount adb sync adb reboot注意早期分支SystemUI在frameworks/base/packages/SystemUI编译命令路径要跟着调整。改SystemUI最怕的是资源ID没清理导致编译报错所以改完别忘了同步更新res/values/public.xml老版本需要新版本自动生成否则编译时会出现资源找不到的诡异问题。5.3 packages/apps里的宝藏Settings、Launcher等packages/apps下放着很多系统应用它们和普通应用的区别在于它们使用系统权限和内部API编译时是把源码直接编进系统镜像的。其中最有意思的是Settings。设置应用的代码量不小涉及系统设置项的展示逻辑、存储方式。如果你想在设置里加一个自定义开关一般要先定义SettingsProvider的配置项然后在Settings里加布局和逻辑还要在framework的Settings.Global或Settings.Secure里声明字段。这个流程走一遍你对Android的“设置中心化”机制就透彻了。Launcher3是默认桌面改动最多的是桌面布局、手势动画和App图标。Camera2是相机的参考实现功能相对简陋但胜在源码清晰适合学习相机流水线。此外还有DeskClock、Calendar、Contacts等不过这些并不是所有AOSP分支都自带有些被移到了packages/providers里。这里有个通用技巧找内置应用先看packages/apps找不到就去packages/providers或frameworks/base/packages下面碰碰运气。6. 从源码到镜像实操笔记与问题排查6.1 第一次完整编译前要准备什么严格的环境要求是很多新手被劝退的第一关。以我自己常用的Ubuntu 20.04为例需要准备磁盘空间至少300GB编译目标超过800GB也不奇怪。请用固态硬盘机械硬盘能让一次全编时间翻倍。内存16GB起步32GB更稳妥。内存不足时编译进程会被杀掉日志里出现Killed字样十分心累。安装必要依赖openjdk-8-jdk老版本或openjdk-11-jdk、git-core、gnupg、flex、bison、build-essential、zip、curl、libncurses5-dev等等。不同Android版本依赖有差异直接按官方文档装全套最省事。安装repo工具配置好git用户名和邮箱。然后是同步源码。方式有两种一种是直接下载每天生成的镜像压缩包另一种是repo init一个manifest分支再repo sync -c。后者可以挑选历史稳定分支更灵活但下载量大容易中断需要网速给力。推荐用repo sync时加-j4避免并发太高触发服务器限流。同步完成后事情还没完。你还需要检查git仓库的repo status是否干净因为很多编译脚本要求工作区没有修改。如果打了自己的补丁也要先确认补丁不会影响构建。6.2 常见坑Java版本、内存不足、vendor daemon问题Java版本不匹配AOSP各版本对JDK版本有硬性要求。Android 8以下用OpenJDK 8Android 10以上很多也要OpenJDK 8到Android 12/13可以用OpenJDK 11。如果你用Ubuntu 22.04默认OpenJDK 17去编老版本大概率会报错Unsupported class file major version或编译到中间突然失败。解决办法是安装对应JDK并配置update-alternatives或者修改build/soong的全局配置。注意不要用Oracle JDK去编新版会遇到各种证书和加密算法问题。内存不足全量编译时内存占用非常恐怖。如果机器只有8GBsoong构建系统经常会报OOM或直接被内核杀掉。经验值是至少16GB起步低于这个标准你就别挑战make -j32了老老实实make -j2碰运气吧。我有一台16GB内存的机器编AOSP时还要额外开大swap才勉强过关。不过就算有了swap速度也会慢得怀疑人生。经济允许的话内存越高越好。vendor daemon问题如果你编的是带VINTF校验的较新版本可能会遇到类似提示vd is starting, please check vendor daemons status in debug log这通常不是编译错误而是在启动时动态系统校验vendor接口的提示。在AOSP源码里/vendor/bin/vndservicemanagerVendor Service Manager没起来或者HAL接口对不上时就会出现这类消息。排查时先看adb logcat -b all | grep vnd确认是哪些HAL服务加载失败再去vendor源码里补接口或加对应许可文件。很多时候只是因为vndservicemanager没有权限访问某配置文件。别把PHP的vendor目录带入AOSP网上搜“vendor目录”时很容易搜出一堆PHP的vendor/autoload.php。AOSP的vendor和Composer的vendor完全不是一回事。你要是看到vendor/autoload.php请立刻退出那是在说PHP的第三方依赖目录。AOSP的vendor下面不会出现autoload.php而是以硬件厂商和HAL模块为主。这个混淆对新手影响很大搜资料时建议加上关键词“Android”或“AOSP”。6.3 快速定位源码目录的实用命令与个人心得AOSP源码那么大找文件不能靠肉眼翻。几个命令能救命# 全局搜类名 grep -r class SystemUI frameworks/base/packages/SystemUI --include*.java # 查看某个模块的编译配置 cat packages/apps/Launcher3/Android.bp | head -50 # 找到产品mk find device -name *.mk | head # 进入源码根目录的快捷方式 crootcroot是envsetup.sh里最实用的命令可以直接回到源码根目录省得在深层目录里迷失。mma当前目录所在模块mm更纯粹只编当前目录如果你改了SystemUI就直接cd进去执行mm输出更快。还有个技巧AOSP的索引文件不是很多社区说的.repo/manifest.xml真正的仓库清单在.repo/manifests/下你可以用repo manifest重新生成一个当前分支的清单内容非常清晰适合研究每个目录来自哪个git仓库。如果你想了解某个模块对应哪些仓库看这个清单比看网页方便多了。实际编译调优心得不要一上来就想着全量编译先试着编一个模块比如m libc跑通整个工具链。然后再make全量能少走很多弯路。另外编译时用ccache可以加速后续构建设置export USE_CCACHE1并把缓存目录放在SSD上第二次编译会快很多。首次编译如果成功后面修改一个模块再编镜像几乎只需要几分钟。我还建议你养成看编译日志的习惯。out/soong.log和模块目录下的ninja_log记录了每次编译的详细过程报错时不要只盯着终端最后那几行去日志里搜索error:或FAILED:通常能找到更本质的原因。最后再说一个小技巧源码下多了之后Git仓库状态会变得乱七八糟。定期执行repo forall -c git gc --aggressive --prunenow可以给所有子仓库清理垃圾对象减少磁盘占用。换分支前最好先repo abandon 分支名放弃本地修改而不是直接切分支否则经常会出现一堆冲突提示。AOSP这套系统虽然庞大但只要把顶层目录当成一张家谱图从build.sh的入口走进来沿着system、frameworks、packages、vendor这几条主线逛一圈你会发现它并没有想象中那么神秘。真正动手编一次镜像、改一次SystemUI积累下属于自己的编译命令集和排障清单以后再进入任何一个基于AOSP的项目你都能很快找到自己的位置。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →