模块化编译
局部修改 AOSP 后,“模块编译成功”只是中间状态。可靠验证需要完成:
源码文件 -> 模块名 -> 构建目标 -> 安装产物 -> 设备目标路径 -> 进程重新加载 -> 行为验证任何一步判断错误,都会出现“编译成功但设备没有变化”:模块名可能不同于目录名,一个模块可能 产生多个安装产物,设备进程也可能仍在使用旧代码。
m-mm-mmm编译命令 已说明局部目标选择, Ninja构建系统 已说明依赖与增量执行。本文继续追踪产物如何到达设备。
本文面向已经能完成局部构建、但还不能证明运行中进程确实使用新产物的读者。问题边界是模块名到 安装产物、设备路径和重新加载的验证链;不覆盖完整刷机、OTA 或 ADB 协议内部。主线由 module-info.json、pathmod/outmod、installmod/adb sync 到真实消费者组成。设备不可用时, 正文给出可执行的 host 侧替代断言,并明确它们不能证明设备运行时状态。
1. 模块产物
1.1 模块名边界
示例输入:下面的模块只用于说明模块名与源文件名的边界,不对应 AOSP 中的真实文件。
cc_library_shared {
name: "libexample",
srcs: ["Example.cpp"],
shared_libs: ["libbase"],
}构建命令使用模块名:
m libexample不是 m Example.cpp,也不是直接把源码路径传给 m。
1.2 module-info.json
构建系统会为当前产品生成 module-info.json,常见字段包括:
| 字段 | 作用 |
|---|---|
module_name | 规范模块名 |
path | 模块定义所在源码路径 |
installed | 安装到 out tree 的产物 |
class | 模块类别 |
dependencies | 部分依赖信息 |
它是派生数据。新增模块或改变安装输出后,需要刷新:
refreshmod
pathmod SystemUI
outmod SystemUIrefreshmod 的实现会要求 ANDROID_PRODUCT_OUT,然后通过 Soong UI 构建 module-info:
源码文件:build/soong/bin/refreshmod
if [ ! "$ANDROID_PRODUCT_OUT" ]; then
echo "No ANDROID_PRODUCT_OUT. Try running 'lunch' first." >&2
return 1
fi
# 说明:module-info由构建系统生成,不是静态源码清单。
_wrap_build "$TOP/build/soong/soong_ui.bash" \
--build-mode --all-modules --dir="$(pwd)" module-infopathmod 读取模块定义路径,outmod 遍历安装输出:
源码文件:build/soong/bin/outmod
相关函数/类型:main
module = modinfo.GetModule(
modinfo.ReadModuleInfo(),
args.module,
)
# 说明:一个模块可能有多个installed outputs。
for output in module["installed"]:
print(os.path.join(
os.getenv("ANDROID_BUILD_TOP", ""),
output,
))使用合成 module-info 数据的只读实验,DemoApp 被解析为源码路径 packages/apps/Demo 和安装产物 system/app/Demo/Demo.apk。
2. 部署方式
| 产物 | 常用方式 | 关键条件 |
|---|---|---|
| 单 APK | installmod 或 adb install | 找到正确 APK、包名和 ABI |
| 普通可写文件 | adb push | 目标路径可写,进程会重新加载 |
| system/product/vendor 文件 | adb sync | root、remount、verity 和分区策略允许 |
| APEX | 专用 APEX 安装/激活流程 | 签名、版本和 staged reboot |
| boot/分区镜像 | fastboot 或 OTA | 设备解锁、镜像匹配和恢复方案 |
2.1 APK部署
installmod 从 outmod 输出中选择第一个 APK,再调用 adb install:
installmod -r DemoApp核心逻辑:
源码文件:build/soong/bin/installmod
module="$1"
_path=$(outmod "$module")
# 说明:installmod只选择第一个.apk输出。
_path=$(echo "$_path" | grep -E \\.apk$ | head -n 1)
if [ -z "$_path" ]; then
echo "Module does not produce an apk" >&2
return 1
fi
adb install <install-arguments> "$_path"多 APK、split APK、APEX 或非 APK 模块不应强行使用 installmod。
2.2 文件部署
adb push --sync <local-file> <remote-path>--sync 只比较时间戳,不比较内容 hash:
源码文件:packages/modules/adb/client/file_sync_client.cpp
相关函数/类型:sync_send
if (sync) {
struct stat st;
if (sync_lstat(sc, rpath, &st)) {
// 说明:时间戳相同即跳过。
if (st.st_mtime == static_cast<time_t>(mtime)) {
sc.RecordFilesSkipped(1);
return true;
}
}
}怀疑时间戳导致跳过时,可去掉 --sync,或显式比较主机和设备 hash。
2.3 分区部署
adb sync 从 ANDROID_PRODUCT_OUT 读取:
data odm oem product system system_ext vendor命令行会把所选分区映射到 ANDROID_PRODUCT_OUT/<partition>,再同步到设备同名根目录:
源码文件:packages/modules/adb/client/commandline.cpp
相关函数/类型:sync command
std::vector<std::string> partitions{
"data", "odm", "oem", "product",
"system", "system_ext", "vendor",
};
for (const auto& partition : partitions) {
if (src == "all" || src == partition ||
src == "/" + partition) {
// 说明:来源是ANDROID_PRODUCT_OUT下的分区目录。
std::string src_dir{product_file(partition)};
if (!directory_exists(src_dir)) continue;
do_sync_sync(
src_dir, "/" + partition,
list_only, compression, dry_run, quiet);
}
}常用顺序:
adb root
adb remount
adb sync -l system
adb sync system-l 只列出文件,-n 是 dry run。root/remount 是否可用取决于设备 build、verity 和产品策略, 不能假定所有设备都允许。
3. 产物生效
3.1 文件复制边界
| 组件 | 常见生效动作 |
|---|---|
| 普通 APK | force-stop 后重新启动 |
| SystemUI | 重启 SystemUI 进程 |
| Java system_server 代码 | 重启 system_server 或设备 |
| Native daemon/service | 重启对应服务或设备 |
| Shared library | 重启加载该库的进程 |
| Framework resource/overlay | 重启相关进程或设备 |
| APEX | 按激活模式重启或 staged reboot |
3.2 产物路径
adb shell ls -l <remote-path>
adb shell stat <remote-path>
adb shell sha256sum <remote-path>主机侧也计算安装输出 hash。并非所有设备包含 sha256sum,可根据环境选择其他只读校验方法。
文件一致只证明传输结果,不能证明 PackageManager、Native linker 或目标服务已经重新加载该文件。 还需要按组件类型检查包、进程与服务状态。
3.3 APK服务验证
adb shell pm path <package-name>
adb shell dumpsys package <package-name>
adb shell am force-stop <package-name>
adb shell ps -A | grep <process>
adb shell service list | grep <service>
adb shell dumpsys <service>
adb logcat -d -s <tag>验证点应来自本次修改,例如新增日志、字段值、dump 输出或可复现行为,而不是只看到 adb install Success。
4. 常见失败
4.1 失败入口
refreshmod
pathmod <module>
outmod <module>4.2 模块错误
使用 dirmods、pathmod 和 Android.bp 确认模块定义;优先使用 outmod 的 installed 输出, 不要直接推送 out/soong/.intermediates 中的文件。
4.3 sync失败
检查 ANDROID_PRODUCT_OUT、目标分区目录、adb sync -l 输出、文件时间戳和设备写权限。
4.4 产物未生效
可能是进程未重启、设备加载了其他分区/APEX 副本、ABI/variant 不匹配、feature flag/overlay 控制 或实际运行路径没有经过修改代码。
5. 模块消费者
模块化验证的终点不是 m <module> 返回成功,而是运行中的消费者已经加载对应产物。完整链路是:
Android.bp/Android.mk
-> 模块名
-> installed output
-> 设备目标路径或安装会话
-> 进程重新加载
-> 可观察行为pathmod 和 outmod 分别确认链路的源码端与产物端;APK、普通文件、系统分区、APEX 和镜像则有不同 部署边界。部署前先用 list-only 或 dry-run 确认影响范围,部署后再比较设备文件、package、service、 进程与行为,避免把“传输命令成功”误认为“新代码正在运行”。
即使某台设备允许 adb root 和 adb remount,也只说明设备开放了对应调试能力。若设备运行的是厂商 车机镜像而不是当前构建的 AOSP product,还必须确认分区布局、ABI、APEX/overlay、签名与进程加载路径 是否匹配;否则 adb sync 成功也不能证明 Android 17 的目标模块已经成为实际消费者。
6. 离线验证
先用合成 module-info.json 运行 pathmod DemoApp 和 outmod DemoApp,断言源码路径与 installed APK 路径分别解析正确;再保存 installmod 对临时 APK 生成的安装命令,断言选择的是 installed output 而不是 .intermediates。前者证明模块索引的输入/输出关系,后者证明部署入口选择;两者都 不证明 PackageManager session 提交、进程重启或业务行为。
有设备时再增加三项断言:设备目标路径 hash 与产物一致、目标 package/service 的 code path 指向 该路径、重启或重新加载后行为变化。缺少任一项时,结论必须停留在“传输或文件状态已验证”,不能 提升为“消费者已生效”。
