4. AIDL接口定义实战(Treble版)
在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方法不能有返回值(必须为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图总结了从定义到构建的完整工作流:
4.7 测试验证要点
在完成AIDL接口定义后,测试验证应重点关注以下方面:
- 编译验证: 确保
m命令能成功生成Java/NDK/CPP后端代码。 - Parcelable序列化测试: 编写单元测试验证
writeToParcel和createFromParcel的正确性。 - oneway行为测试: 验证调用
oneway方法后客户端线程是否立即返回,服务端是否在后台异步执行。 - 版本兼容性测试: 使用旧版本客户端连接新版本服务端,验证新增方法是否不会导致崩溃。
- VINTF兼容性检查: 使用
vintf_fragment_check工具验证接口声明与实现的一致性。
实战建议: 在开发阶段,建议使用
@VintfStability 注解标记接口,并在 Android.bp 中设置 stability: "vintf",这样可以在编译时自动检查接口的版本兼容性,避免运行时出现Binder transaction错误。