4. AIDL接口定义实战(Treble版):定义.aidl文件、parcelable、oneway方法、接口版本管理

在Treble架构下,AIDL(Android Interface Definition Language)是HAL与Framework之间通信的核心契约。与传统的HIDL不同,Treble版的AIDL接口定义更加简洁、类型安全,并且原生支持接口版本管理。本章将带领你从零开始,完成一个完整的AIDL HAL接口定义,涵盖文件结构、自定义数据类型、单向调用以及版本演进策略。

4.1 AIDL接口文件结构

一个标准的Treble AIDL HAL接口由以下文件组成,它们通常放置在 hardware/interfaces/ 或 vendor/xxx/hardware/interfaces/ 目录下:

文件/目录 用途 示例
IFoo.aidl 定义HAL服务的主接口,声明方法 IFoo.aidl
IFooCallback.aidl 定义回调接口,用于服务端向客户端推送事件 IFooCallback.aidl
types.aidl 声明该包下使用的所有parcelable类型 types.aidl
FooData.java 自定义parcelable数据类的实现 FooData.java
Android.bp 构建配置,定义aidl_interface模块 Android.bp
最佳实践: 每个AIDL接口包应包含一个 types.aidl 文件,用于集中管理该包内所有自定义的parcelable类型,避免在多个接口文件中重复声明。

4.2 定义 .aidl 文件

以下是一个完整的AIDL接口定义示例,它模拟了一个温度传感器HAL服务:

// hardware/interfaces/thermal/aidl/IThermal.aidl
package vendor.thermal;

import vendor.thermal.TemperatureData;

interface IThermal {
    /**
     * 获取所有温度传感器的当前读数。
     * @return TemperatureData[] 温度数据数组
     */
    TemperatureData[] getTemperatures();

    /**
     * 注册一个回调,当温度超过阈值时通知客户端。
     * @param callback 回调接口
     */
    void registerCallback(IThermalCallback callback);

    /**
     * 注销回调。
     * @param callback 之前注册的回调接口
     */
    void unregisterCallback(IThermalCallback callback);
}
// hardware/interfaces/thermal/aidl/IThermalCallback.aidl
package vendor.thermal;

import vendor.thermal.TemperatureData;

oneway interface IThermalCallback {
    /**
     * 当温度超过阈值时触发。
     * @param data 触发事件的温度数据
     */
    void onTemperatureThresholdReached(in TemperatureData data);
}
关键点: 回调接口 IThermalCallback 被声明为 oneway,这意味着客户端调用 onTemperatureThresholdReached 时不会阻塞等待服务端处理完成,适用于事件通知场景。

4.3 定义 Parcelable 自定义数据类型

当需要传递复杂数据结构时,必须使用 parcelable 关键字在AIDL中声明,并提供对应的Java/Kotlin实现类。

4.3.1 声明 types.aidl

// hardware/interfaces/thermal/aidl/types.aidl
package vendor.thermal;

parcelable TemperatureData {
    String sensorName;
    double temperatureCelsius;
    long timestampMillis;
    int sensorType; // 0: CPU, 1: GPU, 2: BATTERY
}

4.3.2 实现 Parcelable 类

// hardware/interfaces/thermal/aidl/TemperatureData.java
package vendor.thermal;

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

public class TemperatureData implements Parcelable {
    public String sensorName;
    public double temperatureCelsius;
    public long timestampMillis;
    public int sensorType;

    // 必须实现 describeContents() 和 writeToParcel()
    @Override
    public int describeContents() { return 0; }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(sensorName);
        dest.writeDouble(temperatureCelsius);
        dest.writeLong(timestampMillis);
        dest.writeInt(sensorType);
    }

    // 必须提供 CREATOR 字段
    public static final Parcelable.Creator<TemperatureData> CREATOR =
            new Parcelable.Creator<TemperatureData>() {
                public TemperatureData createFromParcel(Parcel in) {
                    return new TemperatureData(in);
                }
                public TemperatureData[] newArray(int size) {
                    return new TemperatureData[size];
                }
            };

    private TemperatureData(Parcel in) {
        sensorName = in.readString();
        temperatureCelsius = in.readDouble();
        timestampMillis = in.readLong();
        sensorType = in.readInt();
    }
}
常见错误: 在 types.aidl 中声明的字段名必须与Java实现类中的字段名完全一致,否则AIDL编译器会报错。此外,所有字段必须为基本类型、String、其他parcelable或List/Map。

4.4 oneway 方法详解

oneway 关键字用于修饰接口或接口中的方法,表示该调用是异步且非阻塞的。在Treble HAL中,oneway 主要用于以下场景:

  • 事件回调: 服务端向客户端推送事件,客户端不应阻塞服务端。
  • 日志/统计上报: 客户端上报数据,不关心服务端是否立即处理。
  • 低优先级指令: 如设置一个非关键的配置参数。

