Android 11 引入了在 Android 中对 HAL 使用 AIDL 的能力,从而可以在没有 HIDL 的情况下实现 Android 的某些部分。尽可能将 HAL 转换为专门使用 AIDL(当上游 HAL 使用 HIDL 时,必须使用 HIDL)。
在框架组件(例如 system.img
中的组件)和硬件组件(例如 vendor.img
中的组件)之间进行通信的 HAL 必须使用稳定的 AIDL。但是,为了在分区内进行通信,例如从一个 HAL 到另一个 HAL,对要使用的 IPC 机制没有限制。
动机
AIDL 的出现时间比 HIDL 早,并且在许多其他地方使用,例如 Android 框架组件之间或应用中。现在 AIDL 具有稳定性支持,可以使用单个 IPC 运行时实现整个堆栈。AIDL 还具有比 HIDL 更好的版本控制系统。以下是 AIDL 的一些优点
- 使用单一 IPC 语言意味着只需要学习、调试、优化和保护一件事。
- AIDL 支持接口所有者进行就地版本控制
- 所有者可以将方法添加到接口末尾,或将字段添加到 parcelable。这意味着多年来版本控制代码更容易,并且每年的成本也更小(可以就地修改类型,并且每个接口版本都不需要额外的库)。
- 扩展接口可以在运行时而不是在类型系统中附加,因此无需将下游扩展重新基于较新版本的接口。
- 当现有 AIDL 接口的所有者选择稳定该接口时,可以直接使用该接口。以前,必须在 HIDL 中创建接口的完整副本。
针对 AIDL 运行时构建
AIDL 有三个不同的后端:Java、NDK 和 CPP。要使用稳定的 AIDL,请始终使用 system/lib*/libbinder.so
处的系统副本 libbinder
,并在 /dev/binder
上进行通信。对于 vendor
镜像上的代码,这意味着不能使用 libbinder
(来自 VNDK):此库具有不稳定的 C++ API 和不稳定的内部结构。相反,原生供应商代码必须使用 AIDL 的 NDK 后端,链接到 libbinder_ndk
(由系统 libbinder.so
支持),并链接到由 aidl_interface
条目创建的 NDK 库。有关确切的模块名称,请参阅模块命名规则。
编写 AIDL HAL 接口
为了使 AIDL 接口在系统和供应商之间使用,该接口需要进行两项更改
- 每个类型定义都必须使用
@VintfStability
注释。 aidl_interface
声明需要包含stability: "vintf",
。
只有接口的所有者才能进行这些更改。
当您进行这些更改时,接口必须在 VINTF 清单中才能工作。使用 VTS 测试 vts_treble_vintf_vendor_test
测试此项(以及相关要求,例如验证已发布的接口是否已冻结)。您可以通过在 NDK 后端调用 AIBinder_forceDowngradeToLocalStability
、在 C++ 后端调用 android::Stability::forceDowngradeToLocalStability
或在 Java 后端调用 android.os.Binder#forceDowngradeToSystemStability
,在 binder 对象发送到另一个进程之前,使用没有这些要求的 @VintfStability
接口。
此外,为了最大限度地提高代码可移植性并避免潜在问题(例如不必要的额外库),请禁用 CPP 后端。
请注意,以下代码示例中 backends
的使用是正确的,因为有三个后端(Java、NDK 和 CPP)。代码显示了如何禁用 CPP 后端
aidl_interface: {
...
backends: {
cpp: {
enabled: false,
},
},
}
查找 AIDL HAL 接口
AOSP 稳定 AIDL 接口的 HAL 位于与 HIDL 接口相同的基本目录中的 aidl
文件夹中
hardware/interfaces
适用于通常由硬件提供的接口。frameworks/hardware/interfaces
适用于提供给硬件的高级接口。system/hardware/interfaces
适用于提供给硬件的低级接口。
将扩展接口放入 vendor
或 hardware
中的其他 hardware/interfaces
子目录中。
扩展接口
Android 的每个版本都有一组官方 AOSP 接口。当 Android 合作伙伴想要向这些接口添加功能时,他们不应直接更改这些接口,因为这会使他们的 Android 运行时与 AOSP Android 运行时不兼容。避免更改这些接口,以便 GSI 镜像可以继续工作。
扩展可以通过两种不同的方式注册
- 在运行时;请参阅附加的扩展接口
- 作为独立的扩展,全局注册并在 VINTF 中注册
但是,无论扩展如何注册,当供应商特定(意味着不是上游 AOSP 的一部分)组件使用该接口时,都不会发生合并冲突。但是,当下游修改上游 AOSP 组件时,可能会导致合并冲突,建议采用以下策略
- 将接口添加项向上游添加到下一个版本中的 AOSP。
- 向上游添加允许在下一个版本中进一步提高灵活性的接口添加项(没有合并冲突)。
扩展 parcelable:ParcelableHolder
ParcelableHolder
是 Parcelable
接口的一个实例,可以包含 Parcelable
的另一个实例。
ParcelableHolder
的主要用例是使 Parcelable
可扩展。例如,设备实现者希望能够扩展 AOSP 定义的 Parcelable
、AospDefinedParcelable
以包含其增值功能。
使用 ParcelableHolder
接口使用您的增值功能扩展 Parcelable
。ParcelableHolder
接口包含 Parcelable
的一个实例。如果您尝试直接向 Parcelable
添加字段,则会导致错误
parcelable AospDefinedParcelable {
int a;
String b;
String x; // ERROR: added by a device implementer
int[] y; // added by a device implementer
}
如前面的代码所示,这种做法是错误的,因为当在 Android 的下一个版本中修订 Parcelable
时,设备实现者添加的字段可能会发生冲突。
使用 ParcelableHolder
,parcelable 的所有者可以在 Parcelable
的实例中定义扩展点
parcelable AospDefinedParcelable {
int a;
String b;
ParcelableHolder extension;
}
然后,设备实现者可以为其扩展定义自己的 Parcelable
实例
parcelable OemDefinedParcelable {
String x;
int[] y;
}
新的 Parcelable
实例可以使用 ParcelableHolder
字段附加到原始 Parcelable
// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;
ap.extension.setParcelable(op);
...
OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);
// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();
ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);
...
std::shared_ptr<OemDefinedParcelable> op_ptr;
ap.extension.getParcelable(&op_ptr);
// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);
...
std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);
// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });
ap.extension.set_parcelable(Rc::clone(&op));
...
let op = ap.extension.get_parcelable::<OemDefinedParcelable>();
AIDL HAL 服务器实例名称
按照惯例,AIDL HAL 服务的实例名称格式为 $package.$type/$instance
。例如,振动器 HAL 的实例注册为 android.hardware.vibrator.IVibrator/default
。
编写 AIDL HAL 服务器
@VintfStability
AIDL 服务器必须在 VINTF 清单中声明,例如
<hal format="aidl">
<name>android.hardware.vibrator</name>
<version>1</version>
<fqname>IVibrator/default</fqname>
</hal>
否则,它们应正常注册 AIDL 服务。运行 VTS 测试时,预计所有声明的 AIDL HAL 均可用。
编写 AIDL 客户端
AIDL 客户端必须在兼容性矩阵中声明自身,例如
<hal format="aidl" optional="true">
<name>android.hardware.vibrator</name>
<version>1-2</version>
<interface>
<name>IVibrator</name>
<instance>default</instance>
</interface>
</hal>
将现有 HAL 从 HIDL 转换为 AIDL
使用 hidl2aidl
工具将 HIDL 接口转换为 AIDL。
hidl2aidl
功能
- 基于给定软件包的 HAL (
.hal
) 文件创建 AIDL (.aidl
) 文件。 - 为新创建的 AIDL 软件包创建构建规则,并启用所有后端。
- 在 Java、CPP 和 NDK 后端中创建转换方法,用于从 HIDL 类型转换为 AIDL 类型。
- 为具有所需依赖项的转换库创建构建规则。
- 创建静态断言,以确保 HIDL 和 AIDL 枚举器在 CPP 和 NDK 后端中具有相同的值。
按照以下步骤将 HAL 文件包转换为 AIDL 文件
构建位于
system/tools/hidl/hidl2aidl
中的工具。从最新源代码构建此工具可提供最完整的体验。您可以使用最新版本转换以前版本较旧分支上的接口
m hidl2aidl
使用输出目录执行该工具,后跟要转换的软件包。
或者,使用
-l
参数将新许可证文件的内容添加到所有生成文件的顶部。请务必使用正确的许可证和日期hidl2aidl -o <output directory> -l <file with license> <package>
例如
hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
通读生成的文件,并修复转换中的任何问题
conversion.log
包含首先要修复的任何未处理的问题。- 生成的 AIDL 文件可能包含需要操作的警告和建议。这些注释以
//
开头。 - 清理并改进软件包。
- 查看
@JavaDerive
注释,了解可能需要的功能,例如toString
或equals
。
仅构建您需要的目标
- 禁用不会使用的后端。首选 NDK 后端而不是 CPP 后端;请参阅针对 AIDL 运行时构建。
- 删除转换库或任何不会使用的生成的代码。
-
- 使用 AIDL 的内置
Status
和异常通常可以改进接口,并消除对其他特定于接口的状态类型的需求。 - 默认情况下,AIDL 方法中的接口参数不是
@nullable
,就像 HIDL 中一样。
- 使用 AIDL 的内置
AIDL HAL 的 SEPolicy
对供应商代码可见的 AIDL 服务类型必须具有 hal_service_type
属性。否则,sepolicy 配置与任何其他 AIDL 服务相同(尽管 HAL 有特殊属性)。以下是 HAL 服务上下文的示例定义
type hal_foo_service, service_manager_type, hal_service_type;
对于平台定义的大多数服务,已经添加了具有正确类型的服务上下文(例如,android.hardware.foo.IFoo/default
已标记为 hal_foo_service
)。但是,如果框架客户端支持多个实例名称,则必须在特定于设备的 service_contexts
文件中添加其他实例名称
android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0
当您创建新的 HAL 类型时,必须添加 HAL 属性。特定的 HAL 属性可能与多个服务类型相关联(每个服务类型都可以有多个实例,如前所述)。对于 HAL foo
,有 hal_attribute(foo)
。此宏定义属性 hal_foo_client
和 hal_foo_server
。对于给定的域,hal_client_domain
和 hal_server_domain
宏将域与给定的 HAL 属性相关联。例如,系统服务器作为此 HAL 的客户端对应于策略 hal_client_domain(system_server, hal_foo)
。HAL 服务器类似地包括 hal_server_domain(my_hal_domain, hal_foo)
。
通常,对于给定的 HAL 属性,还应为参考或示例 HAL 创建类似 hal_foo_default
的域。但是,某些设备将这些域用于其自身的服务器。仅当有多个服务器提供相同的接口并且其实现中需要不同的权限集时,区分多个服务器的域才重要。在所有这些宏中,hal_foo
不是 sepolicy 对象。相反,这些宏使用此令牌来引用与客户端服务器对关联的属性组。
但是,到目前为止,hal_foo_service
和 hal_foo
(来自 hal_attribute(foo)
的属性对)尚未关联。HAL 属性使用 hal_attribute_service
宏(HIDL HAL 使用 hal_attribute_hwservice
宏)与 AIDL HAL 服务相关联,例如,hal_attribute_service(hal_foo, hal_foo_service)
。这意味着 hal_foo_client
进程可以获得 HAL,并且 hal_foo_server
进程可以注册 HAL。这些注册规则的强制执行由上下文管理器 (servicemanager
) 完成。
服务名称可能并非始终与 HAL 属性相对应,例如,hal_attribute_service(hal_foo, hal_foo2_service)
。通常,因为这暗示服务始终一起使用,您可以删除 hal_foo2_service
,并对所有服务上下文使用 hal_foo_service
。当 HAL 设置多个 hal_attribute_service
实例时,这是因为原始 HAL 属性名称不够通用,无法更改。
将所有这些放在一起,一个示例 HAL 如下所示
public/attributes:
// define hal_foo, hal_foo_client, hal_foo_server
hal_attribute(foo)
public/service.te
// define hal_foo_service
type hal_foo_service, hal_service_type, protected_service, service_manager_type
public/hal_foo.te:
// allow binder connection from client to server
binder_call(hal_foo_client, hal_foo_server)
// allow client to find the service, allow server to register the service
hal_attribute_service(hal_foo, hal_foo_service)
// allow binder communication from server to service_manager
binder_use(hal_foo_server)
private/service_contexts:
// bind an AIDL service name to the selinux type
android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0
private/<some_domain>.te:
// let this domain use the hal service
binder_use(some_domain)
hal_client_domain(some_domain, hal_foo)
vendor/<some_hal_server_domain>.te
// let this domain serve the hal service
hal_server_domain(some_hal_server_domain, hal_foo)
附加的扩展接口
扩展可以附加到任何 binder 接口,无论它是直接向服务管理器注册的顶级接口,还是子接口。获取扩展时,您必须确认扩展的类型是否符合预期。您只能从服务 binder 的进程设置扩展。
当扩展修改现有 HAL 的功能时,请始终使用附加的扩展。当需要全新的功能时,此机制不是必需的,您可以直接向服务管理器注册扩展接口。附加的扩展接口在附加到子接口时最有意义,因为这些层次结构可能很深或多实例。使用全局扩展来镜像另一个服务的 binder 接口层次结构需要大量的簿记工作,才能提供与直接附加的扩展等效的功能。
要在 binder 上设置扩展,请使用以下 API
- NDK 后端:
AIBinder_setExtension
- Java 后端:
android.os.Binder.setExtension
- CPP 后端:
android::Binder::setExtension
- Rust 后端:
binder::Binder::set_extension
要在 binder 上获取扩展,请使用以下 API
- NDK 后端:
AIBinder_getExtension
- Java 后端:
android.os.IBinder.getExtension
- CPP 后端:
android::IBinder::getExtension
- Rust 后端:
binder::Binder::get_extension
您可以在相应后端的 getExtension
函数的文档中找到有关这些 API 的更多信息。有关如何使用扩展的示例,请参阅 hardware/interfaces/tests/extension/vibrator
。
AIDL 和 HIDL 的主要差异
使用 AIDL HAL 或使用 AIDL HAL 接口时,请注意与编写 HIDL HAL 相比的差异。
- AIDL 语言的语法更接近 Java。HIDL 语法类似于 C++。
- 所有 AIDL 接口都具有内置错误状态。无需创建自定义状态类型,只需在接口文件中创建常量状态 int,并在 CPP 和 NDK 后端中使用
EX_SERVICE_SPECIFIC
,在 Java 后端中使用ServiceSpecificException
。请参阅错误处理。 - 当发送 binder 对象时,AIDL 不会自动启动线程池。您必须手动启动它们(请参阅线程管理)。
- AIDL 不会在未检查的传输错误时中止(HIDL
Return
在未检查的错误时中止)。 - 每个 AIDL 文件只能声明一种类型。
- 除了输出参数外,AIDL 参数还可以指定为
in
、out
或inout
(没有同步回调)。 - AIDL 使用
fd
作为原始类型,而不是handle
。 - HIDL 对不兼容的更改使用主版本,对兼容的更改使用次版本。在 AIDL 中,向后兼容的更改就地完成。AIDL 没有主版本的显式概念;相反,这已纳入软件包名称中。例如,AIDL 可能会使用软件包名称
bluetooth2
。 - 默认情况下,AIDL 不会继承实时优先级。必须对每个 binder 使用
setInheritRt
函数才能启用实时优先级继承。
HAL 的供应商测试套件 (VTS) 测试
Android 依赖于供应商测试套件 (VTS)来验证预期的 HAL 实现。VTS 帮助确保 Android 可以向后兼容旧的供应商实现。VTS 未能通过的实现存在已知的兼容性问题,这些问题可能会阻止它们与未来版本的操作系统一起工作。
HAL 的 VTS 主要有两个部分。
1. 验证设备上的 HAL 是否是 Android 已知和预期的
这组测试可以在test/vts-testcase/hal/treble/vintf
中找到。这些测试负责验证
- 在 VINTF 清单中声明的每个
@VintfStability
接口都冻结在已知的已发布版本。这确保接口的双方都同意该接口版本的确切定义。这对于基本操作是必要的。 - 在 VINTF 清单中声明的所有 HAL 在该设备上都可用。任何具有足够权限使用声明的 HAL 服务的客户端都必须能够随时获取和使用这些服务。
- 在 VINTF 清单中声明的所有 HAL 都在提供它们在清单中声明的接口版本。
- 设备上没有提供已弃用的 HAL。如FCM 生命周期中所述,Android 放弃了对较低版本 HAL 接口的支持。
- 必需的 HAL 存在于设备上。某些 HAL 是 Android 正常工作所必需的。
2. 验证每个 HAL 的预期行为
每个 HAL 接口都有自己的 VTS 测试,以验证其客户端的预期行为。测试用例针对声明的 HAL 接口的每个实例运行,并根据已实现的接口版本强制执行特定行为。
这些测试尝试涵盖 Android 框架依赖或将来可能依赖的 HAL 实现的各个方面。
这些测试包括验证对功能、错误处理以及客户端可能从服务中期望的任何其他行为的支持。
HAL 开发的 VTS 里程碑
在创建或修改 Android 的 HAL 接口时,VTS 测试预计会保持最新。
VTS 测试必须在为 Android 供应商 API 版本冻结之前完成并准备好验证供应商实现。它们必须在接口冻结之前准备就绪,以便开发人员可以创建他们的实现、验证它们并向 HAL 接口开发人员提供反馈。
Cuttlefish 上的 VTS
当硬件不可用时,Android 使用 Cuttlefish 作为 HAL 接口的开发工具。这允许对 Android 进行可扩展的 VTS 和集成测试。
hal_implementation_test
测试 Cuttlefish 是否具有最新 HAL 接口版本的实现,以确保 Android 已准备好处理新接口,并且 VTS 测试已准备好在新硬件和设备可用时立即测试新的供应商实现。