Android 11 中引入了在 Android 中使用 AIDL 实现 HAL 的功能。这样就能在不使用 HIDL 的情况下实现 Android 的部分代码。在可能的情况下,应将 HAL 转换为仅使用 AIDL(当上行 HAL 使用 HIDL 时,必须使用 HIDL)。
如果 HAL 使用 AIDL 在框架组件(例如 system.img
中的组件)和硬件组件(例如 vendor.img
中的组件)之间进行通信,必须使用稳定的 AIDL。不过,如需在分区内进行通信(例如从一个 HAL 到另一个 HAL),则对需要使用的 IPC 机制没有任何限制。
设计初衷
AIDL 出现在 HIDL 之前,而且在 Android 框架组件之间或应用内等其他很多地方都有使用。现在,由于 AIDL 具备了稳定性支持,所以能够仅使用一个 IPC 运行时环境来实现整个堆栈。此外,AIDL 的版本控制系统也优于 HIDL。
- 因为仅使用一种 IPC 语言,所以意味着只需了解、调试、优化和保护一个运行时环境。
- AIDL 可为接口所有者提供内建的版本控制机制。
- 所有者可以将方法添加到 Parcelable 的接口或字段的末尾。这意味着可以在持续多年的开发过程中简化对代码的版本控制,并逐年降低产生的开销(可以就地修改类型,而且更新接口版本不需要新增额外的库)。
- 扩展接口可以在运行时附加,而不是在类型系统中附加,因此无需将下游扩展 rebase 到新版接口上。
- 如果现有 AIDL 接口的所有者选择使其稳定化,此类接口可以直接沿用。而在以前,这种情况下必须用 HIDL 创建接口的完整副本。
针对 AIDL 运行时环境进行构建
AIDL 具有三个不同的后端:Java、NDK、CPP。如需使用稳定的 AIDL,您必须始终使用位于 system/lib*/libbinder.so
的 libbinder 的系统副本,并能够访问 /dev/binder
。对于供应商映像中的代码,这意味着 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
检查接口是否正常工作(以及是否满足相关要求,例如验证已发布的接口是否被冻结)。如果在将 binder 对象发送到其他进程之前针对该对象进行以下调用,那么无需满足上述要求也可使用 @VintfStability
接口:在 NDK 后端调用 AIBinder_forceDowngradeToLocalStability
;在 C++ 后端调用 android::Stability::forceDowngradeToLocalStability
;或者在 Java 后端调用 android.os.Binder#forceDowngradeToSystemStability
。在 Java 中,您无法为了在供应商环境中稳定运行而降级服务,因为所有应用都是在系统这个上下文环境中运行的。
此外,为了最大限度提高代码可移植性并避免出现潜在问题(例如不必要的额外库),建议停用 CPP 后端。
需要注意的是,像下面的代码示例这样使用 backends
是正确的,因为后端有三种(Java、NDK 和 CPP)。下面的代码展示了如何专门选择 CPP 后端,并将其停用。
aidl_interface: {
...
backends: {
cpp: {
enabled: false,
},
},
}
查找 AIDL HAL 接口
在 AOSP 中,适用于 HAL 的稳定 AIDL 接口所在的基础目录与 HIDL 接口所在的基础目录相同,位于 aidl
文件夹中。
- hardware/interfaces:适用于通常由硬件提供的接口
- frameworks/hardware/interfaces:适用于向硬件提供的高级接口
- system/hardware/interfaces:适用于向硬件提供的低级接口
您应将扩展接口放入 vendor
或 hardware
下的其他 hardware/interfaces
子目录中。
扩展接口
Android 的每个版本都提供了一套官方 AOSP 接口。如果 Android 合作伙伴需要向这些接口添加功能,应注意不能直接更改这些接口,否则会造成自己的 Android 运行时与 AOSP Android 运行时不兼容。对于 GMS 设备,也应避免直接更改这些接口,以确保 GSI 映像能够正常运行。
扩展可以通过两种不同的方式进行注册:
- 在运行时注册(请参阅附加的扩展)。
- 独立注册(在全局注册和在 VINTF 内注册)。
无论以哪种方式注册扩展,当特定于供应商(即不属于上游 AOSP 的组成部分)的组件使用接口时,都不可能出现合并冲突。但是,如果在下游对上游 AOSP 组件进行修改,就会导致合并冲突。在这种情况下,建议采取以下策略:
- 在下一版本中,在上游对 AOSP 组件执行接口添加操作
- 在下一版本中,在上游执行更加灵活且不会造成合并冲突的接口添加操作
扩展 Parcelable:ParcelableHolder
ParcelableHolder
是可以包含另一个 Parcelable
的 Parcelable
。ParcelableHolder
的主要用例是为 Parcelable
提供可扩展性。例如,设备实现者希望创建一个映像,以便扩展 AospDefinedParcelable
这个由 AOSP 定义的 Parcelable
,使其包含所需的增值功能。
过去没有 ParcelableHolder
时,设备实现者无法修改 AOSP 定义的稳定 AIDL 接口,这是因为添加字段会导致错误:
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;
}
最后,只需使用 ParcelableHolder
字段,即可将新的 Parcelable
附加到原始 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
文件为基础,为给定软件包创建.aidl
文件 - 在启用所有后端的情况下,为新创建的 AIDL 软件包创建构建规则
- 在 Java、CPP 和 NDK 后端中创建转换方法,将 HIDL 类型的接口转换为 AIDL 类型
- 创建构建规则,以便按照所需的依赖关系转换库的格式
- 创建静态断言,以确保在 CPP 和 NDK 后端中,HIDL 和 AIDL 枚举器具有相同的值
按照下面的步骤操作,将软件包中的 .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 与 HIDL 之间的主要差异。
- 使用 AIDL 的内置
Status
和异常通常可以优化接口,并避免需要使用其他接口特定的状态类型。 - 默认情况下,方法中的 AIDL 接口参数不会像在 HIDL 中那样为
@nullable
。
- 使用 AIDL 的内置
适用于 AIDL HAL 的 SEPolicy
对供应商代码可见的 AIDL 服务类型必须具有 hal_service_type
属性。否则,虽然 HAL 具有特殊属性,也将使用与所有其他 AIDL 服务相同的 sepolicy 配置。以下是定义 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
宏与 AIDL HAL 服务进行关联(HIDL HAL 则需要使用 hal_attribute_hwservice
宏)。例如 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
如需详细了解这些 API,请参阅相应后端中的 getExtension
函数的文档。有关如何使用扩展的示例,请访问 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 参数除了可以被指定为 output 参数,还可以被指定为 in/out/inout 参数(没有“同步回调”)。
- AIDL 将 fd 用作基元类型,而不是句柄。
- 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。Android 会按照 FCM 生命周期中所述,停止支持较低版本的 HAL 接口。
- 设备上存在必需的 HAL。某些 HAL 是 Android 正常运行所必需的。
2. 验证每个 HAL 的预期行为
每个 HAL 接口都有自己的 VTS 测试,用于验证其客户端的预期行为。测试用例会针对已声明的 HAL 接口的每个实例运行,并根据实现的接口版本强制执行特定行为。
这些测试会尝试涵盖 Android 框架所依赖的或未来可能依赖的 HAL 实现的各个方面。
这些测试包括验证对功能的支持情况、错误处理以及客户端可能期望服务做出的任何其他行为。
HAL 开发工作的 VTS 里程碑
在创建或修改 Android 的 HAL 接口时,VTS 测试应保持最新状态。
VTS 测试必须已完成并准备就绪,以便在针对 Android Vendor API 版本冻结供应商实现之前对这些实现进行验证。这些测试必须在接口被冻结之前准备就绪,以便开发者可以创建其实现、对这些实现进行验证,并向 HAL 接口开发者提供反馈。
Cuttlefish 上的 VTS
当没有可用的硬件时,Android 会使用 Cuttlefish 作为 HAL 接口的开发平台。这样,就可以对 Android 进行可扩展的 VTS 和集成测试。hal_implementation_test
测试 Cuttlefish 是否实现了最新的 HAL 接口版本,以确保 Android 已准备好处理新接口,并且 VTS 测试已准备好在有新硬件和设备可用时立即测试新的供应商实现。