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 实例
    }
}
关键点: 服务端必须将 Stub 实例通过 onBind() 方法返回给 Binder 驱动。驱动会将该 Binder 引用传递给客户端,客户端通过该引用发起跨进程调用。

4.2 bindService 流程:客户端如何找到服务端

客户端通过 bindService() 发起绑定请求,整个流程涉及 Context、AMS(ActivityManagerService)、Binder 驱动以及服务端进程的协同工作。以下流程图展示了完整的绑定链路:

bindService 完整流程时序图 Client App AMS Binder Driver Service 进程 ① bindService ② 创建 Service ③ onBind() ④ 返回 Binder 引用 Client App Binder Driver Service 进程 ⑤ 调用接口方法 ⑥ 执行方法 ⑦ 返回结果 关键说明: • 步骤①-④:bindService 建立连接,AMS 负责协调进程创建与 Binder 引用传递。 • 步骤⑤-⑦:客户端通过 Proxy 发起跨进程调用,Binder 驱动负责数据序列化与线程切换。 • 多 Camera 场景下,每个 Camera 设备可能对应独立的 Service 或共享同一个 Service 实例。

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 内部执行以下步骤:

  1. 序列化参数: 将方法参数写入 Parcel 对象。
  2. 发起 transact: 调用 mRemote.transact(Stub.TRANSACTION_xxx, data, reply, 0),将数据包发送给 Binder 驱动。
  3. 驱动转发: Binder 驱动根据目标 Binder 引用,将数据包路由到服务端进程的线程池。
  4. Stub.onTransact: 服务端线程池中的线程调用 Stub.onTransact(),根据 code 字段识别方法,反序列化参数。
  5. 调用实现: 调用服务端实际的方法实现,获取返回结果。
  6. 返回结果: 将返回结果序列化写入 reply Parcel,通过 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"));
最佳实践: 在多 Camera 场景下,推荐采用“单 Service 多 Camera”策略,配合内部线程池隔离(如每个 Camera 分配一个独立的工作线程),在降低连接复杂度的同时保证性能隔离。同时,务必在 Stub 方法实现中做好异常捕获与资源清理,防止单个 Camera 异常导致整个 Service 崩溃。