电子说
本节学习目标
学完本章后,你将能够回答:
□Android 构建系统的三个核心组件是什么?
□./build.sh -UKAop 每个参数的含义是什么?
□如何初始化编译环境?
□user、userdebug、eng 三种构建变体的区别?
□如何单独编译一个模块?
□如何用 ccache 加速编译?
1.1 构建系统是什么
1.1.1 概念:构建系统的作用
构建系统就是把我们的源码变成可运行的镜像文件的完整工具链。
1.1.2 Android 构建系统的演进

三者关系图:

简单理解:
•Android.bp = 新版菜谱(语法简洁,推荐)
•Android.mk = 旧版菜谱(兼容旧代码)
•Ninja = 高效厨师(替代了 GNU Make)
•Soong = 菜谱翻译器(把 Android.bp 翻译给 Ninja)
1.1.3 Android 编译产物
编译完成后,你会得到以下文件(在out/target/product/rk3576_u/ 目录下):
treeout/target/product/rk3576_u/├── boot.img ← 内核 + ramdisk (启动必需)├── dtbo.img ← 设备树 Overlay├── vbmeta.img ← AVB 验证元数据├── super.img ← system + vendor + odm + product 的合集│ ├── system.img ← Android Framework、系统应用│ ├── vendor.img ← HAL、vendor 库、Init 脚本│ ├── odm.img ← ODM 定制内容│ └── product.img ← 产品特定内容└── userdata.img ← 用户数据分区 (f2fs)
这些镜像文件最终会被rockdev/Image-rk3576_u/ 目录链接或复制,用于烧写到设备。
实战:查看编译产物
# 进入输出目录cd out/target/product/rk3576_u/# 查看镜像文件大小ls -lh *.img# 查看 system.img 的文件系统类型file system.img
1.2 编译环境准备
1.2.1 系统要求

