AIDL 详解:语法、接口定义与自动生成的 Stub/Proxy
AIDL,全称 Android Interface Definition Language,是 Android 内部进程通信(IPC)的核心工具。说白了,它就是用来在不同进程之间「传话」的。你写一个 .aidl 文件,然后编译工具帮你生成一堆 Java 代码。我自己做 Binder Mock 回放时,最先摸透的就是这一层——因为如果不理解 Stub 和 Proxy 在干嘛,后期 Mock 回放根本无从下手。
AIDL 的语法要点
先看一个完整的 .aidl 接口定义:
// IStudentManager.aidl
package com.example.binder.demo;
import com.example.binder.demo.Student;
import com.example.binder.demo.IScoreCallback;
interface IStudentManager {
// 默认是阻塞调用
void registerCallback(IScoreCallback callback);
void unregisterCallback(IScoreCallback callback);
// 非阻塞调用,用 oneway 修饰
oneway void notifyScoreChanged(String studentId, int score);
// 输入输出方向
void getStudentInfo(in String id, out Student info);
void updateScores(inout List<Student> students);
}
语法部分我挑几个常用的说一下:
- 包声明:和 Java 一样,package 必须写,且最终生成的 Java 代码会放到对应包下。
- 导入:所有非基本类型、非 String、非 List/Map 的自定义 Parcelable 类型都需要显式 import。
- 方法定义:返回值类型 + 方法名 + 参数列表。参数可以带方向标识:
in、out、inout。 - oneway 关键字:如果方法用
oneway修饰,调用端会立即返回,不会等待服务端处理完成。适合通知类场景。
in 导致 Parcel 只把数据写过去,读回来时自然拿不到结果。
自动生成的 Stub 与 Proxy
当你写完 .aidl 文件,Android SDK 的 aidl.exe 工具会帮你生成一个 .java 文件。以 IStudentManager 为例,生成的文件里会包含两个核心内部类:Stub 和 Proxy。
| 角色 | 所在端 | 核心职责 |
|---|---|---|
| Stub | 服务端 | 继承自 Binder,实现接口方法,提供 onTransact 分发逻辑 |
| Proxy | 客户端 | 封装 transact 调用,把参数打包到 Parcel,并处理返回数据 |
Stub 的原理
Stub 本身是一个抽象类,你需要在服务端写一个具体实现类继承它。它的核心方法是 onTransact:
@Override
protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
switch (code) {
case TRANSACTION_registerCallback: {
// 1. 读取参数
data.enforceInterface(DESCRIPTOR);
IScoreCallback _arg0 = IScoreCallback.Stub.asInterface(data.readStrongBinder());
// 2. 调用开发者实现的方法
this.registerCallback(_arg0);
// 3. 写回结果(如果需要)
reply.writeNoException();
return true;
}
// ... 其他方法
}
return super.onTransact(code, data, reply, flags);
}
这里有个关键点:onTransact 运行在 Binder 线程池中,不是你主线程。所以如果你的接口方法里要更新 UI,记得自己切回主线程。嗯,这个坑我踩过——服务端直接抛了 CalledFromWrongThreadException。后来我在 Stub 的实现里加了 Handler 转发。
Proxy 的原理
当你拿到服务端的 IBinder 对象(比如通过 bindService 返回的),其实你拿到的是 Stub 的一个代理引用。客户端调用时,实际上调用的是 Proxy 的同名方法:
public void registerCallback(IScoreCallback callback) throws RemoteException {
Parcel _data = Parcel.obtain();
Parcel _reply = Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder(callback != null ? callback.asBinder() : null);
// 发起跨进程调用
mRemote.transact(Stub.TRANSACTION_registerCallback, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
注意:transact 默认是同步阻塞的。如果你搞了 oneway 方法,那么 flags 参数会传 FLAG_ONEWAY,调用后立即返回,_reply 为空 parcel。
_reply.readException(),服务端抛了异常,结果客户端一直傻等。血泪教训——务必在调用后检查异常,否则可能引发 ANR。
数据方向标识 in / out / inout
AIDL 支持三种参数方向,影响序列化和反序列化的行为:
- in(默认):服务端只收到客户端传过来的数据,服务端修改参数不会回传给客户端。
- out:客户端不传数据,服务端负责构造对象并填充,然后回传给客户端。适用于服务端生成数据的场景。
- inout:双向传输,客户端传过去,服务端改完后返回。
这个方向标识会直接影响生成代码中对 Parcel 的读写顺序。我建议在接口设计时尽量用 in,因为 inout 会多一次 Parcel 操作,性能稍微差一点。
核心结论: AIDL 是 Binder 机制的“契约文件”。Stub 和 Proxy 则是编译器帮我们生成的桥梁。理解它们的工作方式,才能去手动 Mock 或回放 IPC 数据。
总结一个实用 checklist
- 写 AIDL 前先确定哪些类型需要 Parcelable 化(自定义类必须实现 Parcelable 接口)。
- 接口方法尽量少用
inout,能用in就用in,否则容易造成不必要的数据拷贝。 - Stub 中的
onTransact运行在 Binder 线程,非主线程,注意线程安全。 - 客户端 Proxy 调用务必检查
reply.readException(),否则远程异常永远不会被发现。 - 如果你要 Mock 回放 IPC 调用,最直接的方式就是替换掉 Stub 或 Proxy 的实现,直接绕过 Binder 驱动。
oneway,但忘记处理 Binder 引用计数导致服务端端回调一直没释放。后来在 onTransact 里加了 linkToDeath 监听才搞定。所以说,Binder 的生命管理一定要小心。
好了,AIDL 的核心内容就这些。你只要吃透 Stub 和 Proxy 的生成逻辑,后面做 Mock 回放就相当于在“替换”这套桥梁——把真实的 Binder 调用替换成你预设的数据包。这部分我们后面章节会手把手带。