AIDL 语法详细总结
AIDL 语法详细总结
AIDL(Android Interface Definition Language,Android接口定义语言)是Android系统基于Binder IPC机制设计的专用语法规范,用于定义客户端与服务端都认可的跨进程通信编程接口,编译器会自动生成标准化的Binder代理类与桩类,屏蔽底层跨进程通信的复杂实现。
官方核心说明:仅当你需要允许不同应用的客户端跨进程访问你的服务,并且需要在服务中处理多线程并发时,才需要使用AIDL。
一、AIDL 文件基础规范
- 文件后缀:必须以
.aidl为后缀名,不可使用其他后缀。 - 编写规则:必须使用Java语法结构编写,支持单行注释
//和多行注释/* */;除import和package语句前的注释外,所有代码注释都会被同步到生成的IBinder接口中。 - 单文件限制:每个
.aidl文件只能定义一个顶级接口或一个parcelable类型声明,不可在单个文件中定义多个顶级接口/类型。 - 包名规范:AIDL文件的包名必须与对应Java/Kotlin实现类的包名完全一致,否则会出现编译期类找不到错误。
- 存放路径:Android Studio项目中默认存放路径为
src/main/aidl/,需与Java代码的包结构一一对应;跨模块/跨应用使用时,客户端与服务端的AIDL文件内容、包名必须完全一致。 - 导入规则:所有非默认支持的类型,即使与当前文件在同一个包下,也必须显式添加
import语句,这是与Java语法最核心的区别之一。
二、AIDL 支持的完整数据类型
AIDL对数据类型有严格限制,仅支持以下6大类,非支持类型无法通过编译。
| 类型分类 | 具体支持范围 | 导入要求 | 官方核心约束 |
|---|---|---|---|
| Java基本数据类型 | byte、short、int、long、float、double、boolean、char | 无需import | 默认且仅支持in定向Tag,不可修改为其他类型 |
| 基础引用类型 | String、CharSequence、IBinder | 无需import | 默认且仅支持in定向Tag,底层做了专属序列化优化 |
| 集合类型 | List、Map | 无需import | 1. List:泛型参数必须是AIDL支持的类型,编译后实际接收类固定为ArrayList2. Map:不支持参数化泛型(如 Map<String,Integer>),key必须为String,value必须是支持的类型,实际接收类固定为HashMap,官方建议优先使用Bundle替代 |
| AIDL生成接口 | 其他AIDL文件定义的接口类型 | 必须显式import | 可直接作为方法参数/返回值,底层传递的是Binder引用,而非对象副本 |
| Parcelable自定义类型 | 实现了Android Parcelable 序列化接口的类 |
1. 必须创建同名AIDL文件声明parcelable 类名;2. 业务AIDL中必须显式import |
自定义对象跨进程传递的唯一合法方式,AIDL声明文件与Java实现类的包名、类名必须完全一致 |
| 数组类型 | 以上所有支持类型的一维数组(如int[]、User[]、String[]) |
元素类型需import则数组同步需import | 仅支持一维数组,不支持多维数组 |
⚠️ 官方明确禁忌:AIDL原生不支持枚举(enum)、静态内部类、未实现Parcelable的普通JavaBean,枚举类型必须用int常量替代。
三、核心语法规则与关键字详解
1. 接口定义语法
// 包声明,必须与Java实现类包名完全一致
package com.example.aidl;
// 按需导入依赖的类型,即使同包名也必须显式导入
import com.example.aidl.User;
import com.example.aidl.IOnDataChangeListener;
// 接口定义
interface IUserManager {
// 常量定义 + 方法定义
}核心规则:
- 必须使用
interface关键字定义,不可使用class、enum等; - 不可使用
public、private、protected等访问修饰符,AIDL接口默认全局可访问; - 支持接口继承,语法与Java一致(如
interface IUserManager extends IBaseManager),但必须显式import父接口; - 接口内仅可定义方法和常量,不可定义成员变量、构造方法、静态代码块等。
2. 常量定义语法
AIDL支持在接口内定义常量,仅支持int和String类型,语法如下:
interface IUserManager {
// 完整写法,static final可省略,编译器自动补全
static final int ERROR_USER_NOT_EXIST = -1;
// 简写写法,支持const关键字
const int ERROR_NO_PERMISSION = -2;
String KEY_USER_ID = "user_id";
}- 常量默认是
public static final,修饰符可省略; - 仅支持int和String类型,不支持其他引用类型常量。
3. 方法定义语法
// 基础格式:[oneway] 返回值类型 方法名(参数列表) [throws 异常];
// 示例
void addUser(in User user);
User getUserById(int userId) throws android.os.RemoteException;核心规则:
- 方法不可有实现体,必须以分号结尾,与Java接口方法一致;
- 返回值可以是AIDL支持的任意类型,无返回值使用
void; - 参数规则:
- 除基本数据类型、String、CharSequence、IBinder外,所有引用类型参数必须显式添加定向Tag(in/out/inout),否则编译报错;
- 不支持可变参数
...,不支持多维数组参数;
- 异常声明:支持抛出
RemoteException(跨进程通信必选异常),也支持抛出其他Java检查型异常; - 修饰符:不可使用
static、final、synchronized等修饰符,仅支持oneway异步修饰符; - 扩展特性:可手动为方法指定交易代码,避免接口升级导致的方法编号错乱,语法为
void getUser() = 10;; - 空值注解:可空的参数或返回值必须使用
@nullable注解标注。
四、定向Tag(in/out/inout)深度解析
定向Tag是AIDL特有的语法,用于标注跨进程参数的数据流向,本质是控制Binder驱动的序列化与反序列化行为,决定参数数据在客户端与服务端的传递方向,直接影响IPC性能。
核心视角:所有数据流向的描述,均以客户端传入的参数对象为参照。
| 定向Tag | 官方核心定义 | 数据流向 | 底层行为细节 | 性能开销 | 适用场景 |
|---|---|---|---|---|---|
in |
输入型参数(默认值) | 客户端 → 服务端 | 1. 客户端将参数完整序列化后传递给服务端,服务端反序列化得到数据副本 2. 服务端对该参数的所有修改,不会同步回客户端 |
最低(单次序列化) | 绝大多数场景:客户端向服务端传递数据,无需服务端回传修改结果 |
out |
输出型参数 | 服务端 → 客户端 | 1. 客户端不会传递参数的初始值,服务端收到的是空的默认对象实例 2. 服务端对参数的修改,会在方法执行完成后序列化回传给客户端,同步更新客户端的原对象 |
中等(单次序列化) | 客户端仅需服务端回传填充数据,无需传递初始值的场景(如获取默认配置对象) |
inout |
输入输出型参数 | 客户端 ↔ 服务端 双向 | 1. 客户端传递参数的完整初始值,服务端反序列化得到完整对象 2. 服务端对参数的所有修改,都会在方法执行后同步回传给客户端,双向更新 |
最高(双倍序列化) | 极少数场景:同时需要传递初始值,又需要服务端回传修改结果 |
⚠️ 关键注意事项:
- 基本数据类型、String、CharSequence、IBinder、AIDL生成接口,默认且仅支持
inTag,强制使用out/inout会直接编译报错; out和inout仅支持Parcelable自定义类型、List、Map等引用类型,且对应的Parcelable类必须实现readFromParcel()方法,否则out/inout不生效,服务端修改无法回传;- 官方强烈建议:必须将定向Tag限制在实际需要的范围内,滥用
inout会带来严重的IPC性能损耗。
五、oneway 关键字完整用法与约束
oneway是AIDL特有的异步修饰符,用于标记非阻塞IPC调用,核心作用是让客户端调用远程方法时,将事务数据写入Binder驱动后立即返回,不阻塞等待服务端执行结果。
1. 修饰规则
- 修饰整个接口:
oneway interface IEventNotify { ... },则接口内所有方法自动成为oneway异步方法; - 修饰单个方法:
oneway void sendEvent(int eventId);,仅该方法为异步调用; - 不可同时修饰接口和单个方法,避免重复修饰。
2. 强制语法约束(违反直接编译报错)
- oneway方法必须无返回值(返回值必须为void),异步调用无法同步返回结果;
- oneway方法的参数只能使用
in定向Tag,不可使用out/inout,无法回传数据; - oneway方法不可声明抛出任何检查型异常,包括RemoteException,异步调用无法同步抛出异常。
3. 核心特性与原理
- 非阻塞特性:本地调用时,oneway无任何影响,调用仍为同步;仅跨进程远程调用时,才会触发非阻塞逻辑;
- 执行顺序:同一个Binder服务的多个oneway调用,服务端会按调用顺序串行执行(Binder线程池的单一线程),不会并发执行,天然避免线程安全问题;而非oneway方法是并行执行的;
- 线程模型:服务端的oneway方法依然运行在Binder线程池中,而非主线程,不可直接操作UI;
- 可靠性:oneway调用不保证100%执行,若服务端进程意外死亡,客户端不会收到任何回调通知。
4. 适用场景
事件通知、状态上报、日志上报等单向通知场景,无需服务端返回结果,同时需要避免客户端主线程因IPC阻塞导致ANR。
六、Parcelable 自定义类型语法规范
自定义对象跨进程传递的唯一合法方式,是实现Parcelable接口,并配套对应的AIDL声明文件,必须严格遵循以下规范。
1. 第一步:创建AIDL声明文件
创建与Java类同名、同包名的AIDL文件,仅做parcelable类型声明,不可包含其他内容:
// User.aidl,包名必须与User.java完全一致
package com.example.aidl;
// 声明parcelable类型,关键字parcelable首字母小写
parcelable User;2. 第二步:Java类实现Parcelable接口
必须实现完整的序列化、反序列化逻辑,同时为了支持out/inoutTag,必须实现readFromParcel()方法:
package com.example.aidl;
import android.os.Parcel;
import android.os.Parcelable;
public class User implements Parcelable {
public int userId;
public String userName;
// 1. 内容描述方法,默认返回0即可
@Override
public int describeContents() {
return 0;
}
// 2. 序列化:将数据写入Parcel
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(userId);
dest.writeString(userName);
}
// 3. 反序列化:必须定义public static final的CREATOR常量
public static final Creator<User> CREATOR = new Creator<User>() {
@Override
public User createFromParcel(Parcel in) {
User user = new User();
user.userId = in.readInt();
user.userName = in.readString();
return user;
}
@Override
public User[] newArray(int size) {
return new User[size];
}
};
// 4. out/inout Tag必须实现的方法:从Parcel读取数据回写到当前对象
public void readFromParcel(Parcel in) {
userId = in.readInt();
userName = in.readString();
}
}3. 第三步:业务AIDL中导入使用
在业务接口AIDL文件中,显式import该Parcelable类型,即可作为方法参数/返回值使用,必须显式添加定向Tag。
七、进阶语法与特殊场景
1. 双向通信:回调接口
AIDL支持将另一个AIDL接口作为参数传递,实现服务端向客户端的反向调用,这是Android跨进程双向通信的标准实现方式。
步骤1:定义回调AIDL接口
// IOnDataChangeListener.aidl
package com.example.aidl;
import com.example.aidl.User;
interface IOnDataChangeListener {
void onUserChanged(in User newUser);
void onError(int errorCode, String errorMsg);
}步骤2:主接口中定义注册/反注册方法
// IUserManager.aidl
package com.example.aidl;
import com.example.aidl.User;
import com.example.aidl.IOnDataChangeListener;
interface IUserManager {
void registerListener(IOnDataChangeListener listener);
void unregisterListener(IOnDataChangeListener listener);
void addUser(in User user);
}注意:回调接口的方法执行时,客户端默认运行在Binder线程池,如需操作UI,必须手动切换到主线程。
2. 稳定版AIDL(Stable AIDL)
Android 10(API 29)引入的稳定版AIDL,用于保证接口的向后兼容性,适用于系统服务、跨应用长期维护的IPC接口。
核心语法
// 开启稳定AIDL支持
@Backing(type="stable")
package com.example.aidl;
interface IStableUserManager {
// 版本1原生方法
void addUser(in User user);
// 版本2新增方法,标注@Since注解
@Since(2)
void updateUser(in User user);
// 弃用方法,标注@Deprecated注解
@Deprecated
void oldMethod();
}配套gradle配置
android {
buildFeatures {
aidl true
}
aidl {
version 2 // 接口版本号
stability "stable" // 开启稳定模式
}
}稳定版AIDL会生成版本化的序列化代码,保证接口升级后,新旧版本的客户端与服务端可正常通信,不会出现方法编号错乱的问题。
八、语法禁忌与高频踩坑点
1. 绝对禁止的语法
- 禁止在AIDL接口中定义成员变量、构造方法、静态代码块;
- 禁止使用
public/private/protected修饰接口和方法; - 禁止使用
static/final/synchronized修饰方法; - 禁止使用枚举、未实现Parcelable的普通JavaBean作为参数/返回值;
- 禁止oneway方法有返回值、使用out/inout参数、抛出检查型异常;
- 禁止使用多维数组、可变参数
...; - 禁止在单个AIDL文件中定义多个顶级接口/parcelable声明。
2. 高频编译报错与踩坑点
- 同包名类型未显式import:AIDL的import规则与Java不同,哪怕同包名,也必须显式导入Parcelable类型和其他AIDL接口,否则编译报错;
- Parcelable文件与Java类包名不一致:必须保证包名、类名完全一致,否则会出现“找不到符号”错误;
- 引用类型参数未加定向Tag:除基本类型、String、CharSequence、IBinder外,所有引用类型必须显式添加in/out/inout,否则编译失败;
- out/inout参数未实现readFromParcel():导致服务端修改无法回传客户端,定向Tag不生效;
- 服务端方法在主线程执行:默认AIDL方法(包括oneway)均运行在Binder线程池,不可直接操作UI,必须保证线程安全;
- 跨应用AIDL文件不一致:客户端与服务端的AIDL文件包名、内容必须完全一致,否则会出现Binder事务编号错乱、通信失败。
九、完整语法示例
1. Parcelable类型声明:User.aidl
package com.example.aidl_demo;
parcelable User;2. 回调接口:IOnDataCallback.aidl
package com.example.aidl_demo;
import com.example.aidl_demo.User;
interface IOnDataCallback {
void onUserUpdate(in User newUser);
void onError(int errorCode, String errorMsg);
}3. 主业务接口:IUserManager.aidl
package com.example.aidl_demo;
// 显式导入所有依赖类型
import com.example.aidl_demo.User;
import com.example.aidl_demo.IOnDataCallback;
interface IUserManager {
// 常量定义
static final int ERROR_USER_NOT_EXIST = -1;
static final int ERROR_NO_PERMISSION = -2;
String KEY_DATA = "user_data";
// 基础in定向Tag方法
boolean addUser(in User user);
// 带返回值+异常声明的方法
User getUserById(int userId) throws android.os.RemoteException;
// inout双向定向Tag方法
void updateUser(inout User user);
// out输出定向Tag方法
void getDefaultUser(out User user);
// oneway异步方法
oneway void notifyUserActive(int userId);
// 回调注册与反注册
void registerCallback(IOnDataCallback callback);
void unregisterCallback(IOnDataCallback callback);
// 集合类型方法
List<User> getAllUser();
Map<String, User> getUserMap();
}