41. AIDL 原理:AIDL 工具如何生成 Binder 通信的 Stub 与 Proxy 代码?

好,我们直接切入正题。AIDL 这玩意儿,说白了就是 Android 帮你写 Binder 代码的“代码生成器”。你写个 .aidl 接口文件,它就能给你生成一套完整的 C/S 通信骨架。很多新手觉得 AIDL 很神秘,其实它就是个模板引擎,干的是“翻译”的活。

我个人习惯把 AIDL 生成的代码分成三块来看:接口、Stub、Proxy。理解了这三块,你就看透了 AIDL 的本质。

1. 从 .aidl 到 .java:编译器的“翻译”过程

假设你写了一个简单的 AIDL 接口:

// IMyService.aidl
package com.example;

interface IMyService {
    int add(int a, int b);
    String getMessage();
}

当你点击 Build 或者执行 Gradle 编译时,Android SDK 中的 aidl.exe 工具就会登场。它会解析这个文件,生成一个同名的 Java 接口文件 IMyService.java

这个生成的 Java 文件,结构大致如下:

public interface IMyService extends android.os.IInterface {
    // 从 AIDL 方法映射过来的抽象方法
    public int add(int a, int b) throws android.os.RemoteException;
    public String getMessage() throws android.os.RemoteException;

    // 内部静态抽象类 Stub
    public static abstract class Stub extends android.os.Binder implements IMyService {
        // ... 核心实现
    }

    // 内部代理类 Proxy
    private static class Proxy implements IMyService {
        // ... 核心实现
    }
}

嗯,这里要注意:生成的接口继承了 IInterface,这是所有 Binder 接口的根接口。Stub 和 Proxy 都实现了这个接口。

2. Stub 类:服务端的“骨架”

Stub 是服务端代码的核心。它继承了 android.os.Binder,并实现了你的 AIDL 接口。说白了,它就是一个 Binder 本地对象。

生成的 Stub 类里,最关键的是 onTransact() 方法。这个方法就是 Binder 驱动回调服务端的地方。我们来看看它长什么样:

@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
    String descriptor = DESCRIPTOR;
    switch (code) {
        case TRANSACTION_add: {
            data.enforceInterface(descriptor);
            int _arg0;
            _arg0 = data.readInt();
            int _arg1;
            _arg1 = data.readInt();
            int _result = this.add(_arg0, _arg1);
            reply.writeNoException();
            reply.writeInt(_result);
            return true;
        }
        case TRANSACTION_getMessage: {
            data.enforceInterface(descriptor);
            String _result = this.getMessage();
            reply.writeNoException();
            reply.writeString(_result);
            return true;
        }
        default: {
            return super.onTransact(code, data, reply, flags);
        }
    }
}

这段代码的逻辑非常清晰:

  • 反序列化:从 data Parcel 中读取参数(比如 readInt())。
  • 调用本地方法:调用你在 Stub 子类中实现的 add()getMessage()
  • 序列化结果:将返回值写入 reply Parcel。

我在项目中遇到过一个问题:有人直接在 onTransact 里做耗时操作,导致 UI 线程卡死。记住,onTransact 默认运行在 Binder 线程池中,但如果你在服务端主线程调用了 Binder 方法,那它还是跑在主线程上。这个坑我曾经踩过,后来养成了习惯:服务端方法里一律开线程或使用 Handler。

3. Proxy 类:客户端的“代理”

客户端不能直接调用服务端的方法,它只能通过 Binder 驱动发送数据。Proxy 就是干这个的。它持有一个 IBinder 引用(即 mRemote),所有调用都通过它来转发。

生成的 Proxy 代码大概是这样:

private static class Proxy implements IMyService {
    private android.os.IBinder mRemote;

    Proxy(android.os.IBinder remote) {
        mRemote = remote;
    }

    @Override
    public int add(int a, int b) throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            _data.writeInt(a);
            _data.writeInt(b);
            mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
            _reply.readException();
            _result = _reply.readInt();
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }

    @Override
    public String getMessage() throws android.os.RemoteException {
        // 类似实现,略
    }
}

你看,Proxy 做的事情正好和 Stub 相反:

  • 序列化参数:把方法参数写入 _data Parcel。
  • 发起 transact 调用:调用 mRemote.transact(),把数据包发给 Binder 驱动。
  • 反序列化结果:从 _reply Parcel 中读取返回值。

这里有个细节:transact 的最后一个参数 flags 传的是 0,表示这是一个同步调用。如果你传 FLAG_ONEWAY,那就是异步调用,客户端不会等待服务端返回。我建议你在不需要返回值时使用 oneway,能显著提升性能。

4. 核心逻辑流程图

为了让你更直观地理解整个流程,我画了一张图。它展示了从客户端调用到服务端响应的完整数据流。

AIDL 生成的 Stub 与 Proxy 通信流程 客户端进程 服务端进程 Binder 驱动(内核) Proxy.add() 序列化参数 → transact() Parcel 对象 _data / _reply Stub.onTransact() 反序列化参数 → 调用本地方法 本地实现 add() / getMessage() 发送数据 分发数据 返回结果 返回结果 客户端通过 Proxy 序列化请求 → Binder 驱动转发 → 服务端 Stub 反序列化并执行 → 结果原路返回

5. 关键设计要点

AIDL 工具生成的代码里,有几个设计模式值得注意:

设计模式 体现位置 说明
代理模式 Proxy 类 客户端通过 Proxy 间接访问服务端,Proxy 封装了跨进程通信的细节。
模板方法模式 Stub 类 onTransact() 定义了通信骨架,子类只需实现业务方法。
静态工厂方法 Stub.asInterface() 根据是否跨进程,返回 Proxy 或本地对象。这是 Binder 的经典用法。

核心要点: AIDL 生成的代码本质上是一个“序列化/反序列化”的适配层。它把 Java 方法调用转换成 Parcel 数据,再通过 Binder 驱动传输。你写的业务逻辑,被包裹在 Stub 和 Proxy 之间,完全感知不到跨进程的存在。

个人技巧: 我习惯在生成代码后,打开 IMyService.java 看一眼。重点关注 TRANSACTION_ 常量的值,它们是方法编号。如果 AIDL 接口方法很多,这些编号会从 1 开始递增。我曾经在修改 AIDL 时不小心删了一个方法,导致后面的编号全部错位,客户端和服务端版本不一致时直接崩溃。所以,永远不要手动修改生成的代码,也尽量不要在已发布的 AIDL 接口中间插入新方法。

避坑指南: 我曾经遇到过一个线上问题:客户端调用服务端方法时,偶尔会抛出 TransactionTooLargeException。查了半天发现,是 AIDL 方法里传了一个很大的 Bitmap。Binder 事务缓冲区默认只有 1MB,超过就会抛异常。解决方案是改用文件描述符或者分片传输。记住,Binder 不是用来传大数据的

最后总结一下:AIDL 工具生成的 Stub 和 Proxy,就是 Binder 通信的“翻译官”。Stub 在服务端负责“收包并执行”,Proxy 在客户端负责“打包并发送”。你只需要关注业务逻辑,剩下的脏活累活,AIDL 都帮你干了。


公众号:蓝海资料掘金营,微信deep3321