浅学进程间通信4(Android Binder)

26 阅读7分钟

Binder 的基本原理

网上有很多介绍Binder的文章,这里不过多介绍, 首先要明确一点 Binder 是一个 RPC(Remote Procedure Call) 框架,也就是说借助于 Binder,我们可以在 A 进程中访问 B 进程中的函数

RPC 一般基于 IPC(Inter-Process Communication) 来实现的,IPC 就是跨进程数据传输(通信)

Binder 的 RPC 是如何实现的:

一般来说,A 进程访问 B 进程函数,我们需要:

  • 在 A 进程中按照固定的规则打包数据,这些数据包含了:

    • 数据发给那个进程,Binder 中是一个整型变量 Handle
    • 要调用目标进程中的那个函数,Binder 中用一个整型变量 Code 表示
    • 目标函数的参数
    • 要执行具体什么操作,也就是 Binder 协议
  • 进程 B 收到数据,按照固定的格式解析出数据,调用函数,并使用相同的格式将函数的返回值传递给进程 A。

binder_rpc.png

Binder 应用层工作流程

Binder 是一个 RPC(Remote Procedure Call) 框架,翻译成中文就是远程过程调用。也就是说通过 Binder:

  • 可以在 A 进程中访问 B 进程中定义的函数
  • 进程 B 中的这些等待着被远程调用的函数的集合,我们称其为 Binder 服务(Binder Service)
  • 进程 A 称之为 Binder 客户端(Binder Client) ,进程 B 称之为 Binder 服务端(Binder Server)
  • 通常,系统中的服务很多,我们需要一个管家来管理它们,服务管家(ServiceManager)  是 Android 系统启动时,启动的一个用于管理 Binder 服务(Binder Service)  的进程。通常,服务(Service)  需要事先注册到服务管家(ServiceManager) ,其他进程向服务管家(ServiceManager)  查询服务后才能使用服务。
  • Binder 的 RPC 能力通过 **Binder 驱动**实现

通常一个完整的 Binder 程序涉及 4 个流程:

  1. 在 Binder Server 端定义好服务
  2. 然后向 ServiceManager 注册服务
  3. 在 Binder Client 中向 ServiceManager 获取到服务
  4. 发起远程调用,调用 Binder Server 中定义好的服务

binder概图.png

学习与参考: 002.Binder 基本原理 | Ahao's Technical Blog

AIDL(Android Interface Definition Language)

AIDL(安卓接口定义语言) 是 Android 中最常用的跨进程通信机制,主要用于应用层进程间的通信。

主要特点

  • 应用层IPC:主要用于应用之间的通信
  • 基于Binder:底层使用 Binder 机制
  • 支持复杂数据类型:基本类型、String、List、Map、Parcelable 对象等
  • 同步/异步调用:默认同步,可通过 oneway 实现异步

Android的AIDL(Android接口定义语言)是一套完整的跨进程通信API体系。它不仅支持基础数据类型和接口回调,还提供了同步/异步调用、权限控制、服务管理等一系列核心功能。

核心API与关键概念

下表为你梳理了AIDL的核心功能模块:

模块主要API/概念说明与用途
1. 接口定义AIDL文件 (.aidl)使用AIDL语法声明跨进程的接口契约。
2. 核心通信对象IBinder所有IPC通信的底层句柄。Service.onBind返回它。
IInterface所有AIDL接口的根基,定义了与IBinder交互的能力。
Binder服务端的基类,实现了IBinder,用于处理远程调用。
Stub (抽象类)由AIDL编译器生成的服务器存根,继承自Binder并实现主接口。
Proxy (内部类)由AIDL编译器生成的客户端代理,封装了跨进程调用序列化逻辑。
3. 类型系统基本类型int, long, char, boolean, double, byte, float
String字符串,支持UTF-8编码。
CharSequence可直接传递SpannableString等。
Parcelable通过实现该接口,自定义可序列化的复杂对象。
List / Map集合类型,其元素必须为AIDL支持的类型。
定向Tagin(输入), out(输出), inout(双向),修饰非基本类型参数。
4. 接口回调RemoteCallbackList用于在服务端安全地管理并回调多个客户端监听器。
5. 调用模式同步调用默认模式,客户端线程会阻塞直至服务端返回。
异步调用使用oneway关键字修饰接口方法,调用不阻塞。
6. 异常处理RemoteException所有跨进程调用都可能抛出的根异常,如进程死亡、通信失败等。
SecurityException绑定服务或调用时,权限校验失败会抛出。
7. 服务绑定Service承载AIDL接口的服务组件。
ServiceConnection客户端用于监听与服务的连接状态。
bindService() / unbindService()绑定与解绑服务的方法。
Context.BIND_AUTO_CREATE绑定标志,服务不存在时自动创建。

💻 深度解析与最佳实践

1. 通信核心:Binder、Stub与Proxy

这是AIDL工作的基石。服务端实现 Stub,客户端通过 Proxy 调用,两者通过Binder驱动进行数据交换。

// 服务端实现
public class MyService extends Service {
    private final IMyService.Stub binder = new IMyService.Stub() {
        @Override
        public int calculate(int a, int b) throws RemoteException {
            return a + b;
        }
    };
    