涉及的文件:
•build.sh:99 - export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
1.2.2 安装依赖包
# Ubuntu 20.04/22.04 依赖安装sudo apt-get updatesudo apt-get install -ygit-core gnupg flex bison build-essentialzip curl zlib1g-dev libc6-dev libncurses5-devx11proto-core-dev libx11-dev lib32z1-devlibgl1-mesa-dev libxml2-utils xsltproc unzippython3 python-is-python3 openjdk-8-jdkbc ccache# 验证 Java 安装java -version# 应该输出 openjdk version "1.8.x"# 验证 Python 安装python3 --version
1.2.3 常见错误及解决
|
错误信息 |
原因 |
解决 |
|
build/envsetup.sh: line xx: java: command not found |
未安装 JDK |
sudo apt-get install openjdk-8-jdk |
|
flex: command not found |
缺少 flex |
sudo apt-get install flex |
|
No space left on device |
磁盘空间不足 |
清理磁盘或扩展分区 |
|
Out of memory error |
内存不足 |
减少 -J 参数,如 ./build.sh -A -J8 |
1.3 build.sh 主构建脚本
1.3.1 build.sh 是什么
build.sh 是 Rockchip 封装的编译入口脚本,它:
1.解析命令行参数
2.调用 U-Boot、Kernel、Android 各自的编译流程
3.打包最终镜像
涉及的文件:
•build.sh - 主构建脚本
1.3.2 参数逐一解析
build.sh 接受的参数如下:
./build.sh [-U] [-C] [-K] [-A] [-B] [-p] [-o] [-u] [-v VERSION] [-d DTS] [-V VERSION] [-J JOBS]
|
参数 |
含义 |
触发编译 |
说明 |
|
-U |
编译 U-Boot |
u-boot/ |
Bootloader |
|
-C |
用 Clang 编译内核 |
kernel-6.1/ |
默认 Clang |
|
-K |
编译内核 |
kernel-6.1/ |
包含 Clang |
|
-A |
编译 Android |
frameworks/, packages/, hardware/ |
Framework + HAL + 应用 |
|
-B |
编译 A/B 镜像 |
- |
仅 A/B 分区设备使用 |
|
-p |
打包镜像 |
- |
把 out/ 下的镜像复制到 rockdev/Image-xxx/ |
|
-o |
编译 OTA 包 |
- |
生成 OTA 升级包 |
|
-u |
编译 update.img |
- |
生成 RKDevTool 烧写包 |
|
-v |
构建变体 |
- |
user 或userdebug |
|
-d |
内核设备树 |
- |
指定 DTS 文件名 |
|
-V |
构建版本 |
- |
版本号字符串 |
|
-J |
并行任务数 |
- |
默认 16 |
重要提示:这些参数可以组合使用!比如:
•-UK = 编译 U-Boot + 内核
•-UKA = 编译 U-Boot + 内核 + Android
•-UKAp = 编译全部 + 打包镜像
1.3.3 实战:首次完整编译
# 步骤 1: 进入源码根目录cd z:/RK72/rk3576_android15# 步骤 2: 初始化环境source build/envsetup.sh# 步骤 3: 选择构建目标lunch rk3576_u-userdebug# 步骤 4: 开始完整编译./build.sh -UKAop# 等待编译完成... 首次编译可能需要 2-4 小时
1.3.4 常见编译场景
# 场景 1: 首次完整编译(所有组件)./build.sh -UKAop# 场景 2: 只修改了内核代码,重新编译内核./build.sh -Kp# 场景 3: 只修改了 Framework/HAL 代码,重新编译 Android./build.sh -Ap# 场景 4: 完整编译 + OTA + update.img./build.sh -UKAopu# 场景 5: 指定构建变体和 DTS./build.sh -UKAp -v userdebug -d rk3576-evb1-v10
1.3.5 编译日志解读
编译过程中,日志会输出到终端。关键信息:
[ 0% 1/23456] ... ← 进度指示 (百分比 + 文件序号/总数)ninja: build stopped: subcommand failed. ← 编译失败build completed successfully ← 编译成功
如果编译失败,看最后几行输出:
•error: 后面跟的是错误原因
•ninja: 后面跟的是失败的构建规则
1.3.6 案例分析:编译内存不足
症状:编译到 80% 时突然被 Killed,没有任何错误信息。
原因:系统内存不足,Linux 的 OOM Killer 杀掉了编译进程。
解决:
# 方法 1: 减少并行任务数./build.sh -A -J8 # 用 8 个并行任务替代默认的 16 个# 方法 2: 增加 swapsudo fallocate -l 8G /swapfilesudo chmod 600 /swapfilesudo mkswap /swapfilesudo swapon /swapfile# 方法 3: 编译单个模块代替全量编译mmm hardware/rockchip/audio/
1.4 envsetup.sh + lunch
1.4.1 概念:环境初始化做了什么?
第一步:加载构建环境source build/envsetup.sh输出类似:including device/rockchip/rk3576/rk3576_u/vendorsetup.shincluding device/rockchip/common/vendorsetup.sh...
envsetup.sh 做了两件事:
1.加载所有 vendorsetup.sh - 每个产品的 vendorsetup.sh 把自己的产品注册到 lunch 菜单
2.注册辅助命令 - 如 m, mm, mmm, croot, cgrep, godir 等
验证:
# 查看注册了哪些辅助命令help | grep "^ - "
1.4.2 lunch 命令做了什么?
# 第二步:选择构建目标lunch rk3576_u-ap4a-userdebug
lunch 命令设置了以下环境变量(打印输出可以看到):
============================================PLATFORM_VERSION_CODENAME=RELPLATFORM_VERSION=15TARGET_PRODUCT=rk3576_uTARGET_BUILD_VARIANT=userdebugTARGET_BUILD_TYPE=releaseTARGET_ARCH=arm64TARGET_ARCH_VARIANT=armv8-aTARGET_CPU_VARIANT=genericTARGET_CPU_VARIANT_RUNTIME=cortex-a72TARGET_2ND_ARCH=TARGET_2ND_ARCH_VARIANT=HOST_ARCH=x86_64BUILD_ID=AP3A.240905.015OUT_DIR=outPRODUCT_OUT=out/target/product/rk3576_u============================================
关键变量解读:
|
变量 |
值 |
含义 |
|
TARGET_PRODUCT |
rk3576_u |
产品名称,决定加载哪些配置 |
|
TARGET_BUILD_VARIANT |
userdebug |
构建变体(调试版本) |
|
TARGET_ARCH |
arm64 |
CPU 架构 |
|
TARGET_CPU_VARIANT_RUNTIME |
cortex-a72 |
运行时 CPU 优化目标 |
|
PRODUCT_OUT |
out/target/product/rk3576_u |
编译输出目录 |
涉及的文件:
•build/envsetup.sh - 环境初始化脚本
•device/rockchip/rk3576/rk3576_u/rk3576_u.mk - 产品主配置
1.4.3 构建变体的区别
|
特性 |
user |
userdebug |
eng |
|
用途 |
生产发布 |
开发调试 |
工程测试 |
|
root 权限 |
无 |
有 (adb root) |
有 |
|
ADB 调试 |
受限 |
完整 |
完整 |
|
调试符号 |
剥离 |
保留 |
保留 |
|
模块编译 |
精简 |
包含更多 |
全部 |
|
logcat |
受限 |
完整 |
完整 |
|
编译优化 |
最优 |
较优 |
较少 |
|
SELinux |
enforcing |
enforcing |
permissive |
推荐:日常开发使用userdebug。
1.4.4 常见错误
|
错误 |
原因 |
解决 |
|
lunch: command not found |
未执行source build/envsetup.sh |
先执行source build/envsetup.sh |
|
lunch 找不到rk3576_u |
产品配置未加载 |
检查vendorsetup.sh 是否存在 |
|
编译到一半变体不对 |
lunch 选错了 |
重新lunch rk3576_u-userdebug |
1.5 模块编译
1.5.1 m/mm/mmm 命令区别
envsetup.sh 注册了三个编译命令:
|
命令 |
用法 |
说明 |
|
m |
m -j16 |
从任意目录编译整个项目(类似make) |
|
mm |
cd xxx && mm |
编译当前目录的模块 |
|
mmm |
mmm path/to/module |
编译指定目录的模块 |
实战对比:
# 方法 1: 使用 mmm 编译(推荐)mmm hardware/rockchip/audio/# 方法 2: 使用 mm 编译cd hardware/rockchip/audio/mm
1.5.2 实战:编译单个 HAL 模块
# 确保已初始化环境source build/envsetup.shlunch rk3576_u-userdebug# 编译音频 HALmmm hardware/rockchip/audio/# 编译 Camera HAL (AIDL)mmm hardware/rockchip/camera_aidl/# 编译显示合成器mmm hardware/rockchip/hwcomposer/# 编译电源 HALmmm hardware/rockchip/power_aidl/# 编译传感器 HALmmm hardware/rockchip/sensor/# 编译 Grallocmmm hardware/rockchip/libgralloc/
编译输出:
[ 95% 123/130] Linking C++ shared library out/.../audio.primary.rk30board.so[100% 130/130] Install: out/target/product/rk3576_u/vendor/lib64/hw/audio.primary.rk30board.so
1.5.3 编译后快速部署
修改了 HAL 代码后,不需要全量编译 + 烧写整个系统:
# 步骤 1: 编译模块mmm hardware/rockchip/audio/# 步骤 2: 重新打包镜像(不重新编译)make snod # 快速重新打包 system.imgmake vendorimage # 重新打包 vendor.img# 步骤 3: 部署到设备adb rootadb remountadb push out/target/product/rk3576_u/vendor/lib64/hw/audio.primary.rk30board.so /vendor/lib64/hw/# 步骤 4: 重启服务adb shell stopadb shell start
1.5.4 案例分析:如何快速迭代 HAL 开发
场景:你在修改音频 HAL,需要反复编译、部署、测试。
低效做法(每次 30+ 分钟):
./build.sh -Ap # 编译全部 Android → 慢./build.sh -p # 打包镜像 → 慢烧写 vendor.img # 烧写 → 慢重启设备 # 重启 → 慢
高效做法(每次 1-2 分钟):
mmm hardware/rockchip/audio/ # 只编译音频 HAL → 快adb root && adb remount # 获取写入权限adb push out/.../audio.primary.rk30board.so /vendor/lib64/hw/ # 推送 → 快adb shell stop && adb shell start # 重启服务 → 快
1.6 编译加速
1.6.1 ccache 原理和配置
ccache 是什么?
ccache (Compiler Cache) 会缓存编译结果。当你重新编译相同的文件时,直接使用缓存而不是重新编译。
效果:
•首次编译:无加速(需要建立缓存)
•二次编译:加速 3-10 倍(取决于代码变更量)
配置步骤:
# 启用 ccacheexport USE_CCACHE=1export CCACHE_EXEC=/usr/bin/ccache# 设置缓存大小(50GB)prebuilts/misc/linux-x86/ccache/ccache -M 50G# 查看 ccache 状态ccache -s# 输出示例:# cache directory /home/user/.ccache# cache hit (direct) 12345 ← 直接命中# cache hit (preprocessed) 6789 ← 预处理后命中# cache miss 1234 ← 未命中# cache hit rate 90.0% ← 命中率
1.6.2 增量编译技巧
# 技巧 1: 只编译修改的模块mmm# 技巧 2: 减少并行任务数(内存不足时避免 OOM)./build.sh -A -J8# 技巧 3: 使用 Ninja 而非 Make(Android 默认使用 Ninja)export USE_NINJA=true# 技巧 4: 跳过不需要编译的组件# 不修改 U-Boot 时:不用 -U# 不修改内核时:不用 -K
1.6.3 编译问题排查
问题 1: 内存不足
症状:编译过程中被 Killed
排查:
# 查看系统内存free -h# 查看是否有 OOM 记录dmesg | grep -i "out of memory"dmesg | grep -i "killed process"
解决:
# 减少并行任务数./build.sh -A -J8# 或者增加 swap(见 1.3.6)
问题 2: 头文件缺失
症状:fatal error: xxx.h: No such file or directory
排查:
# 搜索头文件位置find . -name "xxx.h" -type f 2>/dev/null | head -5
解决:检查 Android.bp 中的 include_dirs 和shared_libs 是否遗漏了依赖。
问题 3: 符号未定义
症状:undefined reference to 'xxx'
排查:
# 搜索符号定义grep -r "xxx" hardware/rockchip/ --include="*.cpp" --include="*.h" -l
解决:检查static_libs / shared_libs 是否遗漏了包含该符号的库。
问题 4: lunch 失败
症状:lunch 找不到rk3576_u
排查:
# 检查产品配置是否存在ls device/rockchip/rk3576/rk3576_u/# 检查 vendorsetup.shcat device/rockchip/rk3576/rk3576_u/vendorsetup.sh 2>/dev/null
解决:确保source build/envsetup.sh 执行成功,没有报错。
1.7 配置链深度解析
1.7.1 rk3576_u.mk 的 include 关系
这是一个配置继承链,类似面向对象编程中的继承:

