适用于 HAL 的 AIDL

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 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

您应将扩展接口放入 vendorhardware 下的其他 hardware/interfaces 子目录中。

扩展接口

Android 的每个版本都提供了一套官方 AOSP 接口。如果 Android 合作伙伴需要向这些接口添加功能,应注意不能直接更改这些接口,否则会造成自己的 Android 运行时与 AOSP Android 运行时不兼容。对于 GMS 设备,也应避免直接更改这些接口,以确保 GSI 映像能够正常运行。

扩展可以通过两种不同的方式进行注册:

  • 在运行时注册(请参阅附加的扩展)。
  • 独立注册(在全局注册和在 VINTF 内注册)。

无论以哪种方式注册扩展,当特定于供应商(即不属于上游 AOSP 的组成部分)的组件使用接口时,都不可能出现合并冲突。但是,如果在下游对上游 AOSP 组件进行修改,就会导致合并冲突。在这种情况下,建议采取以下策略:

  • 在下一版本中,在上游对 AOSP 组件执行接口添加操作
  • 在下一版本中,在上游执行更加灵活且不会造成合并冲突的接口添加操作

扩展 Parcelable:ParcelableHolder

ParcelableHolder 是可以包含另一个 ParcelableParcelableParcelableHolder 的主要用例是为 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 运行时环境进行构建

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_platform 库。

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 文件:

  1. 构建该工具(位于 system/tools/hidl/hidl2aidl 中)。

    使用最新的来源构建该工具可以提供最丰富而全面的体验。您可以将先前版本的较旧分支中的接口转换为最新版本。

    m hidl2aidl
    
  2. 要执行该工具,您需要依次指定输出目录和要转换的软件包。

    (可选)使用 -l 参数将新许可文件的内容添加到所有已生成文件的顶部。请务必使用正确的许可和日期。

    hidl2aidl -o <output directory> -l <file with license> <package>
    

    例如:

    hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
    
  3. 仔细阅读生成的文件,并修复任何转换方面的问题。

    • conversion.log 中包含了所有需要优先修复的问题。
    • 生成的 .aidl 文件中可能包含需要您采取措施的警告和建议。这些信息作为以 // 开头的注释提供。
    • 请把握这个机会,对软件包进行清理并加以改进。
    • 检查 @JavaDerive 注解,了解可能需要的功能,例如 toStringequals
  4. 仅构建您需要的目标。

    • 停用未使用的后端。首选 NDK 后端,而非 CPP 后端,详见选择运行时
    • 移除转换库或其生成的任何未使用的代码。
  5. 请参阅 AIDL 与 HIDL 之间的主要差异

    • 使用 AIDL 的内置 Status 和异常通常可以优化接口,并避免需要使用其他接口特定的状态类型。
    • 默认情况下,方法中的 AIDL 接口参数不会像在 HIDL 中那样为 @nullable

适用于 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_clienthal_foo_server 两种属性。对于给定网域,hal_client_domainhal_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_servicehal_foohal_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 函数才能启用实时优先级继承。