Skip to content

模块化编译

说明如何从模块名定位 AOSP 源码与安装产物,完成局部构建,并按 APK、文件或分区选择设备部署与验证方式。

基于android-17.0.0_r1
AndroidAOSP模块编译adb

模块化编译 ​

局部修改 AOSP 后,“模块编译成功”只是中间状态。可靠验证需要完成:

text
源码文件 -> 模块名 -> 构建目标 -> 安装产物 -> 设备目标路径 -> 进程重新加载 -> 行为验证

任何一步判断错误,都会出现“编译成功但设备没有变化”:模块名可能不同于目录名,一个模块可能 产生多个安装产物,设备进程也可能仍在使用旧代码。

m-mm-mmm编译命令 已说明局部目标选择, Ninja构建系统 已说明依赖与增量执行。本文继续追踪产物如何到达设备。

本文面向已经能完成局部构建、但还不能证明运行中进程确实使用新产物的读者。问题边界是模块名到 安装产物、设备路径和重新加载的验证链;不覆盖完整刷机、OTA 或 ADB 协议内部。主线由 module-info.json、pathmod/outmod、installmod/adb sync 到真实消费者组成。设备不可用时, 正文给出可执行的 host 侧替代断言,并明确它们不能证明设备运行时状态。

1. 模块产物 ​

1.1 模块名边界 ​

示例输入:下面的模块只用于说明模块名与源文件名的边界,不对应 AOSP 中的真实文件。

make
cc_library_shared {
    name: "libexample",
    srcs: ["Example.cpp"],
    shared_libs: ["libbase"],
}

构建命令使用模块名:

bash
m libexample

不是 m Example.cpp,也不是直接把源码路径传给 m。

1.2 module-info.json ​

构建系统会为当前产品生成 module-info.json,常见字段包括:

字段作用
module_name规范模块名
path模块定义所在源码路径
installed安装到 out tree 的产物
class模块类别
dependencies部分依赖信息

它是派生数据。新增模块或改变安装输出后,需要刷新:

bash
refreshmod
pathmod SystemUI
outmod SystemUI

refreshmod 的实现会要求 ANDROID_PRODUCT_OUT,然后通过 Soong UI 构建 module-info:

源码文件:build/soong/bin/refreshmod

bash
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-info

pathmod 读取模块定义路径,outmod 遍历安装输出:

源码文件:build/soong/bin/outmod

相关函数/类型:main

python
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. 部署方式 ​

产物常用方式关键条件
单 APKinstallmod 或 adb install找到正确 APK、包名和 ABI
普通可写文件adb push目标路径可写,进程会重新加载
system/product/vendor 文件adb syncroot、remount、verity 和分区策略允许
APEX专用 APEX 安装/激活流程签名、版本和 staged reboot
boot/分区镜像fastboot 或 OTA设备解锁、镜像匹配和恢复方案

2.1 APK部署 ​

installmod 从 outmod 输出中选择第一个 APK,再调用 adb install:

bash
installmod -r DemoApp

核心逻辑:

源码文件:build/soong/bin/installmod

bash
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 文件部署 ​

bash
adb push --sync <local-file> <remote-path>

--sync 只比较时间戳,不比较内容 hash:

源码文件:packages/modules/adb/client/file_sync_client.cpp

相关函数/类型:sync_send

cpp
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 读取:

text
data odm oem product system system_ext vendor

命令行会把所选分区映射到 ANDROID_PRODUCT_OUT/<partition>,再同步到设备同名根目录:

源码文件:packages/modules/adb/client/commandline.cpp

相关函数/类型:sync command

cpp
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);
    }
}

常用顺序:

bash
adb root
adb remount
adb sync -l system
adb sync system

-l 只列出文件,-n 是 dry run。root/remount 是否可用取决于设备 build、verity 和产品策略, 不能假定所有设备都允许。

3. 产物生效 ​

3.1 文件复制边界 ​

组件常见生效动作
普通 APKforce-stop 后重新启动
SystemUI重启 SystemUI 进程
Java system_server 代码重启 system_server 或设备
Native daemon/service重启对应服务或设备
Shared library重启加载该库的进程
Framework resource/overlay重启相关进程或设备
APEX按激活模式重启或 staged reboot

3.2 产物路径 ​

bash
adb shell ls -l <remote-path>
adb shell stat <remote-path>
adb shell sha256sum <remote-path>

主机侧也计算安装输出 hash。并非所有设备包含 sha256sum,可根据环境选择其他只读校验方法。

文件一致只证明传输结果,不能证明 PackageManager、Native linker 或目标服务已经重新加载该文件。 还需要按组件类型检查包、进程与服务状态。

3.3 APK服务验证 ​

bash
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 失败入口 ​

bash
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> 返回成功,而是运行中的消费者已经加载对应产物。完整链路是:

text
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 指向 该路径、重启或重新加载后行为变化。缺少任一项时,结论必须停留在“传输或文件状态已验证”,不能 提升为“消费者已生效”。