Skip to content

AIDL接口版本管理

从 Soong 快照与哈希生成追踪到 Stub/Proxy 版本协商,并解释旧服务、新客户端与不兼容变更的边界。

基于android-17.0.0_r1
AndroidAIDLBinder

AIDL接口版本管理 ​

版本化 Stable AIDL 的关键不是“生成一个版本号”,而是把当前接口、冻结快照、hash、生成常量和客户端协商连成闭环。下面的图先给出这些 owner 的关系,后文再逐段进入构建脚本、生成器和旧服务兼容代码。

本文面向已经理解 AIDL 生成 Stub/Proxy、Parcel 编码和 Binder 同步调用的读者。问题不是“给接口加一个整数”,而是:独立更新的客户端和服务端如何确认彼此看到的接口快照,构建系统又怎样阻止不兼容的快照进入发布产物。文中以 aidl-test-versioned-interface 为主线,源码均对应 Android 17 的 system/tools/aidl。

1. 快照目录 ​

1.1 模块声明 ​

源码文件:system/tools/aidl/Android.bp

make
aidl_interface {
    name: "aidl-test-versioned-interface",
    local_include_dir: "tests/versioned",
    srcs: ["tests/versioned/**/*.aidl"],
    versions_with_info: [
        { version: "1", imports: [] },
        { version: "2", imports: [] },
        { version: "3", imports: [] },
    ],
    frozen: true,
}

versions_with_info 把可重现的 API 快照与当前源文件分开;生成 V1/V2/V3 变体,而不是让消费者直接依赖工作区中的“最新”文件。

1.2 不可编辑 ​

源码文件:system/tools/aidl/aidl_api/aidl-test-versioned-interface/3/android/aidl/versioned/tests/IFooInterface.aidl

快照文件明确写着 immutable。兼容变更通过 m <name>-update-api 更新 current,再冻结新版本;发布组件因此能继续使用旧快照。

2. 哈希生成 ​

2.1 输入排序 ​

源码文件:system/tools/aidl/build/aidl_hash_tool/main.go

go
sort.Strings(files)
for _, file := range files {
    fh := sha1.New()
    io.Copy(fh, f)
    fmt.Fprintf(h, "%x  %s\n", fh.Sum(nil), file)
}
fmt.Fprintln(h, version)

工具先把路径规范化为相对 apiDir 的 ./...,按字典序逐文件计算 SHA-1,再把版本号加入汇总输入。哈希证明的是“这组文件加这个版本”的快照,不是运行时 Binder 对象身份。

2.2 构建校验 ​

源码文件:system/tools/aidl/build/aidl_api.go

checkIntegrity 将快照文件列表、版本目录和保存的 hash 文件交给 verify_hash;哈希不匹配时构建规则输出 message_check_integrity.txt 并失败。它与 checkCompatibility 的 API diff 是两道门:前者防止快照被静默改写,后者判断新旧声明是否兼容。

3. 代码常量 ​

3.1 C++生成 ​

源码文件:system/tools/aidl/generate_cpp.cpp

cpp
static inline const int32_t VERSION = 2;
static inline const std::string HASH = "...";

生成器把版本和哈希写入接口头文件。未冻结的最新版本会用 PreviousVersion()、PreviousHash() 作为降级值,避免发布配置下把实验性 API 当成已冻结契约。

3.2 Java生成 ​

源码文件:system/tools/aidl/generate_java_binder.cpp

生成的 Java 接口同时区分“调用方编译时版本”和远端 getInterfaceVersion() 返回值;两者可能不同,因此客户端不能只读取本地常量推断服务端能力。

4. 运行协商 ​

4.1 真实接口 ​

源码文件:system/tools/aidl/tests/trunk_stable_test/aidl_api/android.aidl.test.trunk/current/android/aidl/test/trunk/ITrunkStableTest.aidl

java
@VersionSupport(version=2)
interface ITrunkStableTest { ... }

解析器在 system/tools/aidl/aidl_language.cpp 的 VersionSpecificCheckValid 中检查注解版本是否等于编译输入版本;不一致直接报错,防止接口文件自称 V2 却被按 V3 生成。

4.2 旧服务 ​

源码文件:system/tools/aidl/tests/aidl_test_client_versioned_interface.cpp

测试把服务链接到 V1、客户端链接到更新版本,并断言 getInterfaceVersion()、getInterfaceHash() 返回旧实现值;调用新增 newApi() 则得到 UNKNOWN_TRANSACTION。这说明版本查询是能力发现,不能把缺失事务当作传输损坏。

4.3 数据兼容 ​

同一测试还验证旧服务读取带新字段的 Parcelable、旧 Union 字段仍成功,而新 Union 分支返回错误。版本管理保护的是编码演进边界,具体字段兼容规则仍由 Parcelable/Union 的生成代码执行。

5. 失败边界 ​

5.1 哈希漂移 ​

修改冻结目录中的任一 .aidl 文件会使 verify_hash 失败;应恢复快照或按流程生成新版本,不能手工覆盖 hash 文件。

5.2 新事务 ​

新客户端调用旧服务没有对应 transaction code 时,服务端返回 UNKNOWN_TRANSACTION。客户端应依据版本/哈希选择降级路径,而不是重试同一事务。

5.3 多版本依赖 ​

Soong 会拒绝同一模块依赖多个版本的 aidl_interface;构建脚本中的 example_dep_build_failure_output.txt 展示了 V1/V2 同时进入依赖图时的错误。版本选择必须在模块图中保持单一且可追踪。

6. 阅读检查 ​

给定“客户端升级后调用失败”的现象,先检查客户端生成接口的 VERSION/HASH,再查服务端测试链接的 -V1/-V2 变体,最后沿 getInterfaceVersion() 和 UNKNOWN_TRANSACTION 判断是能力差异、哈希漂移还是依赖图选错版本。能复述这条路径,才算真正理解 AIDL 版本管理。