3. AIDL 基础语法:接口定义、数据类型、定向 tag 与 oneway 关键字

在深入多 Camera HAL 场景之前,必须牢固掌握 AIDL 的核心语法。AIDL(Android Interface Definition Language)是 Android 系统中用于跨进程通信(IPC)的接口定义语言。本章将系统讲解 AIDL 的接口定义规则、支持的数据类型、参数定向 tag(in/out/inout)以及 oneway 关键字的语义与使用场景。

3.1 AIDL 接口定义基础

AIDL 接口定义语法与 Java 接口类似,但扩展名为 .aidl。每个 AIDL 文件定义一个接口,接口内可以声明多个方法。

3.1.1 基本语法结构

// ICameraDevice.aidl
package com.example.camera.hal;

interface ICameraDevice {
    // 打开相机设备
    int open(String cameraId);

    // 开始预览
    void startPreview();

    // 停止预览
    void stopPreview();

    // 关闭相机设备
    void close();
}

关键规则:

  • 文件扩展名必须为 .aidl。
  • 包名必须与文件所在目录结构一致。
  • 接口名通常以 I 开头,遵循大驼峰命名法。
  • 方法声明中,参数和返回值类型必须是 AIDL 支持的数据类型。
  • 非基本类型参数必须指定定向 tag(in/out/inout)。
最佳实践: 在 HAL 层多 Camera 场景中,建议每个 Camera 设备对应一个独立的 AIDL 接口,如 ICameraDevice0、ICameraDevice1,便于管理和扩展。

3.1.2 接口继承

AIDL 支持接口继承,子接口可以继承父接口的方法。

// IBaseCamera.aidl
package com.example.camera.hal;

interface IBaseCamera {
    int getCameraId();
    String getCameraInfo();
}

// ICameraDevice.aidl
package com.example.camera.hal;

interface ICameraDevice extends IBaseCamera {
    int open(String cameraId);
    void startPreview();
    void stopPreview();
    void close();
}

3.2 AIDL 支持的数据类型

AIDL 对数据类型有严格限制,只支持以下类型:

类别 支持类型 说明
基本类型 boolean, byte, char, short, int, long, float, double 与 Java 基本类型一致,无需定向 tag
字符串 String 默认 in 方向,可指定 out/inout
字符序列 CharSequence 与 String 类似,用于文本序列
列表 List<T> T 必须是 AIDL 支持的类型,如 List<String>
映射 Map<K, V> K 和 V 必须是 AIDL 支持的类型
Parcelable 自定义 Parcelable 类 需在 AIDL 文件中声明 parcelable 定向
AIDL 接口 其他 AIDL 接口类型 用于回调或服务引用
注意: AIDL 不支持 Java 的 Date、BigDecimal 等复杂类型。如果需要传递复杂对象,必须实现 Parcelable 接口并在 AIDL 中声明。

3.2.1 Parcelable 自定义类型

在 HAL 多 Camera 场景中,经常需要传递自定义数据结构,如相机配置参数、帧信息等。此时需要定义 Parcelable 类。

// CameraConfig.java
package com.example.camera.hal;

import android.os.Parcel;
import android.os.Parcelable;

public class CameraConfig implements Parcelable {
    public int width;
    public int height;
    public int format;
    public int fps;

    public CameraConfig(int width, int height, int format, int fps) {
        this.width = width;
        this.height = height;
        this.format = format;
        this.fps = fps;
    }

    protected CameraConfig(Parcel in) {
        width = in.readInt();
        height = in.readInt();
        format = in.readInt();
        fps = in.readInt();
    }

