3. AIDL语言入门
AIDL(Android Interface Definition Language)是 Android 系统中用于定义跨进程通信接口的 IDL 语言。它的核心价值在于:让客户端与服务端在编译期就拥有一份“契约”,由构建工具自动生成 Binder 通信所需的桩代码与代理代码,开发者只需关注接口语义本身。本节从接口定义、数据类型、方向标记、包名规则、编译产物和基本语法六个方面,建立 AIDL 的完整入门认知。
一、核心原理:AIDL 解决什么问题
Android 的 Binder 机制在底层只传输字节流,客户端调用服务端方法时,参数需要被序列化写入 Parcel,服务端再从 Parcel 中反序列化读取。如果每个方法都手写这套读写逻辑,既繁琐又极易出错。AIDL 的作用就是把“接口声明”翻译成“Binder 通信代码”:
- 客户端侧:生成
Stub.Proxy代理类,把方法调用转换为Parcel写入、transact()调用和结果读取。 - 服务端侧:生成
Stub抽象类,在onTransact()中根据方法编号读取参数、调用真实实现、写回结果。 - 双方共享:方法编号(
TRANSACTION_xxx)、接口描述符(DESCRIPTOR)和参数顺序,保证两端一致。
RemoteException 或数据错乱。二、接口定义与基本语法
AIDL 文件以 .aidl 为后缀,语法接近 Java 接口,但更严格。一个典型接口定义如下:
// IUserService.aidl
package com.example.demo;
import com.example.demo.UserInfo;
interface IUserService {
UserInfo getUser(int id);
void setUser(in UserInfo user);
List<UserInfo> listUsers();
int add(int a, int b);
}
基本语法要点:
- 文件顶部必须声明
package,且与文件所在目录路径一致。 - 使用
import引入其他包中的自定义类型,同包类型可省略。 - 接口名与文件名一致,方法不能有
public、static、final等修饰符。 - 方法可以有返回值,也可以返回
void;不支持重载方法名相同但参数不同的情况。 - 不支持泛型方法、异常声明、字段和常量(常量可通过
const定义在接口内)。
三、数据类型与方向标记
AIDL 支持的数据类型分为三类,方向标记则决定参数在跨进程传输时的流向。
| 类型类别 | 具体类型 | 是否需要 import | 方向标记 |
|---|---|---|---|
| 基本类型 | int、long、boolean、float、double、char、byte | 否 | 默认 in,不可写 out/inout |
| String / CharSequence | String、CharSequence | 否 | 默认 in,可写 out/inout |
| 集合类型 | List、Map | 否 | 默认 in,元素必须是 AIDL 支持类型 |
| 自定义 Parcelable | 实现 Parcelable 的类 | 是 | 必须显式标注 in/out/inout |
| 其他 AIDL 接口 | 另一个 .aidl 接口 | 是 | 必须显式标注 in/out/inout |
方向标记的含义:
in:数据从客户端流向服务端,服务端修改不影响客户端。out:数据从服务端流回客户端,客户端传入的初始值在服务端为空。inout:双向流动,服务端可读取并修改后回传,开销最大。
in,但显式写出可读性更好。四、包名规则与目录结构
AIDL 的包名规则与 Java 包名一致,但有一个硬性约束:package 声明必须与文件在源码集中的相对路径完全匹配。例如:
src/main/aidl/com/example/demo/IUserService.aidl
对应 package com.example.demo;
如果路径与包名不一致,AIDL 编译器会报 couldn't find import for class 或 package ... does not match directory。在 Android Gradle 项目中,AIDL 文件默认放在 src/main/aidl/ 下;使用 sourceSets 可以自定义目录,但包名与路径的对应关系不变。
五、编译产物与调用链
AIDL 文件在构建时由 aidl 工具编译为 Java 文件,最终参与 APK 打包。理解编译产物是排查跨进程问题的关键。下图展示了从 AIDL 源文件到客户端调用、服务端响应的完整链路:
编译产物通常位于 build/generated/aidl_source_output_dir/ 下,核心结构包括:
DESCRIPTOR:接口唯一标识字符串,通常为包名 + 接口名。TRANSACTION_xxx:每个方法对应一个整型编号,onTransact()据此分发。Stub.Proxy:客户端代理,实现接口方法并完成Parcel读写。Stub:服务端抽象类,继承Binder并实现onTransact()。asInterface():把IBinder转换为接口,同进程直接返回本地对象,跨进程返回 Proxy。
六、案例与常见误区
假设我们要实现一个用户查询服务,接口定义如下:
// IUserService.aidl
package com.example.demo;
import com.example.demo.UserInfo;
interface IUserService {
UserInfo getUser(int id);
void updateUser(in UserInfo user);
void fillUser(out UserInfo user);
List<UserInfo> queryUsers(in String keyword);
}
服务端实现时继承 IUserService.Stub,客户端通过 IUserService.Stub.asInterface(binder) 获取接口。常见误区包括:
- 包名与路径不一致:AIDL 文件放在
aidl/com/example/下,但 package 写成com.demo,编译报错。 - 自定义类型未实现 Parcelable:AIDL 无法识别普通 Java 类,必须实现
Parcelable并提供CREATOR。 - 方向标记缺失:自定义 Parcelable 参数不写
in/out/inout,编译直接失败。 - 方法重载:AIDL 不支持同名方法重载,需要改用不同方法名。
- 修改接口不兼容:已发布接口增加方法、调整参数顺序或改变方向标记,会导致旧客户端调用失败。