RK3568 AOSP 编译与快速部署实战
记录 RK3568 平台 Android 11 源码的编译体系与日常调试中最常用的增量编译 + 推送部署方法。告别每次改一行代码就全量烧录的低效循环。
一、编译环境
bash
cd ~/aosp_sys/rk3568
source build/envsetup.sh
lunch rk3568-userdebug
make.sh 是日常编译的统一入口,封装了三种模式。
二、三种编译模式
2.1 全量编译
bash
./make.sh # 等价于 ./make.sh full
执行 ./build.sh -UKAu:uboot + kernel + android + 打包 update.img。通常 30~60 分钟,改 C/C++ 或内核代码时使用。
2.2 快速编译 + 部署(日常主力)
bash
./make.sh quick
改 frameworks/base/ 下 Java 代码时使用,完整流程耗时约 2~5 分钟:
markdown
1. 检查并清理 out/.lock 残留锁文件
2. make framework-minus-apex services.core -j$(nproc) ← 增量编译
3. jar uf 合并 .class → services.jar
4. adb root + adb remount
5. 删除 /system/framework/{arm,arm64,oat} + /data/dalvik-cache ← 清除旧 OAT
6. truncate boot-framework.{art,oat,vdex} ← overlayfs 下无法删除,清零使其失效
7. push framework.jar + services.jar
8. adb reboot + wait-for-device
2.3 仅推送(编译产物已有,只部署)
bash
./make.sh push
跳过编译,直接推送上次编译好的 jar 并重启。适合反复测试同一版本或设备 OAT 缓存未清理干净时重新推送。
三、make.sh 脚本完整源码
bash
#!/bin/bash
# ============================================================
# RK3568 编译脚本
# 全量编译: ./make.sh
# 仅编译+部署 jar: ./make.sh quick
# 仅部署(跳过编译): ./make.sh push
# ============================================================
set -e
source build/envsetup.sh
lunch rk3568-userdebug
MODE="${1:-full}"
BASE_DIR="$PWD"
FRAMEWORK_SRC="$BASE_DIR/out/target/product/rk3568/system/framework/framework.jar"
SERVICES_SRC="$BASE_DIR/out/target/product/rk3568/system/framework/services.jar"
# ====== 编译前清理锁文件 ======
LOCK_FILE="$BASE_DIR/out/.lock"
if [ -f "$LOCK_FILE" ]; then
LOCK_PID=$(cat "$LOCK_FILE" 2>/dev/null | head -1)
if [ -n "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2>/dev/null; then
echo "!!! 检测到活跃的编译进程 (PID=$LOCK_PID),等待其完成..."
while kill -0 "$LOCK_PID" 2>/dev/null; do sleep 2; done
echo "前序编译已完成"
else
echo "清理残留锁文件 (PID=${LOCK_PID:-unknown} 已不存在)"
fi
rm -f "$LOCK_FILE"
fi
# ====== 编译 ======
build_jars() {
echo "=== 编译 framework.jar + services.core ==="
make framework-minus-apex services.core -j$(nproc)
# services.core 编译后需手动合并到 services.jar
echo "=== 合并 services.core → services.jar ==="
local CLASSES_DIR="$BASE_DIR/out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/javac/classes"
local SERVICES_JAR="$BASE_DIR/out/target/product/rk3568/system/framework/services.jar"
if [ -d "$CLASSES_DIR/com/st/android/stethtether" ] && [ -f "$SERVICES_JAR" ]; then
jar uf "$SERVICES_JAR" -C "$CLASSES_DIR" com/st/android/stethtether/ 2>/dev/null
echo "services.jar 已更新 ($(ls -la $SERVICES_JAR | awk '{print $5}') bytes)"
fi
}
# ====== 部署 ======
push_jars() {
echo "=== 部署到设备 ==="
adb root
adb remount
echo "清理 oat/vdex 缓存..."
adb shell rm -rf /system/framework/arm 2>/dev/null || true
adb shell rm -rf /system/framework/arm64 2>/dev/null || true
adb shell rm -rf /system/framework/oat 2>/dev/null || true
adb shell rm -rf /data/dalvik-cache 2>/dev/null || true
# overlayfs 保护下无法删除基镜像的 boot-framework.art,用 truncate 使其失效
for f in /system/framework/arm64/boot-framework.art \
/system/framework/arm64/boot-framework.oat \
/system/framework/arm64/boot-framework.vdex \
/system/framework/arm/boot-framework.art \
/system/framework/arm/boot-framework.oat \
/system/framework/arm/boot-framework.vdex; do
if adb shell "[ -f $f ]" 2>/dev/null; then
echo "使 $f 失效..."
adb shell "su root truncate -s 0 $f 2>/dev/null" || true
fi
done
if [ -f "$FRAMEWORK_SRC" ]; then
echo "推送 framework.jar..."
adb push "$FRAMEWORK_SRC" /system/framework/framework.jar
fi
if [ -f "$SERVICES_SRC" ]; then
echo "推送 services.jar..."
adb push "$SERVICES_SRC" /system/framework/services.jar
fi
echo "重启..."
adb reboot
adb wait-for-device
echo "=== 完成 ==="
}
# ====== 主流程 ======
case "$MODE" in
full)
echo "=== 全量编译 ==="
./build.sh -UKAu
;;
quick)
build_jars
push_jars
;;
push)
push_jars
;;
*)
echo "用法: $0 {full|quick|push}"
echo " full - 全量编译 (uboot+kernel+android+update.img)"
echo " quick - 编译 framework.jar + services.jar 并部署"
echo " push - 跳过编译,仅推送已有 jar 并重启"
exit 1
;;
esac
关键设计点
| 机制 | 说明 |
|---|---|
| 锁文件清理 | 上次编译崩溃残留 out/.lock,检查 PID 是否存在,不存在则删除 |
| jar uf 合并 | services.core 编译产物是独立 class 目录,需手动合并到 services.jar |
| truncate 清零 | overlayfs 挂载下无法删除基镜像的 .art/.oat/.vdex,用 truncate -s 0 清零使其失效 |
| 错误容忍 | rm -rf 和 truncate 失败不中断流程(` |
四、为什么必须删除 OAT 缓存
这是 make.sh quick 中最容易被忽略但最关键的一步。
Android 11 的 ART 运行时会优先使用预编译的 .odex/.vdex 文件。如果只替换 jar 而不删除 OAT 缓存:
系统优先加载旧的 .odex → 新 jar 中的代码不生效 → 甚至可能崩溃
正确做法:
bash
# 1. 删除 OAT 目录
adb shell rm -rf /system/framework/arm
adb shell rm -rf /system/framework/arm64
adb shell rm -rf /system/framework/oat
adb shell rm -rf /data/dalvik-cache
# 2. overlayfs 下的 boot-framework 基镜像文件无法删除,用 truncate 清零
adb shell truncate -s 0 /system/framework/arm64/boot-framework.art
adb shell truncate -s 0 /system/framework/arm64/boot-framework.oat
adb shell truncate -s 0 /system/framework/arm64/boot-framework.vdex
# 3. 推送新 jar
adb push framework.jar /system/framework/
adb push services.jar /system/framework/
# 4. 重启(首次启动稍慢,ART 需重新 AOT 编译)
adb reboot
五、不同类型代码的编译与部署
4.1 Java 系统服务 (services.jar)
改动 frameworks/base/services/core/java/ 下的代码:
bash
source build/envsetup.sh && lunch rk3568-userdebug
make services -j$(nproc)
# 然后按第三步操作推送
4.2 Framework API (framework.jar)
改动 frameworks/base/core/java/ 下的代码:
bash
make framework-minus-apex -j$(nproc)
# 同样需要删除 OAT + 推送
⚠️ framework.jar 是 bootclasspath 成员。如果只推 framework.jar 不删 OAT,zygote 启动时会因
Native registration unable to find class崩溃,系统无法启动。
4.3 C/C++ Native 库 (libinputreader.so 等)
Native 库没有 OAT 缓存问题,直接推送和重启即可:
bash
# 编译
make libinputflinger -j$(nproc)
# 推送
adb root && adb remount
adb push out/target/product/rk3568/system/lib64/libinputreader.so /system/lib64/
adb push out/target/product/rk3568/system/lib/libinputreader.so /system/lib/
adb reboot
4.4 SystemUI APK
bash
make SystemUI -j$(nproc)
adb root && adb remount
adb push out/target/product/rk3568/system/priv-app/SystemUI/SystemUI.apk \
/system/priv-app/SystemUI/
adb reboot
4.5 SELinux 策略
bash
# 仅重编 selinux 策略
./make.sh --selinux
# 或
make selinux_policy -j$(nproc)
4.6 内核驱动
内核驱动是内置(obj-y),不是独立 .ko 模块,需要编译 boot.img 并烧录:
bash
# 编译 kernel
make bootimage -j$(nproc)
# 或全量编译
./make.sh
# 烧录 boot.img 到设备
修改后需全量编译或单独编译 kernel 并烧录 boot.img。
六、编译产物路径
| 模块 | 编译后位置 |
|---|---|
| framework.jar | out/target/product/rk3568/system/framework/framework.jar |
| services.jar | out/target/product/rk3568/system/framework/services.jar |
| services.core class | out/soong/.intermediates/frameworks/base/services/core/services.core.unboosted/android_common/javac/classes/ |
| libinputreader.so (64位) | out/target/product/rk3568/system/lib64/libinputreader.so |
| libinputreader.so (32位) | out/target/product/rk3568/system/lib/libinputreader.so |
| SystemUI.apk | out/target/product/rk3568/system/priv-app/SystemUI/SystemUI.apk |
| boot.img | out/target/product/rk3568/boot.img |
| update.img | rockdev/Image-rk3568/update.img |
七、编译排错速查
| 症状 | 原因 | 解决 |
|---|---|---|
[Syntax Error] 在正常 Java 代码行 |
Javadoc 中的 */ 被误解析 |
用空格替代 |
cannot find symbol |
新增常量未编译 | 先 touch 修改文件再编译 |
out/.lock 编译超时 |
soong_build 残留 | rm -f out/.lock(make.sh quick 自动处理) |
| services.jar 推送后不生效 | OAT 缓存未清理 | 推送前删除 arm/arm64/oat |
Native registration unable to find class |
推送 framework.jar 未删除 oat | 删除 OAT 后重启 |
| neverallow violated | 新 allow 撞已有 neverallow | 审 neverallow 策略 → 放宽或换 domain |
| freeze_test 文件差异 | 改了 sepolicy 未同步 prebuilts | cp → prebuilts/api/30.0/ |
Wdeclaration-after-statement |
内核 C90 不允许语句中声明变量 | 变量声明移到函数顶部 |
| jar uf 后代码不生效 | boot-framework.art 缓存 | truncate 清零或全量烧录 |
八、实用技巧
7.1 验证 jar 是否更新
bash
# 确认 class 已合并到 jar 中
strings services.jar | grep "新添加的方法名或类名"
7.2 查看编译进度
bash
# 查看当前编译任务
ps aux | grep ninja
# 查看编译输出
tail -f out/verbose.log 2>/dev/null
7.3 设备无法启动时的抢救
jar 推送后系统崩溃(system_server 不断重启),仍可通过 adb root && adb remount 在早期启动阶段修复:
bash
# 设备崩溃后强制进入 recovery 或等待 adb 可用
adb wait-for-device
adb root && adb remount
# 删除坏 jar 的 OAT 并替换为旧版本
adb shell rm -rf /system/framework/arm*
adb push services.jar /system/framework/
adb reboot
7.4 跳过编译直接测试
bash
# 如果只是设备重启后 OAT 缓存没清理干净
./make.sh push # 跳过编译,直接推 jar + 清缓存 + 重启
本文基于 RK3568 Android11 平台实际开发经验整理。