Android AIDL 原理

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客户端调用服务端全流程:

  1. 绑定服务 :客户端通过bindService()请求连接目标服务
  2. 接收通道 :系统回调onServiceConnected()传递底层IBinder通信通道
  3. 接口转换 :通过Stub.asInterface()将原始 IBinder 转换为业务接口
  4. 发起调用 :客户端直接调用 AIDL 接口方法(如service.getData()
  5. 参数封装:Proxy 自动将方法参数序列化为 Parcel 数据包
  6. 跨进程传输:通过 Binder 驱动将请求转发到服务端进程
  7. 服务处理:服务端 Stub 解析请求并执行实际业务逻辑
  8. 结果返回:服务端将处理结果序列化后通过原通道返回
  9. 结果解析:客户端 Proxy 反序列化数据并返回给调用方
  10. 异常处理 :客户端必须捕获RemoteException处理通信失败

关键补充点:

  1. 线程阻塞:同步调用会阻塞客户端线程直至返回
  2. 资源回收:系统自动管理底层IPC资源
  3. 生命周期 :需在合适时机调用unbindService()释放连接

大概流程:

sequenceDiagram participant Client as 客户端 participant Proxy as Proxy participant Binder as Binder驱动 participant Stub as Stub participant Service as 服务实现 Client->>Proxy: 调用addUser(user) Note over Proxy: 1. 创建Parcel对象
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 服务不存在或绑定失败
sequenceDiagram participant App as 客户端App participant AMS as ActivityManagerService participant Driver as Binder驱动 participant Server as 服务端进程 App->>AMS: bindService(Intent) AMS->>Server: 创建/唤醒服务进程 Server->>Driver: 注册Binder对象 Driver->>Driver: 生成BinderProxy Driver->>AMS: 返回Binder引用 AMS->>App: 回调onServiceConnected(proxy)
相关推荐
CYRUS_STUDIO7 分钟前
逆向 JNI 函数找不到入口?动态注册定位技巧全解析
android·逆向·源码阅读
时光追逐者1 小时前
.NET初级软件工程师面试经验分享
经验分享·面试·职场和发展·c#·.net·.netcore
海的诗篇_3 小时前
前端开发面试题总结-vue2框架篇(四)
前端·css·面试·vue·html
whysqwhw3 小时前
Egloo 中Kotlin 多平台中的 expect/actual
android
用户2018792831673 小时前
《Android 城堡防御战:ProGuard 骑士的代码混淆魔法》
android
路人与大师4 小时前
2025年 6月面试 经验总结 生成式语言模型岗位
面试·生成式语言模型
用户2018792831674 小时前
🔐 加密特工行动:Android 中的 AES 与 RSA 秘密行动指南
android
用户2018792831675 小时前
Android开发的"魔杖"之ADB命令
android
_荒5 小时前
uniapp AI流式问答对话,问答内容支持图片和视频,支持app和H5
android·前端·vue.js