Android AIDL 原理

663 阅读9分钟

Android AIDL 使用详解 一文中,我们知道了如何使用 AIDL 进行进程间通信。那么 AIDL 的实现原理是什么呢?接着上文我们继续深入讨论。

1. 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,如下:

image.png

2. IInterface 接口

很明显生成的 IUserManager 接口继承了 IInterface 接口。

所有可以在 Binder 中传输的接口都需要继承 IInterface 接口。

package android.os;

public interface IInterface
{
    public IBinder asBinder();
}

该接口只有一个 asBinder 方法,每个实现类都需要实现。asBinder 返回一个能进行跨进程通信的 "通道对象"(IBinder 实例)。这个对象是 Android 跨进程通信(IPC)的核心载体。

实际的 IBinder 对象有以下两种:

  • 本地对象(同一进程内):返回 Binder 实例
  • 远程代理(跨进程):返回 BinderProxy 实例

3. DESCRIPTOR

DESCRIPTOR 是一个用于唯一标识 Binder 的字符串。一般用包名加接口名表示。标识 IUserManager。

public static final java.lang.String DESCRIPTOR = "com.example.studysdk.IUserManager";

4. Default 类

生成的 Default 类为接口提供了一个默认的、空的实现。这个类的主要用途是作为接口实现的一个基础或模板,以及在某些情况下作为占位符或测试用途。在开发过程中,可能还没有准备好接口的实际实现,但需要先编译和通过接口定义。此时,Default 类可以作为占位符,使得编译能够通过,同时不会引入实际的逻辑。


  /** 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 中声明的方法

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开始的唯一交易码。

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 接口。

image.png
public class Binder implements IBinder {

当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的 transact 过程。

而当两者位于不同进程时,方法调用需要走 transact 过程,这个逻辑由 Stub 的内部代理类 Proxy 来完成。

6.1 attachInterface

将这个 Stub 对象(也就是 Binder 的子类实例)与一个特定的接口描述符(DESCRIPTOR)关联起来。

Stub 对象就被“标记”为实现了特定接口的对象。这样,当这个对象被传递到另一个进程时,接收方可以通过这个描述符来识别它实现了哪个接口,并据此来调用接口中的方法。

public Stub()
{
  this.attachInterface(this, DESCRIPTOR);
}

6.2 asInterface

用于将服务端的 Binder 对象转换成客户端所需的 AIDL 接口类型的对象。

这种转换过程是区分进程的,如果客户端和服务端位于同一进程,那么此方法返回的就是服务端的 Stub 对象本身,否则返回的是系统封装后的 Stub.proxy 对象。

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 对象。

@Override public android.os.IBinder asBinder()
{
  return this;
}

6.4 onTransact

这个方法运行在服务端中的 Binder 线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。

服务端通过 code 可以确定客户端所请求的目标方法是什么,接着从 data 中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完毕后,就向 reply 中写入返回值(如果目标方法有返回值的话)。

需要注意的是,如果此方法返回 false,那么客户端的请求会失败,因此我们可以利用这个特性来做权限验证,毕竟我们也不希望随便一个进程都能远程调用我们的服务。

@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 接口,但实际功能是将方法调用转发给远程服务。

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

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

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


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 机制中的一个重要部分。

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对象<br>2. 写入接口标识符<br>3. 序列化参数
    
    Proxy->>Binder: transact(TRANSACTION_addUser, data, reply, 0)
    Note over Binder: 跨进程数据传输
    
    Binder->>Stub: onTransact(code, data, reply, flags)
    Note over Stub: 1. 验证接口标识<br>2. 分发到对应方法
    
    Stub->>Service: 调用真实addUser(user)
    Note over Service: 执行业务逻辑
    
    Service->>Stub: 返回结果
    Note over Stub: 1. 写入异常信息<br>2. 返回true
    
    Stub->>Binder: 返回处理结果
    Binder->>Proxy: 唤醒等待线程
    
    Proxy->>Client: 1. 检查状态<br>2. 读取异常<br>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)