作为一名Android系统开发工程师,最头疼的事情莫过于每次修改framework代码后都要全编整个系统。记得我刚入行时,有一次为了调试一个WindowManager的小改动,不得不等待长达3小时的完整编译。直到后来学会了增量编译技巧,开发效率直接提升了5倍不止。
增量编译的核心价值在于精准定位和快速验证。当你修改了framework层的某个模块时,只需要重新编译这个模块及其直接依赖项,而不是整个系统。这就像修车时只需要更换坏掉的零件,而不是把整辆车都拆开重装。
以最常见的场景为例:
这些情况下,传统的全编方式会编译超过2000个模块,而实际上可能只需要重新编译services.jar或framework.jar等少数几个关键模块。根据我的实测数据,在i7处理器+32GB内存的开发机上:
Android的模块化构建始于Android.bp这个蓝图文件。它比传统的Makefile更加简洁高效,采用类似JSON的声明式语法。每个模块都明确定义了:
以frameworks/base/services/Android.bp为例:
bp复制java_library {
name: "services",
defaults: ["services_java_defaults"],
installable: true,
srcs: [":services-main-sources"],
dex_preopt: {
app_image: true,
profile: "art-profile",
},
}
关键字段解析:
理解模块间的依赖关系是高效增量编译的前提。通过以下命令可以生成依赖图:
bash复制m nothing --dump-module-graph | dot -Tpng > graph.png
典型的依赖链条:
code复制services.jar
├── framework.jar
│ ├── core-libart.jar
│ └── ext.jar
└── android.hardware.foo@1.0-java
这意味着修改framework.jar后,需要重新编译所有依赖它的模块,包括services.jar。而修改services.jar通常只需要单独编译它本身。
在开始增量编译前,必须确保:
建议创建一个编译环境检查脚本:
bash复制#!/bin/bash
# 检查全编记录
[ -f out/build-aosp_arm64.ninja ] || {
echo "请先执行完整编译!"
exit 1
}
# 检查设备连接
adb get-state | grep -q device || {
echo "未检测到设备连接"
exit 1
}
基本编译语法:
bash复制make -j[N] [模块名]
以编译services模块为例:
bash复制make -j12 services
参数优化技巧:
showcommands参数可以看到详细编译步骤BUILD_MODULES_IN_PATHS限定编译范围更高效编译产物通常位于:
code复制out/target/product/[设备名]/system/framework/
部署前建议备份原始文件:
bash复制adb pull /system/framework/services.jar ./services.jar.bak
推送新编译产物的两种方式:
方法一:adb push(推荐)
bash复制adb push out/target/product/[设备名]/system/framework/services.jar /system/framework/
adb shell chmod 644 /system/framework/services.jar
adb shell sync
adb reboot
方法二:通过U盘中转
bash复制# 在设备上操作
mount -o rw,remount /
cp /mnt/media_rw/[U盘ID]/services.jar /system/framework/
chmod 644 /system/framework/services.jar
sync
reboot
验证修改是否生效:
bash复制adb logcat | grep -i windowmanager
问题一:未定义的符号错误
code复制error: cannot find symbol class WindowManagerPolicy
解决方案:
bash复制make -j12 framework services
问题二:API不兼容
code复制error: incompatible types: cannot convert WindowManager.LayoutParams to LP
需要检查模块的sdk_version是否一致。
Ninja构建系统会缓存编译结果,但有时需要手动清理:
bash复制# 清理指定模块
out/soong/.bootstrap/bin/ninja -t clean [模块名]
# 保留增量编译缓存的情况下强制重建
make -j12 services --no-build-output-check
在eng或userdebug版本中,可以调整dex2oat参数加速编译:
bp复制dex_preopt: {
speed: true,
filter: "speed",
generate_dex_info: false,
}
这是我日常使用的增量编译脚本:
bash复制#!/bin/bash
MODULE=$1
DEVICE=generic_x86_64
function compile_and_push() {
echo "▶ 开始编译 $MODULE..."
make -j12 $MODULE || return 1
case $MODULE in
services)
FILES=(
"services.jar"
"oat/arm/services.odex"
"oat/arm/services.vdex"
"oat/arm/services.art"
"services.jar.prof"
)
;;
framework)
FILES=("framework.jar")
;;
*)
echo "未知模块!"
return 1
esac
for file in "${FILES[@]}"; do
SRC="out/target/product/$DEVICE/system/framework/$file"
DEST="/system/framework/$file"
echo "▷ 推送 $file..."
adb push $SRC $DEST
adb shell chmod 644 $DEST
done
adb shell sync
echo "✓ 重启设备..."
adb reboot
}
compile_and_push
使用方法:
bash复制./fastbuild.sh services
这个脚本会自动完成编译、推送、权限设置和重启全过程。我在实际项目中验证过,相比手动操作可以节省70%的时间。特别是在调试WindowManager的触摸事件处理时,可以快速迭代10-15个版本/小时。