以下SVG图展示了 oneway 与普通方法在调用流程上的区别:

oneway 方法 vs 普通方法调用流程对比 客户端调用普通方法 服务端执行方法(阻塞) 返回结果给客户端 客户端线程等待,耗时 = 服务端处理时间 客户端调用 oneway 方法 Binder驱动缓存请求 立即返回客户端(不等待) 服务端异步处理请求 客户端线程不阻塞,耗时 ≈ 0
注意事项:
  • oneway 方法不能有返回值(必须为 void),也不能抛出异常。
  • oneway 方法的参数不能使用 out 或 inout 方向标签,只能使用 in。
  • 在服务端实现中,oneway 方法会在Binder线程池中执行,因此需要注意线程安全。

4.5 接口版本管理

Treble架构要求HAL接口必须支持版本管理,以确保Framework与vendor实现之间的兼容性。AIDL接口的版本管理通过 @VintfStability 注解和 Android.bp 中的版本号实现。

4.5.1 版本管理策略

版本号 含义 兼容性要求
1 初始版本 无
2 向后兼容的扩展 只能添加新方法,不能修改或删除已有方法
3 向后兼容的扩展 同上,可添加新方法或新的parcelable字段
major.minor 重大变更 major版本变化表示不兼容的修改,minor表示兼容扩展

4.5.2 在Android.bp中配置版本

// hardware/interfaces/thermal/aidl/Android.bp
aidl_interface {
    name: "vendor.thermal",
    srcs: [
        "IThermal.aidl",
        "IThermalCallback.aidl",
        "types.aidl",
    ],
    // 定义版本列表
    versions: [
        "1",
        "2",
        "3",
    ],
    // 标记为VINTF稳定性接口
    stability: "vintf",
    // 后端实现
    backend: {
        java: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
        // 对于HAL实现,通常还需要cpp后端
        cpp: {
            enabled: true,
        },
    },
}

4.5.3 版本演进示例

假设我们在版本2中需要添加一个新的方法 getSensorCount(),并在版本3中为 TemperatureData 添加一个 location 字段:

// 版本1: IThermal.aidl (保持不变)
package vendor.thermal;
interface IThermal {
    TemperatureData[] getTemperatures();
    void registerCallback(IThermalCallback callback);
    void unregisterCallback(IThermalCallback callback);
}

// 版本2: IThermal.aidl (新增方法)
package vendor.thermal;
interface IThermal {
    TemperatureData[] getTemperatures();
    void registerCallback(IThermalCallback callback);
    void unregisterCallback(IThermalCallback callback);
    int getSensorCount(); // 新增方法
}

// 版本3: types.aidl (新增字段)
package vendor.thermal;
parcelable TemperatureData {
    String sensorName;
    double temperatureCelsius;
    long timestampMillis;
    int sensorType;
    String location; // 新增字段,默认值为空字符串
}
版本兼容性规则:
  • 只能向接口末尾添加新方法,不能插入或删除已有方法。
  • 只能向parcelable类的末尾添加新字段,且新字段必须有合理的默认值。
  • 不能修改已有方法的签名(参数类型、顺序、返回值)。
  • 不能删除任何方法或字段。

4.6 实战:完整的AIDL HAL接口定义流程

以下SVG图总结了从定义到构建的完整工作流:

AIDL HAL接口定义完整工作流 1. 创建包目录 hardware/interfaces/xxx/aidl/ 2. 定义 types.aidl 声明parcelable类型 3. 实现Parcelable类 Java/Kotlin实现 4. 定义主接口 IFoo.aidl 5. 定义回调接口 IFooCallback.aidl (oneway) 6. 配置版本 Android.bp + versions 7. 编译生成 m vendor.thermal 8. 实现服务端 继承生成的Stub类 9. 注册到VINTF manifest.xml 关键检查点 • types.aidl 中的字段名必须与Java实现类完全一致 • oneway 接口/方法不能有返回值,参数只能使用 in 方向 • 版本升级时只能向后追加,不能修改或删除已有API

4.7 测试验证要点

在完成AIDL接口定义后,测试验证应重点关注以下方面:

  • 编译验证: 确保 m 命令能成功生成Java/NDK/CPP后端代码。
  • Parcelable序列化测试: 编写单元测试验证 writeToParcel 和 createFromParcel 的正确性。
  • oneway行为测试: 验证调用 oneway 方法后客户端线程是否立即返回,服务端是否在后台异步执行。
  • 版本兼容性测试: 使用旧版本客户端连接新版本服务端,验证新增方法是否不会导致崩溃。
  • VINTF兼容性检查: 使用 vintf_fragment_check 工具验证接口声明与实现的一致性。
实战建议: 在开发阶段,建议使用 @VintfStability 注解标记接口,并在 Android.bp 中设置 stability: "vintf",这样可以在编译时自动检查接口的版本兼容性,避免运行时出现Binder transaction错误。