刚接触 Android 系统定制时,最难的往往不是改代码,而是听懂同事说的话:"去 device 里看产品配置""这个 APK 落在 product 分区""刷 vendor_boot 试试""先确认是不是 A/B 设备"。这些词跨越了源码、构建、设备分区和启动流程四个层面,名字还经常相似。
本文把它们放到一张地图里。文中的 "ASOP" 按常见写法应为 AOSP(Android Open Source Project)。下文以现代 Android 设备为主;具体目录、镜像和刷写方式始终以目标设备的源码、分区表及厂商文档为准。
一、先记住四层:源码目录 ≠ 镜像文件 ≠ 设备分区
text
源码树 构建配置与工具 编译输出 设备
frameworks/ device/、vendor/ out/target/product/... system 分区
packages/ ──→ Android.bp、*.mk ──→ system.img、vendor.img ──→ vendor 分区
system/ Soong / Make / Ninja boot.img、super.img boot / super 等
例如 packages/apps/Settings/ 是源码目录;编译得到 Settings APK;构建系统依据产品配置把它安装到某个镜像的文件树;刷机或 OTA 再把镜像内容带到设备上。源码目录的名字不自动决定最终落在哪个分区。
以后遇到一个陌生路径,先问它属于哪层:是源码、构建脚本、out/ 下的临时安装目录,还是设备运行时的挂载路径?这个问题能省掉很多无效搜索。
二、AOSP、Repo、Product:先选"编什么设备"
AOSP 与厂商 BSP
AOSP 是 Android 的开源基础,包含 Framework、系统服务、应用、构建系统及大量通用代码。能编出某个 Android 版本,不等于它直接支持任意手机。具体设备通常还需要厂商提供的 BSP(Board Support Package,板级支持包)、内核、设备树、驱动、HAL 实现和专有二进制文件。
一个商业设备的代码常见组成是:
text
AOSP 通用代码 + 芯片平台代码 + 设备配置 + 厂商私有组件 + 产品定制
因此,拿公开 AOSP 源码直接刷到陌生量产手机,通常既无法启动,也可能破坏现有系统。开发时要使用与设备型号、硬件版本及对应分支匹配的源码和镜像。
Repo、Manifest、Git 仓库
AOSP 由许多 Git 仓库组成,Repo 负责按 Manifest 清单把它们组织成一棵源码树。Manifest 描述仓库 URL、路径与修订版本;repo sync 同步的是一组仓库,而不是一个巨大的单仓库。.repo/ 保存 Repo 元数据和 Manifest,通常不手改其内部数据。
需要追查"这个目录来自哪个仓库"时,可先看 Manifest,再在目标目录执行 git remote -v、git log。项目内的 local_manifests/ 可能额外引入设备或厂商仓库。
Product、Device、Board、Variant
这几个词经常一起出现,但各管一件事:
| 名词 | 主要回答的问题 | 常见位置 |
|---|---|---|
| Product(产品) | 要把哪些模块、属性和资源打进系统? | device/<厂商>/<设备>/*.mk、AndroidProducts.mk |
| Device(设备) | 这台设备的具体配置是什么? | device/<厂商>/<设备>/ |
| Board(板级配置) | 分区、架构、内核和底层构建参数是什么? | BoardConfig.mk 及其包含文件 |
| Variant(构建变体) | 调试能力和安全限制如何选择? | user、userdebug、eng |
lunch 选中的通常是"产品 + 变体",具体语法与候选项取决于 Android 版本和项目配置。进入源码树后,可用以下命令查看当前工程实际提供的目标:
bash
source build/envsetup.sh
lunch
不要从别人的文章直接复制产品名;产品名必须在当前源码树里存在。
user 面向正式交付;userdebug 更接近正式系统但保留部分调试能力;eng 更偏工程调试。调试版上的行为不能直接当成正式 user 版结论,例如 adb root 和分区可写性都可能不同。
三、源码树里常见目录是什么意思
下面是常见路径的"第一印象"。不同 Android 分支会移动代码,厂商源码树还可能增加自定义目录。
| 路径 | 常见内容 | 你一般什么时候看它 |
|---|---|---|
build/ |
构建入口、产品配置、Make/Soong 相关逻辑 | lunch、编译变量或打包行为异常 |
build/soong/ |
Soong 构建系统与 Blueprint 处理 | Android.bp 模块解析、依赖问题 |
build/make/ |
Make 侧构建规则与产品规则 | 产品继承、镜像生成、传统 .mk 配置 |
device/ |
具体设备的产品配置、板级配置、设备专属文件 | 新增产品、改预装、资源覆盖、分区配置 |
vendor/ |
厂商实现、专有组件或厂商产品配置 | HAL、驱动配套服务、私有库;公开源码树可能缺失 |
hardware/ |
硬件接口与通用硬件相关代码 | HAL 接口与硬件抽象层 |
frameworks/ |
Framework、系统服务、Java API 等 | 改系统行为、权限、窗口、资源管理 |
packages/apps/ |
Settings、Launcher 等系统 App 的源码 | 改系统 App 界面与逻辑 |
packages/modules/ |
可模块化更新的系统组件源码 | 网络、权限等模块的定制与排查 |
system/ |
init、核心 native 组件、系统工具等源码 |
启动、属性、底层系统行为 |
external/ |
引入的第三方开源库 | 依赖或第三方组件问题 |
prebuilts/ |
预编译工具链和二进制依赖 | 编译器、JDK、工具版本排查 |
kernel/、设备内核目录 |
内核源码或构建配置,布局依厂商而异 | 驱动、内核镜像、设备树问题 |
bootable/ |
启动和恢复相关代码 | Recovery 等启动路径排查 |
out/ |
中间文件与最终构建产物 | 找 APK、镜像、OTA 包、编译日志 |
.repo/ |
Repo 元数据和 Manifest | 查仓库来源、分支与同步状态 |
这里最容易混淆的是源码树的 system/ 和设备上的 /system:前者是一组源码仓库,后者是文件系统挂载点或逻辑分区内容。源码树 vendor/ 与设备 /vendor 也不是简单的一一对应关系。
Android.bp、Android.mk 与产品配置文件
Android.bp:Soong 的模块描述文件,常见模块类型有cc_binary、java_library、android_app。它定义"这个模块怎样编译"。Android.mk:传统 Make 模块描述文件,很多老代码和厂商代码仍在使用。device.mk/ 产品.mk:通过PRODUCT_PACKAGES、PRODUCT_COPY_FILES等变量选择"哪些模块或文件进入某个产品"。BoardConfig.mk:板级参数,可能涉及架构、镜像、分区与内核构建。AndroidProducts.mk:声明该目录提供哪些产品,并为lunch提供候选目标。*.rc:init配置,定义服务何时启动、执行动作及属性触发;它不是普通 shell 脚本。*.te、file_contexts:SELinux 策略及文件安全上下文,系统服务新增文件或访问路径时经常需要配套修改。
可以把它理解为:Android.bp 说"模块怎么做",产品配置说"这个产品要不要它",BoardConfig.mk 说"这块板子怎么打包和启动"。
四、从修改源码到找到产物
一条典型的开发路径是:
text
修改源码或产品配置
↓
选择 lunch 目标
↓
m <模块名> 或 m 构建目标
↓
构建系统把文件安装到 out/target/product/<产品>/ 下的分区文件树
↓
生成镜像 / OTA 包
↓
刷机或升级,验证设备运行结果
常见输出目录:
text
out/target/product/<产品>/
├── system/ # system 的安装文件树
├── system_ext/ # 如设备配置包含此分区
├── product/ # 产品文件树
├── vendor/ # 厂商文件树
├── odm/ # 如设备配置包含此分区
├── boot.img
├── vendor_boot.img # 视设备和 Android 版本而定
├── system.img
├── vendor.img
├── product.img
├── super.img # 动态分区设备可能生成
└── ...
这些文件是否存在、叫什么名字,取决于设备的分区方案和构建目标。out/target/product/<产品>/system/ 是构建机上的安装文件树 ,不是设备已经挂载的 /system。
例如修改 Settings:源码可能在 packages/apps/Settings/,但打出的 APK 装进哪个分区,应检查模块属性和产品配置,或在设备上用 adb shell pm path com.android.settings 看实际安装路径。仅凭源码路径猜"要刷 system.img"并不可靠。
五、系统镜像和分区:最常见的名字
镜像(image) 是构建或发布得到的文件;分区(partition) 是设备存储上的逻辑区域。镜像通常用于填充相应分区,但 Android 设备的分区组织并不固定。
| 镜像或分区 | 作用 | 定制时常见变化 |
|---|---|---|
boot.img / boot |
与内核和启动相关;具体内容因设备代际而异 | 内核、启动参数等 |
init_boot.img / init_boot |
部分较新设备把通用 ramdisk 放在这里 | init 启动相关内容;并非所有设备都有 |
vendor_boot.img / vendor_boot |
厂商启动 ramdisk 等设备相关启动内容 | 厂商 init 配置与启动组件 |
dtbo.img / dtbo |
Device Tree Overlay,描述硬件差异 | 板级硬件配置;不是所有设备都有独立分区 |
vbmeta.img / vbmeta |
Android Verified Boot(AVB)校验元数据 | 镜像签名与验证链 |
system.img / system |
Android 通用系统内容 | Framework、部分系统服务和 App |
system_ext.img / system_ext |
扩展系统内容,放系统相关但不属于通用 system 的组件 |
OEM/平台扩展 |
product.img / product |
面向产品的应用、资源和配置 | 预装 App、产品资源、属性 |
vendor.img / vendor |
芯片或设备厂商实现 | HAL 实现、厂商服务与库 |
odm.img / odm |
设备制造商的硬件定制内容 | 设备级硬件配置和实现 |
super.img / super |
动态分区的物理容器 | 承载若干逻辑分区的布局和内容 |
userdata.img / userdata |
用户数据初始化镜像;实际 /data 会持续写入 |
恢复出厂、开发测试;不能当系统代码分区 |
metadata |
加密、检查点等元数据,依设备实现 | 通常不直接手工修改 |
这张表是理解职责的起点,不是"每台设备都必须刷这些镜像"的清单。特别是启动相关镜像,分工随设备首发 Android 版本、GKI 方案和厂商实现而变化;不能把某台设备的 boot.img 用在另一台设备上。
super、动态分区与 fastbootd
采用动态分区 的设备,system、vendor、product、system_ext、odm 等可以作为 super 中的逻辑分区,实际集合由设备配置决定。这样系统升级时可以调整逻辑分区的大小,而不必为每个分区永久划死空间。
super.img 并不意味着设备上只有一个可见的 /super 文件夹;系统仍会把逻辑分区挂载为 /system、/vendor 等路径。super.img 也不一定是开发中最合适的单刷对象,要按设备发布包和刷写脚本决定。
Bootloader fastboot 在 Android 启动前工作;fastbootd 是用户空间 fastboot,常用于刷写动态逻辑分区。某设备在 bootloader 模式下不能刷 system,并不一定是镜像有问题,可能是需要进入 fastbootd。设备是否支持、如何切换,应先查官方刷机说明。
A/B 槽位与 Virtual A/B
A/B 设备有两组可切换的启动槽位,分区名可能带 _a、_b。系统通常从当前活动槽位启动,并把 OTA 更新写到另一槽位,重启后切换。Virtual A/B 进一步通过快照机制减少物理分区重复;它仍保留"槽位与可回退更新"的概念,但内部布局不能只凭 _a、_b 推断。
刷错槽位、只更新某个启动镜像却没同步相配套的分区,可能造成启动失败。检查设备时先读出实际槽位,不要假定所有设备都是 A/B,也不要假定当前槽位永远是 a。
六、Treble、HAL、GKI、VINTF、AVB:它们解决什么问题
| 缩写 / 名词 | 一句话解释 | 排查关联 |
|---|---|---|
| Project Treble | 把 Android Framework 与厂商实现之间的边界规范化 | 升级系统时的接口兼容性 |
| HAL(硬件抽象层) | Framework/系统服务访问硬件功能的约定接口及其实现 | 相机、音频、传感器等功能异常 |
| AIDL / HIDL | 描述跨进程接口的技术;现代 HAL 越来越多采用 AIDL | 接口版本、客户端和服务端兼容 |
| VINTF | 通过设备 Manifest 与兼容性矩阵声明 Framework 和厂商接口要求 | 启动兼容性校验、HAL 服务声明 |
| GKI | 通用内核镜像思路,减少 Android 内核与厂商模块的耦合 | 内核、模块、启动镜像配套 |
| AVB | Android Verified Boot,验证启动链与分区内容 | 改镜像后校验失败、无法启动 |
| SELinux | 对进程和文件访问实施强制访问控制 | 服务启动但被 avc: denied 拒绝 |
| RRO | Runtime Resource Overlay,运行时资源覆盖 | 改界面文案、布尔配置、尺寸等资源 |
这几个概念经常在同一个问题里同时出现。例如把一个相机 HAL 服务放进 vendor 后,仍可能需要 VINTF 声明、init 服务定义和 SELinux 策略;只把二进制编译出来不等于设备能正常使用它。
七、"烧录镜像"到底指哪种操作
在 Android 开发里,"烧录"通常是口头说法。实际操作至少有四类:
| 方式 | 写入对象 | 常见用途 | 要特别确认 |
|---|---|---|---|
| Bootloader fastboot | 启动链或设备支持的分区 | 工程机、官方工厂镜像 | 设备型号、解锁状态、槽位 |
| fastbootd | 动态分区中的逻辑分区 | 刷 system、vendor 等 |
当前是否处于用户空间 fastboot |
| Recovery / OTA sideload | OTA 包,由升级程序决定写哪些分区 | 正式升级、整包验证 | 签名、版本与升级路径 |
| 厂商下载工具 | 芯片平台定义的完整固件包 | 量产、救砖、底层刷写 | 专用包、线材、权限、设备硬件版本 |
adb 主要与已启动的 Android 系统 通信;fastboot 与 bootloader 或 fastbootd 通信。它们是不同阶段的工具。adb push 一个文件到设备,也不等于把它做成了可持久、可验证的系统镜像。
刷写前的只读检查
下面的命令只用于识别设备状态,输出因设备而异:
bash
adb devices
adb shell getprop ro.product.device
adb shell getprop ro.build.fingerprint
adb reboot bootloader
fastboot devices
fastboot getvar product
fastboot getvar current-slot
fastboot getvar is-userspace
adb reboot bootloader 会重启进入 bootloader;若设备还未启动,直接从 fastboot devices 开始。部分 getvar 在某些设备上会返回未知,不能仅靠一个变量下判断。
随后核对:
- 固件包与设备准确型号、硬件版本和分区方案匹配;
- 镜像来自同一套构建,启动镜像与
system/vendor等相互兼容; - 是否 A/B、当前槽位是什么、目标分区是物理还是
super内的逻辑分区; - Bootloader 解锁是否允许,以及解锁或刷机是否清除用户数据;
- 刷机包脚本是否包含清除
userdata、变更槽位或锁定 Bootloader 的操作; - 是否有该设备官方的恢复包和数据备份。
概念上,单分区刷写是 fastboot flash <分区名> <匹配镜像>;动态逻辑分区可能需要先进入 fastbootd。这里不提供可直接执行的固定分区命令,因为同名分区在不同设备上的槽位、校验链和刷写阶段可能不同。实际操作应以该设备对应版本的工厂镜像脚本或厂商说明为准,并先读懂脚本内容。也不要把"关闭验证"或"重锁 Bootloader"当成通用修复方式。
OTA 与工厂镜像的区别
工厂镜像 通常面向初始化或维修,可包含多个分区镜像和刷写脚本,可能清除用户数据。OTA 包面向从一个已知版本升级到另一个版本,包含校验、签名、分区更新与版本约束,A/B 设备还可能使用快照。两者不能只看文件扩展名来互换使用。
在做产品发布时,应把"开发机临时刷通"与"用户设备能安全升级"分开验证。新增系统 App、修改分区大小、调整 HAL 或内核时,尤其需要完整的 OTA 路径测试。
八、三个常见定制需求,从哪里开始找
1. 修改系统设置界面或预装 App
先在 packages/apps/ 及厂商 App 仓库找到源码;再看其 Android.bp 或 Android.mk、产品 PRODUCT_PACKAGES,确认模块是否参与当前产品;编译后查看安装路径。若是系统特权 App,还要检查签名、权限白名单和对应分区。新 App 仅复制到 /system/priv-app 不保证能获得特权权限。
2. 修改开机默认配置或资源
先判断是系统属性、Framework 资源、产品资源还是 RRO。搜索配置定义与使用点,再查产品继承链、overlay 优先级和最终编译产物。运行时可用 adb shell getprop 或资源相关工具核对结果。多个 overlay 命中同一资源时,必须确认目标包、优先级和设备配置。
3. 增加一个硬件服务
从接口定义、厂商实现、init 服务、VINTF 声明、SELinux 策略及产品模块列表逐项确认。编译通过后,再看启动日志、服务注册状态和 avc: denied。这类功能常跨 hardware/、vendor/、device/,很少只改一个文件就完成。
九、常见报错如何反推所在层
| 现象 | 优先怀疑的层 | 先看什么 |
|---|---|---|
lunch 找不到目标 |
产品配置 | AndroidProducts.mk、Manifest 是否同步齐全 |
m 报模块不存在 |
构建描述 | Android.bp/Android.mk、模块名、Soong namespace |
| 编译成功但设备没有文件 | 产品打包 | PRODUCT_PACKAGES、安装分区、out/target/product/... |
| 刷写提示 partition 不存在 | 分区/刷写阶段 | 设备分区表、A/B 槽位、是否需 fastbootd |
| 启动卡在 Logo 或验证失败 | 启动链/校验 | 镜像是否同套、AVB、boot/vendor_boot/vbmeta 配套 |
| 服务在但调用失败 | 接口/权限 | VINTF、HAL 版本、SELinux、服务日志 |
| 本地刷机正常但 OTA 失败 | 发布流程 | 签名、增量基线、分区变化、包版本与快照状态 |
排错时保持"从现象回到层次"的思路:文件没进镜像,就先查构建配置;文件进了镜像却没生效,再查挂载、属性、权限或服务启动;镜像甚至刷不进去,先查设备分区和刷写模式。
十、入门时最值得记住的六句话
- AOSP 源码不等于某台手机的完整固件。 设备还需要配套的内核、厂商组件和配置。
- 源码目录不等于设备分区。 要用模块配置和最终安装路径确认文件落点。
out/target/product/<产品>/是构建产物入口。 从这里追到镜像与 OTA 包。super是动态分区的容器,system等逻辑分区仍各有职责。- fastboot、fastbootd 与 OTA 服务于不同刷写阶段。 先认设备与包,再选工具。
- 镜像、签名、槽位和硬件版本必须匹配。 单刷某个文件成功,不代表整机可启动或可升级。
如果还没有搭好源码与构建环境,可以先读本项目的 AOSP 源码编译环境搭建指南。建立可重复的构建流程后,再用本文的"源码 → 配置 → 产物 → 分区 → 设备"路径定位每一次定制改动。