第2章 AIDL接口定义语法:数据类型支持、定向tag、oneway声明
AIDL(Android Interface Definition Language,Android接口定义语言)是Binder跨进程通信的核心契约语言。服务端与客户端通过AIDL约定的接口进行方法调用,Binder驱动负责将调用序列化、传递并还原。本章聚焦AIDL接口定义中的数据类型支持、定向 tag 以及 oneway 声明三个关键语法要素,掌握这些才能写出正确、高效的跨进程接口。
2.1 数据类型支持
AIDL 支持的数据类型分为基本类型、可序列化类型、List/Map 容器以及 Binder 接口类型。下表列出所有支持的类别及对应 Java/Kotlin 映射:
| 类别 | AIDL 关键字 | Java/Kotlin 类型 | 说明 |
|---|---|---|---|
| 基本类型 | byte / short / int / long / float / double / boolean / char | byte / short / int / long / float / double / boolean / char | 非空标量,跨进程直接复制 |
| 字符串 | String | java.lang.String | UTF-16 编码,可 null |
| 字符序列 | CharSequence | java.lang.CharSequence | 可 null |
| Parcelable 对象 | 自定义类型 | 实现 android.os.Parcelable 的类 | 需在同包名下定义 .aidl 文件声明 |
| Binder 接口 | IBinder / 自定义 AIDL 接口 | android.os.IBinder 或生成的 Stub 子类 | 传递 Binder 引用,不跨进程拷贝对象 |
| 列表 | List<E> | java.util.List<E> | E 必须是支持的类型;接收端总是 ArrayList 或 Binder 内部列表 |
| 映射 | Map<K,V> | java.util.Map<K,V> | K/V 必须是支持的类型;接收端总是 HashMap |
注意: 自定义的 Parcelable 类必须在 AIDL 文件中使用
parcelable 关键字声明(或使用同名的 .aidl 文件),且该类要实现 Parcelable 接口并保持类路径一致。泛型容器中的元素类型也必须是 AIDL 支持的类型,否则会编译失败。
2.2 定向 tag:in / out / inout
定向tag(Directional tag)用于标注方法参数的数据流动方向,直接决定Binder驱动如何处理序列化/反序列化。AIDL 提供三种 tag:
- in(默认,可省略):数据从客户端流向服务端。客户端发送时序列化到 Parcel,服务端接收时反序列化;方法返回时不写回。
- out:数据从服务端流向客户端。客户端传递一个空壳对象,服务端填充数据后序列化回客户端,客户端接收后更新原对象。
- inout:双向流动。客户端序列化送出,服务端反序列化后修改,再序列化回客户端。
选择 tag 的原则:仅读取参数用 in(性能最优,避免不必要拷贝);仅让服务端填充数据用 out;需要读取并修改后返回用 inout。滥用 inout 会增加一次跨进程拷贝,影响性能。
// 示例 AIDL 接口
interface IStudentManager {
// in:客户端传入学生ID,服务端返回姓名(String 默认 out?不,String 默认 in,返回通过返回值)
String getStudentName(in int studentId);
// out:客户端传入空对象,服务端填充
void fillStudentInfo(out Student student);
// inout:客户端传入已部分填充的对象,服务端修改后返回
void updateStudentInfo(inout Student student);
}
实践经验: 对于基本类型(int, long 等)和 String,定向 tag 只能使用
in,因为它们是不可变对象。只有实现了 Parcelable 的自定义对象或 List/Map 容器才允许使用 out 或 inout。
2.3 oneway 声明
oneway 关键字修饰 AIDL 接口中的方法,表示该调用是异步的。当客户端调用一个 oneway 方法时,Binder 驱动将请求数据发送到服务端后立即返回,不等待服务端执行结果。语义特征:
- 客户端不阻塞;方法无返回值(void),且不能有 out 或 inout 参数。
- 服务端收到请求后进入 Binder 线程池执行,调用顺序按客户端发送顺序保持(同一 Binder 节点上的 oneway 调用保证有序)。
- 常用于日志上报、状态通知、心跳等不需要响应的场景,降低跨进程延迟。
// 声明 oneway 接口
oneway interface ICallback {
void onProgress(int percent);
void onError(int errorCode, String message);
}
性能对比:
- 普通方法(非 oneway):RTT ≈ 500µs ~ 1ms(取决于数据大小和线程调度)。
- oneway 方法:下行仅开销一次 Binder 事务(约 100-200µs),客户端即刻返回。
2.4 语法综合示例
以下是一个完整的 AIDL 文件,综合使用了各种数据类型、定向 tag 和 oneway:
// IDataService.aidl
package com.example.binderdemo;
import com.example.binderdemo.DataPacket;
import com.example.binderdemo.ResultCallback;
interface IDataService {
// 基本类型 in 参数 + 返回值
String greetUser(in int uid, in String name);
// 自定义 Parcelable 对象 inout
int processPacket(inout DataPacket packet);
// 传递 Binder 接口作为回调(服务端持有回调引用)
void registerCallback(in ResultCallback callback);
// oneway 异步通知
oneway void notifyEvent(in int eventId, in String payload);
}
2.5 AIDL 接口定义流程与 Binder 交互关系
下图展示从 AIDL 定义到 Binder 数据流的核心阶段,重点体现数据类型、定向 tag 和 oneway 如何影响 Parcel 序列化与线程模型。
2.6 常见错误与调试
| 错误类型 | 现象 | 原因 | 解决 |
|---|---|---|---|
| 数据类型不支持 | 编译报错 "unsupported type" | 使用了 AIDL 不支持的 Java 类型(如 Date, 普通对象) | 改为 Parcelable 或基本类型;或传递 String 格式(如毫秒时间戳) |
| 定向 tag 冲突 | 运行时数据未正确传递 | String 或 int 写了 out/inout | 移除 out/inout,改为 in |
| oneway 方法有返回值 | 编译报错 "oneway methods must have void return" | oneway 不能返回或带 out 参数 | 将返回值改为 void,或移除 oneway |
| Parcelable 类未声明 | 编译找不到符号 | 自定义 Parcelable 未在 .aidl 中 parcelable 声明 | 在同一包下创建同名的 .aidl 文件,内容为 parcelable Student; |
| oneway 调用时序异常 | 服务端接收消息乱序 | 使用了不同 Binder 对象上的 oneway 调用 | oneway 仅保证同一 Binder 节点内部顺序;跨 Binder 节点无顺序保证 |
调试技巧: 在服务端 Stub 的
onTransact() 中加日志(code、data、reply),可以查看定向 tag 对应的 Parcel 读写行为。使用 adb shell dumpsys binder 查看 Binder 事务统计,确认 oneway 调用次数匹配预期。