Skip to content

AIDL后端生成

追踪同一 AIDL 接口进入 Java、CPP、NDK、Rust 生成器后的代码结构、Parcel 契约和错误边界。

基于android-17.0.0_r1
AndroidAIDLBinderC++NDKRust

AIDL后端生成 ​

四个 backend 共享同一份已经通过语义检查的 AST,但生成的语言类型、Binder 基类、错误模型和 ABI 消费者不同。图中的共同入口对应 aidl.cpp,四条输出边分别对应后文的 generator 与 golden 文件。

本文面向已读过 Stub与Proxy代码生成、AIDL接口版本管理 的读者。目标不是比较语言语法,而是回答一个源码阅读问题:同一份 AIDL AST 经过四个后端后,哪些 Binder 契约保持不变,哪些部分由目标运行时重新定义。示例使用 aidl-test-versioned-interface 的 V2 golden output;golden 文件是编译器测试产物,不是手写示例。

1. 分派入口 ​

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

cpp
if (lang == Options::Language::CPP) {
  cpp::GenerateCpp(output_file_name, options, typenames, *defined_type, io_delegate);
} else if (lang == Options::Language::NDK) {
  ndk::GenerateNdk(output_file_name, options, typenames, *defined_type, io_delegate);
} else if (lang == Options::Language::JAVA) {
  java::GenerateJava(output_file_name, options, typenames, *defined_type, io_delegate);
} else if (lang == Options::Language::RUST) {
  rust::GenerateRust(output_file_name, options, typenames, *defined_type, io_delegate);
}

前端解析、类型检查和方法编号先完成,后端只消费同一个 AidlTypenames/AidlDefinedType。因此接口描述符、transaction code 和参数方向来自共同 AST;生成文件布局、错误类型和所有权模型则由后端决定。

2. Java后端 ​

2.1 文件形态 ​

源码文件:system/tools/aidl/generate_java.cpp;golden 文件:system/tools/aidl/tests/golden_output/aidl-test-versioned-interface-V2-java-source/gen/android/aidl/versioned/tests/IFooInterface.java

Java 后端输出一个接口文件,Stub 继承 android.os.Binder,Proxy 持有 IBinder。onTransact 从 Parcel 读取参数并调用实现,Proxy 把参数写入 Parcel 后调用 transact,异常通过 Java RemoteException/Parcel 异常路径传播。

2.2 运行边界 ​

Java 生成器可根据 min_sdk_version 改变新 API 的调用策略;这解释了为什么不能把 Java 生成文件直接当成 CPP/NDK 的 ABI 参考。Java 的对象、ClassLoader 和 Parcelable.Creator 规则仍由 android.os.Parcel 承担。

3. CPP后端 ​

3.1 类层次 ​

源码文件:system/tools/aidl/generate_cpp.cpp;golden 文件:system/tools/aidl/tests/golden_output/aidl-test-versioned-interface-V2-cpp-source/gen/include/android/aidl/versioned/tests/IFooInterface.h

CPP 后端生成接口头、Proxy/Stub 实现和 DefaultImpl。接口继承 ::android::IInterface,服务端基类使用 Bn...,客户端代理使用 Bp...,状态由 android::binder::Status 或事务错误码表示。String、Binder 引用和文件描述符沿 libbinder 的 String16、sp<IBinder>、RAII 类型编码。

3.2 默认实现 ​

生成器为未覆盖的方法生成返回 UNKNOWN_TRANSACTION 的默认实现。这个分支是版本兼容的关键:旧服务不会误把新 transaction 当成成功执行,而调用者可以据此选择降级。

4. NDK后端 ​

4.1 ABI边界 ​

源码文件:system/tools/aidl/generate_ndk.cpp;golden 文件:system/tools/aidl/tests/golden_output/aidl-test-versioned-interface-V2-ndk-source/gen/include/aidl/android/aidl/versioned/tests/IFooInterface.h

NDK 后端的头文件位于 aidl/ 命名空间,使用 ::ndk::SpAIBinder、ScopedFileDescriptor 和 binder_status_t 体系。它不是把 CPP 文件换个 include,而是把稳定 ABI 的 libbinder_ndk 作为所有权和错误码边界。

4.2 Bn与Bp ​

NDK 仍生成服务端 Bn... 与客户端 Bp...,但两者通过 NDK 的 AIBinder 包装交给 C API 生命周期管理。调用者看到的是 ScopedAStatus;服务实现需要显式把异常/事务失败转成该状态,不能假设 CPP 的 binder::Status 可直接复用。

5. Rust后端 ​

5.1 Trait结构 ​

源码文件:system/tools/aidl/generate_rust.cpp;golden 文件:system/tools/aidl/tests/golden_output/aidl-test-versioned-interface-V2-rust-source/gen/android/aidl/versioned/tests/IFooInterface.rs

Rust 后端生成 trait、Binder 声明宏和 Parcel 读写实现。接口方法返回 binder::Result<T>,服务实现由 Rust 类型系统表达成功/失败;它不是把 C++ 指针签名机械翻译成 Rust,而是把 Binder 状态包装进 Result。

5.2 所有权 ​

Rust 生成代码对 Binder 引用、字符串和 Parcelable 使用明确的 owned/borrowed 转换。Parcel 解码失败会沿 Result 返回,调用者若直接 unwrap 才会把通信失败升级为进程 panic;生成器本身不替业务决定恢复策略。

6. 共同契约 ​

6.1 事务编号 ​

四个后端都从同一 AidlMethod 顺序生成 transaction 常量和 onTransact 分支。改变方法顺序或删除旧方法会改变 ABI 解释,因此版本化 API 检查必须在生成前阻止不兼容声明。

6.2 版本查询 ​

版本化接口的 VERSION、HASH 常量由各生成器分别写出,但远端查询仍经过真实 Binder 事务。system/tools/aidl/tests/aidl_test_client_versioned_interface.cpp 同时测试 CPP、NDK、Java 客户端:旧服务返回自己的版本/哈希,新方法返回 UNKNOWN_TRANSACTION,而兼容 Parcelable 字段仍可读。

7. Golden校验 ​

源码文件:system/tools/aidl/tests/golden_test.sh

脚本把 V1/V2/V3 与四后端模块列入同一检查表,对构建目录和 golden_output 做递归 diff。编译器模板的任何变化都会先表现为 golden diff,再由开发者判断是否是有意的生成契约变化;这比只编译一个后端更能发现跨后端漂移。

8. 阅读检查 ​

拿一个新增 AIDL 方法,分别在四套 golden 文件中定位它的接口声明、Proxy 写入、Stub 读取和错误返回;再回到 aidl.cpp 说明这些差异从哪个分派分支产生。若只能说“Java 用异常、Rust 用 Result”,却找不到对应生成函数和测试断言,说明还没有建立从 AST 到 Binder 事务的源码主线。