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
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
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
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
@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 版本管理。
