4. AIDL 服务端与客户端实现:Service 端创建、bindService 流程、Stub 与 Proxy 机制
在多 Camera HAL 架构中,AIDL 通信的稳定性与性能直接决定了上层 Framework 与底层硬件服务之间的数据通路质量。本章将深入剖析 AIDL 服务端与客户端的完整实现链路,重点讲解 Service 端创建、bindService 绑定流程,以及核心的 Stub 与 Proxy 代理机制。理解这些底层原理,是构建高可靠、低延迟的多 Camera 通信管道的基础。
4.1 AIDL 服务端创建:从接口定义到 Service 实现
AIDL 服务端的创建遵循“接口定义 → Stub 生成 → Service 实现”的三步范式。在多 Camera 场景下,服务端通常运行在一个独立的系统进程中(如 cameraserver),负责管理多个 Camera 设备句柄与数据流。
4.1.1 接口定义与自动生成
首先,在 .aidl 文件中声明服务接口。以下是一个简化的多 Camera 控制接口示例:
// ICameraControl.aidl
package com.example.camera;
interface ICameraControl {
int openCamera(int cameraId);
int closeCamera(int cameraId);
int startPreview(int cameraId, in String previewSurface);
int stopPreview(int cameraId);
int setParameters(int cameraId, in String parameters);
}
编译后,AIDL 工具会自动生成 ICameraControl.java 接口文件,其中包含一个名为 Stub 的抽象静态内部类。Stub 类继承自 android.os.Binder 并实现了 ICameraControl 接口,它负责将远程调用请求反序列化并分发给具体的方法实现。
4.1.2 Service 端实现:继承 Stub 并注册
服务端需要创建一个继承自 ICameraControl.Stub 的具体类,并重写所有接口方法。在多 Camera 场景下,每个方法内部通常需要加锁以保护共享的 Camera 设备列表。
public class CameraControlService extends Service {
private final ICameraControl.Stub mBinder = new ICameraControl.Stub() {
@Override
public int openCamera(int cameraId) throws RemoteException {
// 实际逻辑:打开对应 cameraId 的硬件设备
// 注意线程安全与资源管理
return 0; // 成功返回 0
}
@Override
public int closeCamera(int cameraId) throws RemoteException {
// 关闭设备并释放资源
return 0;
}
@Override
public int startPreview(int cameraId, String previewSurface) throws RemoteException {
// 配置数据流并启动预览
return 0;
}
// ... 其他方法实现
};
@Override
public IBinder onBind(Intent intent) {
return mBinder; // 返回 Stub 实例
}
}
onBind() 方法返回给 Binder 驱动。驱动会将该 Binder 引用传递给客户端,客户端通过该引用发起跨进程调用。
4.2 bindService 流程:客户端如何找到服务端
客户端通过 bindService() 发起绑定请求,整个流程涉及 Context、AMS(ActivityManagerService)、Binder 驱动以及服务端进程的协同工作。以下流程图展示了完整的绑定链路:
4.3 Stub 与 Proxy 机制:跨进程调用的核心
Stub 和 Proxy 是 AIDL 自动生成的一对对称代理类,它们共同封装了 Binder 通信的序列化与反序列化细节。理解这对机制,是掌握 AIDL 性能调优与问题排查的关键。
4.3.1 架构对比:Stub 与 Proxy 的角色
| 角色 | 所在进程 | 核心职责 | 关键方法 |
|---|---|---|---|
| Stub | 服务端 | 接收 Binder 驱动传来的请求,反序列化参数,调用服务端实现方法,序列化返回结果 | onTransact() |
| Proxy | 客户端 | 封装客户端调用,序列化参数,通过 Binder 驱动发送请求,等待并反序列化返回结果 | transact() |
4.3.2 调用流程:从 Proxy 到 Stub 的完整链路
当客户端调用 AIDL 接口方法时,实际调用的是 Proxy 类的同名方法。Proxy 内部执行以下步骤:
- 序列化参数: 将方法参数写入
Parcel对象。 - 发起 transact: 调用
mRemote.transact(Stub.TRANSACTION_xxx, data, reply, 0),将数据包发送给 Binder 驱动。 - 驱动转发: Binder 驱动根据目标 Binder 引用,将数据包路由到服务端进程的线程池。
- Stub.onTransact: 服务端线程池中的线程调用
Stub.onTransact(),根据code字段识别方法,反序列化参数。 - 调用实现: 调用服务端实际的方法实现,获取返回结果。
- 返回结果: 将返回结果序列化写入
replyParcel,通过 Binder 驱动返回给客户端。
// 客户端 Proxy 调用示例(自动生成)
public int openCamera(int cameraId) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
try {
data.writeInterfaceToken(DESCRIPTOR);
data.writeInt(cameraId);
// 发起跨进程调用,阻塞等待返回
mRemote.transact(Stub.TRANSACTION_openCamera, data, reply, 0);
reply.readException();
return reply.readInt();
} finally {
reply.recycle();
data.recycle();
}
}
transact() 调用都会涉及两次进程上下文切换(客户端→服务端→客户端)。在多 Camera 高帧率场景下,应尽量减少单帧处理中的跨进程调用次数,优先采用批量参数传递或共享内存(如 SurfaceFlinger 的 BufferQueue)来降低开销。
4.3.3 多 Camera 场景下的 Stub 线程模型
服务端 Binder 线程池默认大小为 16,所有 Camera 设备的 AIDL 调用共享该线程池。当多个 Camera 同时发起高频率调用时,可能出现线程饥饿或死锁风险。以下表格总结了常见问题与应对策略:
| 问题场景 | 原因 | 解决方案 |
|---|---|---|
| 线程池耗尽 | 多个 Camera 同时发起同步调用,占满 16 个线程 | 增加 Binder 线程池大小(setMaxThreads);或改用异步 oneway 调用 |
| 死锁 | Stub 方法内部又调用了其他 Binder 服务,形成循环等待 | 严格避免在 Binder 线程中同步调用其他 Binder 服务;使用 Handler 切换到工作线程 |
| 优先级反转 | 低优先级 Camera 调用阻塞了高优先级 Camera 的请求 | 为不同 Camera 设备分配独立的 Service 实例;或使用 Binder 的优先级继承机制 |
4.4 实战要点:Service 注册与多 Camera 实例管理
在 HAL 架构中,Service 通常注册到 ServiceManager,以便客户端通过名称查找。多 Camera 场景下,有两种常见的注册策略:
- 单 Service 多 Camera: 一个 Service 实例管理所有 Camera 设备,通过
cameraId参数区分。优点是连接开销小,缺点是存在单点竞争。 - 多 Service 独立管理: 每个 Camera 设备对应一个独立的 Service 实例,注册时使用不同的名称(如
camera_service_0、camera_service_1)。优点是隔离性好,缺点是客户端需要管理多个连接。
// 服务端注册示例(单 Service 多 Camera)
public class CameraService extends Service {
private static final String SERVICE_NAME = "multicamera_control";
@Override
public void onCreate() {
super.onCreate();
// 注册到 ServiceManager
ServiceManager.addService(SERVICE_NAME, mBinder);
}
// ... Stub 实现
}
// 客户端查找示例
ICameraControl service = ICameraControl.Stub.asInterface(
ServiceManager.getService("multicamera_control"));