  @Override 
  public IBinder onBind(Intent intent) { 
      return binder; 
  }
}
// 客户端调用
ServiceConnection conn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // 关键:将IBinder转换为接口代理对象
        IMyService myService = IMyService.Stub.asInterface(service);
        myService.calculate(5, 3);
    }
};

2. 高级数据类型与方向Tag

Parcelable 是实现复杂数据传输的关键。

// 1. 定义 Parcelable 数据类
public class User implements Parcelable {
    public String name;
    public int id;
    // ... 必须实现 writeToParcel, describeContents, CREATOR
}

// 2. 为 Parcelable 创建对应的 .aidl 文件 (User.aidl)
// parcelable User;

// 3. 在接口AIDL中引用
// import com.example.User; // 显式导入
// void updateUser(in User user);

方向Tag (in, out, inout) 直接影响性能和逻辑:

  • in:数据从客户端流向服务端,服务端修改不会影响客户端对象。
  • out:对象从服务端“返回”给客户端。客户端传入的对象字段不会被发送,但服务端可以创建或修改对象并传回。
  • inout:双向传输,开销最大,应谨慎使用。 方向修饰符(仅对 “非基本类型 & 非 String/CharSequence” 参数生效)
void printUser(in User u); // 客户端把 User 序列化传过来  
void fillUser(out User u); // 服务端 new 一个 User 写回去  
void updateUser(inout User u); // 两端都可改

3. 管理接口回调:RemoteCallbackList

在服务端,不能直接保存客户端传来的 IBinder 回调接口,因为Binder对象可能因进程死亡而失效。RemoteCallbackList 能自动管理这种生命周期。

public class MyService extends Service {
    private final RemoteCallbackList<ICallback> callbackList = new RemoteCallbackList<>();

    private final IMyService.Stub binder = new IMyService.Stub() {
        @Override
        public void registerCallback(ICallback cb) {
            callbackList.register(cb);
        }
        @Override
        public void unregisterCallback(ICallback cb) {
            callbackList.unregister(cb);
        }
    };

    // 向所有客户端广播事件
    private void broadcastEvent(String event) {
        int n = callbackList.beginBroadcast();
        for (int i = 0; i < n; i++) {
            try {
                callbackList.getBroadcastItem(i).onEvent(event);
            } catch (RemoteException e) {
                // 客户端可能已死亡,忽略
            }
        }
        callbackList.finishBroadcast();
    }
}

4. 同步 (two-way) 与异步 (oneway)

  • 同步:客户端线程会阻塞,适用于需要立即结果的调用。
  • 异步:在接口方法前加 oneway 关键字,不等待返回,也没有返回值。适用于通知型操作,能有效避免客户端阻塞,但需注意其“至多一次 (at-most-once)”的语义,调用可能因进程死亡而丢失。
// 同步方法
int calculate(in int a, in int b);

// 异步方法(无返回值,不抛RemoteException)
oneway void notifySomethingHappened(in String event);

5. 权限控制与安全

可在 AndroidManifest.xml 中为服务声明自定义权限,并在 onBind 或具体方法内校验。

<service
    android:name=".MyService"
    android:permission="com.example.MY_PERMISSION"
    android:exported="true"> <!-- 允许其他应用绑定 -->
</service>
// 在服务端的onBind或方法实现中校验
public IBinder onBind(Intent intent) {
    if (checkCallingOrSelfPermission("com.example.MY_PERMISSION") 
        != PackageManager.PERMISSION_GRANTED) {
        return null; // 绑定失败
    }
    return binder;
}

🎯 性能与稳定性最佳实践

  1. 减少跨进程调用次数:设计接口时,尽量将多个操作合并为一次调用(如传递一个User对象,而非多次调用设置nameid)。
  2. 注意线程模型:AIDL调用在服务端默认运行在 Binder线程池 中(非主线程),因此需要进行线程同步。使用 oneway 调用时,服务端方法仍会在Binder线程池中执行。
  3. 妥善处理 RemoteException:所有跨进程调用都必须捕获此异常,它意味着连接已断开。
  4. 及时解绑:在客户端(如Activity)的 onDestroy 中调用 unbindService,防止内存泄漏。
  5. 接口版本兼容:为已发布的AIDL接口添加新方法时,考虑向后兼容,例如使用默认实现或版本号协商。

Demo 示例

IRemoteService.aidl

package com.example.aidl;
import com.example.aidl.User;   // 自定义 Parcelable
interface IRemoteService {
    int add(int a, int b);                // 基本类型
    void printUser(in User u);            // in 方向
    void getUser(out User u);             // out 方向
    oneway void fireCallback();           // 非阻塞
}

User.aidl

package com.example.aidl;
parcelable User;

服务端实现

public class RemoteService extends Service {
    private final IRemoteService.Stub binder = new IRemoteService.Stub() {
    
        public int add(int a, int b) { return a + b; }
        
        public void printUser(User u) { Log.d("AIDL", u.name); }
        
        public void getUser(User u) {
            u.name = "server"; u.age = 18;   // 填充 out 参数
        }
        
        public void fireCallback() { /* 单向,无返回 */ }
    };
    
    public IBinder onBind(Intent i) { return binder; }
}

客户端调用

IRemoteService s = IRemoteService.Stub.asInterface(service);
int r = s.add(1, 2);