    public static final Creator<CameraConfig> CREATOR = new Creator<CameraConfig>() {
        @Override
        public CameraConfig createFromParcel(Parcel in) {
            return new CameraConfig(in);
        }

        @Override
        public CameraConfig[] newArray(int size) {
            return new CameraConfig[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(width);
        dest.writeInt(height);
        dest.writeInt(format);
        dest.writeInt(fps);
    }
}

在 AIDL 文件中声明 Parcelable 类型:

// ICameraDevice.aidl
package com.example.camera.hal;

parcelable CameraConfig;

interface ICameraDevice {
    int configure(CameraConfig config);
}

3.3 定向 tag:in / out / inout

定向 tag 是 AIDL 中非常重要的概念,用于指定参数在 IPC 过程中的数据传输方向。它直接影响跨进程通信的性能和语义。

3.3.1 三种定向 tag 对比

定向 tag 数据流向 序列化行为 性能开销 典型使用场景
in 客户端 → 服务端 客户端序列化后发送,服务端反序列化读取 低(单向序列化) 传递配置参数、命令等只读数据
out 服务端 → 客户端 服务端序列化后返回,客户端反序列化读取 低(单向序列化) 获取状态、结果等输出数据
inout 双向 客户端序列化发送,服务端反序列化修改后,再序列化返回 高(双向序列化) 需要修改并返回输入参数的场景

3.3.2 语法示例

// ICameraService.aidl
package com.example.camera.hal;

import com.example.camera.hal.CameraConfig;
import com.example.camera.hal.CameraFrame;

interface ICameraService {
    // in:客户端传入配置,服务端只读
    int configure(in CameraConfig config);

    // out:服务端填充帧数据,客户端读取
    int captureFrame(out CameraFrame frame);

    // inout:客户端传入参数,服务端修改后返回
    int adjustParameters(inout CameraConfig config);
}
性能建议: 在多 Camera 高帧率场景中,尽量使用 in 或 out 而非 inout,因为双向序列化会增加 IPC 延迟和 CPU 开销。例如,传递帧数据时使用 out 方向,传递配置时使用 in 方向。

3.3.3 定向 tag 选择决策流程

定向 tag 选择决策流程 开始选择定向 tag 是否需要修改参数内容? 否 是 数据流向是? 客户端→服务端 服务端→客户端 使用 in 使用 out 使用 inout 注意:inout 会触发 双向序列化,性能开销大 根据场景选择最优定向 tag

3.4 oneway 关键字

oneway 关键字用于修饰 AIDL 接口方法,表示该方法调用是异步的,客户端调用后立即返回,不等待服务端处理完成。

3.4.1 oneway 语义

特性 普通方法 oneway 方法
调用行为 同步阻塞,等待服务端返回 异步非阻塞,立即返回
返回值 可以有返回值 必须为 void
异常处理 可以抛出异常 不能抛出异常
out/inout 参数 支持 不支持(无返回值)
典型场景 查询状态、获取数据 通知、事件回调、日志

3.4.2 语法示例

// ICameraCallback.aidl
package com.example.camera.hal;

interface ICameraCallback {
    // 普通方法:同步查询
    int getCurrentState();

    // oneway 方法:异步通知
    oneway void onFrameAvailable(int frameId);
    oneway void onError(int errorCode, String errorMsg);
    oneway void onCameraDisconnected();
}

3.4.3 多 Camera 场景中的 oneway 应用

在 HAL 多 Camera 场景中,oneway 关键字常用于以下场景:

  • 帧到达通知: 当某个 Camera 设备产生新帧时,通过 oneway 回调通知上层,避免阻塞帧处理流水线。
  • 错误事件上报: Camera 设备出现异常时,异步上报错误码,不影响其他 Camera 的正常工作。
  • 状态变更通知: 如 Camera 设备热插拔、配置变更等事件,使用 oneway 实现广播通知。
性能优势: 在多 Camera 高并发场景中,使用 oneway 方法可以显著降低 IPC 延迟。例如,3 个 Camera 同时上报帧到达事件时,oneway 调用可以避免线程阻塞和死锁风险。

3.4.4 oneway 与定向 tag 的配合

由于 oneway 方法没有返回值,因此不能使用 out 和 inout 定向 tag。所有参数必须为 in 方向(基本类型默认 in)。

// 正确:oneway 方法只使用 in 参数
oneway void notifyEvent(in String eventName, in int eventCode);

// 错误:oneway 方法不能使用 out 参数
// oneway void getResult(out int result);  // 编译错误

// 错误:oneway 方法不能有返回值
// int onewayMethod();  // 编译错误

3.5 综合示例:多 Camera HAL 接口定义

以下是一个综合示例,展示如何将本章知识点应用于多 Camera HAL 场景:

// IMultiCameraHal.aidl
package com.example.camera.hal;

import com.example.camera.hal.CameraConfig;
import com.example.camera.hal.CameraFrame;
import com.example.camera.hal.ICameraCallback;

interface IMultiCameraHal {
    // 同步方法:查询 Camera 数量
    int getCameraCount();

    // 同步方法:打开指定 Camera(in 参数)
    int openCamera(in String cameraId, in CameraConfig config);

    // 同步方法:关闭指定 Camera
    void closeCamera(in String cameraId);

    // 同步方法:捕获帧(out 参数)
    int captureFrame(in String cameraId, out CameraFrame frame);

    // 同步方法:调整参数(inout 参数)
    int adjustParameters(in String cameraId, inout CameraConfig config);

    // oneway 方法:注册回调
    oneway void registerCallback(in ICameraCallback callback);

    // oneway 方法:注销回调
    oneway void unregisterCallback(in ICameraCallback callback);
}
设计建议: 在实际 HAL 开发中,建议将同步方法(如打开、关闭、配置)和异步方法(如回调通知)分离到不同的 AIDL 接口中,保持接口职责单一,便于维护和测试。

3.6 本章小结

  • 接口定义: AIDL 文件使用 .aidl 扩展名,支持接口继承,包名必须与目录结构一致。
  • 数据类型: 支持基本类型、String、CharSequence、List、Map、Parcelable 和 AIDL 接口类型。
  • 定向 tag: in(客户端→服务端)、out(服务端→客户端)、inout(双向),选择时需权衡性能与语义需求。
  • oneway 关键字: 实现异步非阻塞调用,适用于事件通知、回调等场景,不能有返回值或 out/inout 参数。