在 Android AIDL 使用详解 一文中,我们知道了如何使用 AIDL 进行进程间通信。那么 AIDL 的实现原理是什么呢?接着上文我们继续深入讨论。
1. AIDL 的生成文件
aidl
// IUserManager.aidl
package com.example.studysdk;
import com.example.studysdk.User;
// Declare any non-default types here with import statements
interface IUserManager {
List<User> getUser();
void addUser(in User user);
}
根据 IUserManager.aidl 生成 IUserManager.java,如下:

2. IInterface 接口
很明显生成的 IUserManager 接口继承了 IInterface 接口。
所有可以在 Binder 中传输的接口都需要继承 IInterface 接口。
java
package android.os;
public interface IInterface
{
public IBinder asBinder();
}
该接口只有一个 asBinder 方法,每个实现类都需要实现。asBinder 返回一个能进行跨进程通信的 "通道对象"(IBinder 实例)。这个对象是 Android 跨进程通信(IPC)的核心载体。
实际的 IBinder 对象有以下两种:
- 本地对象 (同一进程内):返回
Binder
实例 - 远程代理 (跨进程):返回
BinderProxy
实例
3. DESCRIPTOR
DESCRIPTOR 是一个用于唯一标识 Binder 的字符串。一般用包名加接口名表示。标识 IUserManager。
java
public static final java.lang.String DESCRIPTOR = "com.example.studysdk.IUserManager";
4. Default 类
生成的 Default
类为接口提供了一个默认的、空的实现。这个类的主要用途是作为接口实现的一个基础或模板,以及在某些情况下作为占位符或测试用途。在开发过程中,可能还没有准备好接口的实际实现,但需要先编译和通过接口定义。此时,Default
类可以作为占位符,使得编译能够通过,同时不会引入实际的逻辑。
java
/** Default implementation for IUserManager. */
public static class Default implements com.example.studysdk.IUserManager
{
@Override public java.util.List<com.example.studysdk.User> getUser() throws android.os.RemoteException
{
return null;
}
@Override public void addUser(com.example.studysdk.User user) throws android.os.RemoteException
{
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
5. 接口方法声明以及方法标识定义
声明了两个方法,也是在 IUserManager.aidl 中声明的方法
java
public java.util.List<com.example.studysdk.User> getUser() throws android.os.RemoteException;
public void addUser(com.example.studysdk.User user) throws android.os.RemoteException;
同时声明两个整型 id 交易码标识这两个方法.这两个 id 用于标识在 transact 过程中客户端所请求的到底是哪个方法。
FIRST_CALL_TRANSACTION
是一个整数值(通常是0x00000001
),表示用户自定义交易码的起始值。任何通过AIDL定义并需要跨进程调用的方法都会被分配一个从FIRST_CALL_TRANSACTION
开始的唯一交易码。
java
static final int TRANSACTION_getUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
6. Stub
接着,它声明了一个内部类 Stub,这个 Stub 就是一个 Binder 类,是一个抽象类,继承 Binder 类,实现了 IUserManager 接口。
java
public class Binder implements IBinder {
当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的 transact 过程。
而当两者位于不同进程时,方法调用需要走 transact 过程,这个逻辑由 Stub 的内部代理类 Proxy 来完成。
6.1 attachInterface
将这个 Stub 对象(也就是 Binder 的子类实例)与一个特定的接口描述符(DESCRIPTOR)关联起来。
Stub 对象就被"标记"为实现了特定接口的对象。这样,当这个对象被传递到另一个进程时,接收方可以通过这个描述符来识别它实现了哪个接口,并据此来调用接口中的方法。
java
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
6.2 asInterface
用于将服务端的 Binder 对象转换成客户端所需的 AIDL 接口类型的对象。
这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的 Stub 对象本身,否则返回的是系统封装后的 Stub.proxy 对象。
java
public static com.example.studysdk.IUserManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.studysdk.IUserManager))) {
return ((com.example.studysdk.IUserManager)iin);
}
return new com.example.studysdk.IUserManager.Stub.Proxy(obj);
}
queryLocalInterface 用于检索与当前 IBinder
对象关联的本地接口实现。descriptor
是一个字符串,它用于指定要检索的接口的描述符。该方法返回一个 IInterface
对象,它代表了与当前 IBinder
对象关联的本地接口实现。如果本地没有实现该接口,则返回 null
。
- 本地调用 :如果调用发生在同一个进程内,
IBinder
对象可能直接指向一个本地对象(如Stub
的实例)。在这种情况下,queryLocalInterface
方法会尝试检索并返回该本地对象的接口实现。 - 远程调用 :如果调用发生在不同进程间,
IBinder
对象将是一个代理对象。在这种情况下,queryLocalInterface
方法会返回null
,因为远程进程无法直接访问本地进程的接口实现。
6.3 asBinder
用于获取一个接口的 IBinder
对象。
java
@Override public android.os.IBinder asBinder()
{
return this;
}
6.4 onTransact
这个方法运行在服务端中的 Binder 线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。
服务端通过 code 可以确定客户端所请求的目标方法是什么,接着从 data 中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完毕后,就向 reply 中写入返回值(如果目标方法有返回值的话)。
需要注意的是,如果此方法返回 false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调用我们的服务。
java
@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException
{
java.lang.String descriptor = DESCRIPTOR;
// 检查 code 是否在有效的跨进程调用范围内
if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) {
data.enforceInterface(descriptor);
}
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
}
switch (code)
{
case TRANSACTION_getUser:
{
java.util.List<com.example.studysdk.User> _result = this.getUser();
reply.writeNoException();
_Parcel.writeTypedList(reply, _result, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
break;
}
case TRANSACTION_addUser:
{
com.example.studysdk.User _arg0;
_arg0 = _Parcel.readTypedObject(data, com.example.studysdk.User.CREATOR);
this.addUser(_arg0);
reply.writeNoException();
break;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
// 方法返回 `true`,表示事务已成功处理。
return true;
}
enforceInterface 用于验证跨进程调用时 Parcel 数据是否包含预期的接口描述符,以确保调用的合法性和安全性。
INTERFACE_TRANSACTION 是一个特殊的 code,用于查询接口的描述符。当 code:
-
等于 INTERFACE_TRANSACTION 时,方法将接口的描述符写入 reply Parcel,并返回 true。
-
等于 TRANSACTION_getUser 时,方法调用本地的 getUser 方法获取用户列表,并将结果写入 reply Parcel。
-
等于 TRANSACTION_addUser 时,调用本地的 addUser 方法添加用户。
-
不匹配任何已知的方法 code,则调用父类的 onTransact 方法来处理未知请求。
6.5 Proxy
Proxy 类是 AIDL 生成文件中的核心组件之一,它是客户端访问远程服务的本地代理。Proxy 在客户端进程内创建的对象,它实现了与服务端相同的 AIDL 接口,但实际功能是将方法调用转发给远程服务。
java
private static class Proxy implements com.example.studysdk.IUserManager
{
private android.os.IBinder mRemote;
// Proxy 就是 ServiceManagerProxy,而 remote 就是 BinderProxy
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public java.util.List<com.example.studysdk.User> getUser() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List<com.example.studysdk.User> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
boolean _status = mRemote.transact(Stub.TRANSACTION_getUser, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.studysdk.User.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addUser(com.example.studysdk.User user) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_Parcel.writeTypedObject(_data, user, 0);
boolean _status = mRemote.transact(Stub.TRANSACTION_addUser, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
- mRemote:持有远程服务的 Binder 代理对象(BinderProxy)
- 构造函数:接收原始 IBinder 对象(来自 ServiceConnection)
- asBinder() :返回持有的原始 IBinder(用于底层操作)
- getInterfaceDescriptor() :返回接口描述符(用于验证)
7. _Parcel
7.1 readTypedObject
java
static private <T> T readTypedObject(
android.os.Parcel parcel,
android.os.Parcelable.Creator<T> c) {
if (parcel.readInt() != 0) {
return c.createFromParcel(parcel);
} else {
return null;
}
}
这个方法用于从 Parcel 中读取一个 Parcelable 对象。它接收两个参数:一个 Parcel 对象和一个 Parcelable.Creator<T> 对象。
- 首先,它通过调用 parcel.readInt() 读取一个整数,这个整数用来表示接下来是否有 Parcelable 对象要读取。
- 如果读取的整数不为0,意味着有一个对象需要被读取,此时它会调用 c.createFromParcel(parcel) 来创建并返回这个对象。
- 如果读取的整数为0,表示没有对象要读取,返回 null 。
7.2 writeTypedObject
java
static private <T extends android.os.Parcelable> void writeTypedObject(
android.os.Parcel parcel, T value, int parcelableFlags) {
if (value != null) {
parcel.writeInt(1);
value.writeToParcel(parcel, parcelableFlags);
} else {
parcel.writeInt(0);
}
}
这个方法用于将一个 Parcelable 对象写入到 Parcel 中。它接收三个参数:一个 Parcel 对象,一个 Parcelable 对象 value ,以及一个 parcelableFlags 整数。
- 如果 value 不为 null ,它会先向 Parcel 中写入一个整数1(表示接下来有对象要写入),然后调用 value.writeToParcel(parcel, parcelableFlags) 将对象写入。
- 如果 value 为 null ,它会向 Parcel 中写入一个整数0(表示没有对象要写入)。
7.3 writeTypedList
java
static private <T extends android.os.Parcelable> void writeTypedList(
android.os.Parcel parcel, java.util.List<T> value, int parcelableFlags) {
if (value == null) {
parcel.writeInt(-1);
} else {
int N = value.size();
int i = 0;
parcel.writeInt(N);
while (i < N) {
writeTypedObject(parcel, value.get(i), parcelableFlags);
i++;
}
}
}
这个方法用于将一个 Parcelable 对象的列表写入到 Parcel 中。它接收三个参数:一个 Parcel 对象,一个 Parcelable 对象列表 value,以及一个 parcelableFlags 整数。
-
如果 value 为 null,它会向 Parcel 中写入整数-1(可能表示列表为空或不存在)。
-
如果 value 不为 null,它会先写入列表的大小(N),然后遍历列表,对每个元素调用 writeTypedObject 方法写入到 Parcel 中。
_Parcel 类中的这些方法为 AIDL 通信提供了基础的数据序列化和反序列化支持。通过这些方法,可以方便地将 Parcelable 对象和对象列表在进程间传输,是 Android IPC 机制中的一个重要部分。
java
static class _Parcel {
static private <T> T readTypedObject(
android.os.Parcel parcel,
android.os.Parcelable.Creator<T> c) {
if (parcel.readInt() != 0) {
return c.createFromParcel(parcel);
} else {
return null;
}
}
static private <T extends android.os.Parcelable> void writeTypedObject(
android.os.Parcel parcel, T value, int parcelableFlags) {
if (value != null) {
parcel.writeInt(1);
value.writeToParcel(parcel, parcelableFlags);
} else {
parcel.writeInt(0);
}
}
static private <T extends android.os.Parcelable> void writeTypedList(
android.os.Parcel parcel, java.util.List<T> value, int parcelableFlags) {
if (value == null) {
parcel.writeInt(-1);
} else {
int N = value.size();
int i = 0;
parcel.writeInt(N);
while (i < N) {
writeTypedObject(parcel, value.get(i), parcelableFlags);
i++;
}
}
}
}
8. 总结
首先,当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在UI线程中发起此远程请求;
由于服务端的 Binder 方法运行在 Binder 的线程池中,所以 Binder 方法不管是否耗时都应该采用同步的方式去实现,因为它已经运行在一个线程中了。
AIDL客户端调用服务端全流程:
- 绑定服务 :客户端通过
bindService()
请求连接目标服务 - 接收通道 :系统回调
onServiceConnected()
传递底层IBinder
通信通道 - 接口转换 :通过
Stub.asInterface()
将原始 IBinder 转换为业务接口 - 发起调用 :客户端直接调用 AIDL 接口方法(如
service.getData()
) - 参数封装:Proxy 自动将方法参数序列化为 Parcel 数据包
- 跨进程传输:通过 Binder 驱动将请求转发到服务端进程
- 服务处理:服务端 Stub 解析请求并执行实际业务逻辑
- 结果返回:服务端将处理结果序列化后通过原通道返回
- 结果解析:客户端 Proxy 反序列化数据并返回给调用方
- 异常处理 :客户端必须捕获
RemoteException
处理通信失败
关键补充点:
- 线程阻塞:同步调用会阻塞客户端线程直至返回
- 资源回收:系统自动管理底层IPC资源
- 生命周期 :需在合适时机调用
unbindService()
释放连接
大概流程:
2. 写入接口标识符
3. 序列化参数 Proxy->>Binder: transact(TRANSACTION_addUser, data, reply, 0) Note over Binder: 跨进程数据传输 Binder->>Stub: onTransact(code, data, reply, flags) Note over Stub: 1. 验证接口标识
2. 分发到对应方法 Stub->>Service: 调用真实addUser(user) Note over Service: 执行业务逻辑 Service->>Stub: 返回结果 Note over Stub: 1. 写入异常信息
2. 返回true Stub->>Binder: 返回处理结果 Binder->>Proxy: 唤醒等待线程 Proxy->>Client: 1. 检查状态
2. 读取异常
3. 返回void
onServiceConnected 回调的 IBinder 对象:
场景 | service 实际类型 | 特点 |
---|---|---|
同进程 | Stub 实例 | 直接内存引用 |
跨进程 | BinderProxy 实例 | 驱动创建的跨进程代理 |
服务未启动 | null | 服务不存在或绑定失败 |