2. AIDL 接口设计基础:语法、方向、类型与文件组织
AIDL(Android Interface Definition Language)是 Binder 跨进程通信的接口契约语言。本章从语法结构、参数方向、类型约束和文件组织四个维度,建立 AIDL 接口设计的完整知识基线。
2.1 AIDL 语法结构
一个标准的 AIDL 接口文件由包声明、导入、接口声明和方法签名四部分组成。以下是一个导航回调场景中的典型示例:
// NavigationCallback.aidl
package com.example.navigation;
import com.example.navigation.RouteInfo;
interface INavigationCallback {
void onRouteUpdated(in RouteInfo route);
void onNavigationError(String message, int errorCode);
void onArrival();
}
核心语法规则:
- 包声明必须与宿主应用的包名一致,生成路径由 AIDL 编译器自动映射。
- 导入仅对非基本类型、非 String/CharSequence 的 parcelable 类型需要显式 import。
- 接口名建议以大写 I 开头,增强可读性。
- 方法签名支持 0 个或多个参数,支持返回值(void 或基本类型 / parcelable)。
命名规范提示:回调接口推荐命名为 I<业务名>Callback,如 INavigationCallback、IPositionListener,便于在 AIDL 目录中快速识别回调型接口。
2.2 参数方向:in / out / inout
方向标记是 AIDL 区别于普通 Java 接口的关键特征。它决定了 Binder 驱动在跨进程传输时数据拷贝的方式和序列化方向。三个方向的语义对比如下:
| 方向 | 数据流向 | 序列化行为 | 典型场景 |
|---|---|---|---|
| in | 调用方 → 服务方 | 调用时将数据完整写入 Parcel;服务方可读不可写回。 | 回调事件上报、数据查询请求 |
| out | 服务方 → 调用方 | 调用时写入空占位;服务方填充后写回。 | 服务端填充结果、获取配置 |
| inout | 双向传输 | 调用时写入完整数据;服务方修改后全量写回。 | 协商更新、状态双向同步 |
性能警告:inout 在传输大对象时会产生两次序列化开销。在回调场景中,非必要不使用 inout。对于只读的回调事件,始终使用 in 方向。
选择原则:如果不确定方向,优先使用 in。只有确定服务方需要修改对象并将修改返回给调用方时,才考虑 out 或 inout。在导航回调中,所有回调参数均使用 in 方向。
2.3 参数类型限制
AIDL 支持的数据类型由严格的划分,不可直接使用任意 Java 对象。以下是完整的类型系统清单:
| 类别 | 支持类型 | 备注 |
|---|---|---|
| 基本类型 | boolean, byte, char, short, int, long, float, double | 所有基本类型均支持,无装箱要求 |
| 字符串 | String, CharSequence | 默认支持,无需额外导入 |
| 列表 | List<T> (T 为支持类型) | 接收端总是 ArrayList |
| 映射 | Map<K,V> (K,V 为支持类型) | 接收端总是 HashMap |
| 自定义 Parcelable | 实现 Parcelable 接口的类 | 需在 AIDL 文件中使用 parcelable 声明 |
| AIDL 接口 | 其他 AIDL 接口类型 | 用于传递 Binder 代理对象 |
重要限制:不支持泛型通配符(如 List<?>)、不支持枚举、不支持 Serializable、不支持 非基本类型数组(基本类型数组如 int[] 是支持的)。导航回调中常用的 RouteInfo 必须实现 Parcelable 并在 AIDL 中声明。
// RouteInfo.aidl 中的 parcelable 声明
// 方式一:在独立 .aidl 文件中声明
// RouteInfo.aidl (文件内容仅一行)
package com.example.navigation;
parcelable RouteInfo;
// 方式二:在同一接口文件中声明(适用于小型项目)
// INavigationCallback.aidl 顶部追加
package com.example.navigation;
parcelable RouteInfo;
2.4 接口文件组织规范
AIDL 文件的目录结构直接影响编译产物和跨模块引用。推荐的企业级组织方式如下:
app/src/main/aidl/
└── com/
└── example/
└── navigation/
├── INavigationCallback.aidl # 回调接口(供远程端调用)
├── INavigationService.aidl # 服务接口(供客户端调用)
├── RouteInfo.aidl # 数据实体 parcelable 声明
└── NavigationConstants.aidl # 常量接口(可选)
文件组织三条硬规则:
- 规则1:包路径必须与 java 源文件中的包声明完全一致,否则编译器会报 "can't resolve" 错误。
- 规则2:每个自定义 Parcelable 类必须对应一个同名的 .aidl 声明文件,或者集中在同一个 .aidl 文件中用多个
parcelable关键词声明。 - 规则3:跨模块引用时,必须将依赖的 AIDL 文件连同包路径一并复制到当前模块的 aidl 目录下,不能跨模块引用 .aidl 源文件。
工程经验:在导航多模块项目中,建议将 AIDL 文件放在独立的
:navigation-aidl 模块中,其他模块通过依赖该模块共享接口定义。这样避免重复拷贝导致接口不一致。
2.5 AIDL 编译产物
AIDL 编译器会为每个接口生成对应的 .java 代理类,理解产物结构有助于调试远程调用异常:
// 由 INavigationCallback.aidl 生成的 Java 类结构
// 路径: build/generated/aidl_source_output_dir/.../
public interface INavigationCallback extends android.os.IInterface {
public static abstract class Stub extends android.os.Binder implements INavigationCallback {
// 本地实现只需继承 Stub 并实现接口方法
public static INavigationCallback asInterface(android.os.IBinder obj);
public android.os.IBinder asBinder();
}
public static class Proxy implements INavigationCallback {
// Proxy 封装了 transact() 调用,对客户端透明
}
public void onRouteUpdated(com.example.navigation.RouteInfo route) throws RemoteException;
public void onNavigationError(java.lang.String message, int errorCode) throws RemoteException;
public void onArrival() throws RemoteException;
}
核心机制:Stub 是 Binder 本地对象,Proxy 是远程代理。客户端通过 asInterface() 自动区分本地/远程调用——当调用发生在同一进程时,直接执行本地方法;跨进程时走 Parcel 序列化通道。
调试技巧:在 generated 目录中查看生成的 .java 文件,可以精确跟踪方向标记如何影响 writeToParcel 和 createFromParcel 的调用顺序。如果发现方向异常,优先检查这里。
至此,AIDL 接口设计的四个核心维度——语法、方向、类型、文件组织——已全部覆盖。下一阶段将进入客户端与服务端的实际实现流程,上述知识将直接应用于导航回调的代码编写。