实际文件路径:
•device/rockchip/rk3576/rk3576_u/rk3576_u.mk
•device/rockchip/rk3576/rk3576_u/BoardConfig.mk
•device/rockchip/rk3576/BoardConfig.mk
•device/rockchip/common/BoardConfig.mk
•device/rockchip/rk3576/device.mk
•device/rockchip/common/device.mk
1.7.2 BOARD_ 变量 vs PRODUCT_ 变量

1.7.3 案例分析:为什么我的包没被安装?
场景:在device.mk 中添加了PRODUCT_PACKAGES += MyService,编译后设备上没有。
排查步骤:
# 步骤 1: 确认文件在正确的目录下cat device/rockchip/rk3576/device.mk | grep "MyService"# 步骤 2: 确认模块定义正确# 检查 MyService 的 Android.bp 是否有:# name: "MyService"# 步骤 3: 重新编译并打包mmmmake vendorimage# 步骤 4: 检查输出目录ls out/target/product/rk3576_u/vendor/bin/ | grep MyService# 步骤 5: 检查设备adb shell ls /vendor/bin/ | grep MyService
常见原因:
1.PRODUCT_PACKAGES 写错了包名(与 Android.bp 的 name 不一致)
2.没有重新编译(只修改了 device.mk 但没有触发重新编译)
3.镜像没有重新打包或没有烧写到设备
1.8 获取 SDK 源码(官方指南)
1.8.1 repo 同步(Rockchip Gerrit)
Rockchip Android15 SDK 使用 Gerrit 进行代码管理和分发。
完整同步(在线):
# 安装 repo 工具git clone https://gerrit.rock-chips.com:8443/repo-release/tools/repo# 初始化 manifest./repo/repo init --repo-url https://gerrit.rock-chips.com:8443/repo-release/tools/repo-u https://gerrit.rock-chips.com:8443/Android_15/manifests-b master -m Android15.xml# 同步所有仓库.repo/repo/repo sync -c# Express 版本(精简版,下载更快)./repo/repo init --repo-url https://gerrit.rock-chips.com:8443/repo-release/tools/repo-u https://gerrit.rock-chips.com:8443/Android_15/manifests-b master -m Android15_Express.xml
镜像仓库同步(适合内部团队部署私有镜像):
# 1. 创建镜像仓库mkdir RK_Android15_mirrorcd RK_Android15_mirror# 2. 初始化镜像../repo/repo init --repo-url https://gerrit.rock-chips.com:8443/repo-release/tools/repo-u https://gerrit.rock-chips.com:8443/Android_15/manifests-b master -m Android15.xml --mirror# 3. 同步.repo/repo/repo sync -c# 4. 团队从镜像同步mkdir Android15cd Android15~/repo/repo init -u ssh://git@10.10.10.206/Android_15/manifests_xxx.git -m Android15.xml
从压缩包恢复(离线场景):
# 解压分卷压缩包mkdir Rockchip_Android15.0_SDK_RELEASEcat Rockchip_Android15.0_SDK_RELEASE.tar.gz.* | tar -zx -C Rockchip_Android15.0_SDK_RELEASEcd Rockchip_Android15.0_SDK_RELEASE# 本地同步.repo/repo/repo sync -l# 更新到最新.repo/repo/repo sync -c
1.8.2 切换内核分支
开发中经常需要切换内核分支进行调试:
# 1. 进入内核目录cd kernel-6.1# 2. 从 remote 创建本地分支git checkout remotes/m/master -b xxx_branch# 3. 推送到远程git push rk29 xxx_branch:xxx_branch# 4. 修改 manifest 指向新分支cd .repo/manifests# 编辑 include/rk_modules_repository.xml,修改 kernel 的 revisiongit add include/rk_modules_repository.xmlgit commit -m "change kernel branch on xxx_branch"git push origin default:master
1.9 内核独立编译
除了build.sh -K 外,内核也可以独立编译,这在调试内核问题时更高效。
配置编译环境:
cd kernel-6.1# 设置 Clang 工具链export PATH=../prebuilts/clang/host/linux-x86/clang-r487747c/bin:$PATHalias msk='make CROSS_COMPILE=aarch64-linux-gnu- LLVM=1 LLVM_IAS=1'# RK3576: 配置 + 编译msk ARCH=arm64 rockchip_defconfig rk3576-ftrace-debug.configmsk ARCH=arm64 BOOT_IMG=../rockdev/Image-rk3576_u/boot.img rk3576-evb1-v10.img -j32
产物说明:
|
编译方式 |
产物 |
用途 |
|
build.sh -K |
kernel.img + resource.img |
传统烧写方式 |
|
独立编译 + BOOT_IMG |
boot.img |
直接烧写 boot 分区 |
1.10 fastbootd 烧写与 GSI
1.10.1 fastbootd vs bootloader 模式
Android 15 支持两种 fastboot 模式:
|
模式 |
进入方式 |
可烧写分区 |
用途 |
|
bootloader |
adb reboot bootloader |
boot、dtbo、vbmeta、recovery |
底层分区烧写 |
|
fastbootd |
adb reboot fastboot |
system、vendor、odm、product |
动态分区烧写 |
1.10.2 烧写动态分区
# 1. 进入 fastbootd 模式adb reboot fastboot# 2. 烧写各个分区fastboot flash vendor vendor.imgfastboot flash system system.imgfastboot flash odm odm.imgfastboot flash product product.img
1.10.3 GSI 烧写流程
GSI (Generic System Image) 用于测试纯 AOSP 系统:
# 1. 解锁 AVB(首次)adb reboot bootloaderfastboot oem at-unlock-vboot# 2. 进入 fastbootd 模式fastboot flash misc misc.imgfastboot reboot fastboot# 3. 删除 product 分区(GSI 不需要)fastboot delete-logical-partition product# 4. 烧写 GSI systemfastboot flash system system.img# 5. 重启fastboot reboot
1.10.4 DSU(Dynamic System Updates)
DSU 允许在不影响当前系统的情况下测试 GSI,最低需要 1GB DDR:
•使用场景:VTS 测试、CTS-ON-GSI 测试、Google-signed GSI 测试
•需要使用boot-debug.img 替代boot.img
1.11 新增产品配置
如何在 SDK 中新增一个产品(以 rk3562_new_u 为例):
步骤 1:注册产品
# device/rockchip/rk3562/AndroidProducts.mkPRODUCT_MAKEFILES :=$(LOCAL_DIR)/rk3562_u/rk3562_u.mk$(LOCAL_DIR)/rk3562_new_u/rk3562_new_u.mkCOMMON_LUNCH_CHOICES :=rk3562_u-userdebugrk3562_u-userrk3562_new_u-userdebugrk3562_new_u-user
步骤 2:复制现有产品配置
cd device/rockchip/rk3562cp -rf rk3562_u rk3562_new_ucd rk3562_new_u# 修改产品名称sed -i 's/rk3562_u/rk3562_new_u/g' rk3562_new_u.mk BoardConfig.mk
步骤 3:编译
source build/envsetup.shlunch rk3562_new_u-userdebug./build.sh -UKAop
本章小结

最后想说:Android构建系统看似复杂,但只要理清核心流程和关键命令,就能大幅提升开发效率。如果这篇内容对你有帮助,记得点赞+收藏,后续还会分享更多Android底层开发干货~
全部0条评论
快来发表一下你的评论吧 !