本部分介绍了如何注册和发现服务,以及如何通过调用 .hal
文件内的接口中定义的方法将数据发送到服务。
注册服务
HIDL 接口服务器(实现接口的对象)可注册为已命名的服务。注册的名称不需要与接口或软件包名称相关。如果没有指定名称,则使用名称“默认”;这应该用于不需要注册同一接口的两个实现的 HAL。例如,在每个接口中定义的服务注册的 C++ 调用是:
status_t status = myFoo->registerAsService(); status_t anotherStatus = anotherFoo->registerAsService("another_foo_service"); // if needed
HIDL 接口的版本包含在接口本身中。版本自动与服务注册关联,并可通过每个 HIDL 接口上的方法调用 (android::hardware::IInterface::getInterfaceVersion()
) 进行检索。服务器对象不需要注册,并可通过 HIDL 方法参数传递到其他进程,相应的接收进程会向服务器发送 HIDL 方法调用。
发现服务
客户端代码按名称和版本请求给定的接口,并对所需的 HAL 类调用 getService
:
// C++ sp<V1_1::IFooService> service = V1_1::IFooService::getService(); sp<V1_1::IFooService> alternateService = V1_1::IFooService::getService("another_foo_service"); // Java V1_1.IFooService service = V1_1.IFooService.getService(true /* retry */); V1_1.IFooService alternateService = V1_1.IFooService.getService("another", true /* retry */);
每个版本的 HIDL 接口都会被视为单独的接口。因此,IFooService
版本 1.1 和 IFooService
版本 2.2 都可以注册为“foo_service”,在任一接口上调用 getService("foo_service")
都会获得相应接口的已注册服务。因此,在大多数情况下,注册或发现服务均无需提供名称参数(也就是说名称为“默认”)。
供应商接口对象还会影响所返回接口的传输方法。对于软件包 android.hardware.foo@1.0
中的接口 IFoo
,IFoo::getService
返回的接口始终使用设备清单中针对 android.hardware.foo
声明的传输方法(如果相应条目存在的话);如果该传输方法不存在,则返回 nullptr。
在某些情况下,即使没有获得相关服务,也可能需要立即继续。例如,当客户端希望自行管理服务通知或者在需要获得所有 hwservice 并检索它们的诊断程序(如 atrace
)中时,可能会发生这种情况。在这种情况下,可以使用其他 API,如 C++ 中的 tryGetService
或 Java 中的 getService("instance-name", false)
。Java 中提供的旧版 API getService
也必须与服务通知一起使用。使用此 API 不会避免以下竞态条件:当客户端使用某个非重试 API 请求服务器后,该服务器对自身进行了注册。
服务终止通知
想要在服务终止时收到通知的客户端会接收到框架传送的终止通知。如需接收通知,客户端必须:
- 创建 HIDL 类/接口
hidl_death_recipient
的子类(在 C++ 代码中,而不是在 HIDL 中)。 - 替换其
serviceDied()
方法。 - 实例化
hidl_death_recipient
子类的对象。 - 在要监控的服务上调用
linkToDeath()
方法,并传入IDeathRecipient
的接口对象。请注意,此方法并不具备在其上调用它的终止接收方或代理的所有权。
伪代码示例(C++ 和 Java 类似):
class IMyDeathReceiver : hidl_death_recipient { virtual void serviceDied(uint64_t cookie, wp<IBase>& service) override { log("RIP service %d!", cookie); // Cookie should be 42 } }; .... IMyDeathReceiver deathReceiver = new IMyDeathReceiver(); m_importantService->linkToDeath(deathReceiver, 42);
同一终止接收方可能已在多个不同的服务上注册。
数据传输
可通过调用 .hal
文件内的接口中定义的方法将数据发送到服务。具体方法有两类:
- 阻塞方法会等到服务器产生结果。
- 单向方法仅朝一个方向发送数据且不阻塞。如果 RPC 调用中正在传输的数据量超过实现限制,调用可能会阻塞或返回错误指示(具体行为尚不确定)。
不返回值但未声明为 oneway
的方法仍会阻塞。
在 HIDL 接口中声明的所有方法都在一个方向上调用,要么从 HAL 发出,要么到 HAL。接口没有指定具体调用方向。需要从 HAL 发起调用的架构应该在 HAL 软件包中提供两个(或更多个)接口并从每个进程提供相应的接口。我们根据接口的调用方向来取名“客户端”或“服务器”(即 HAL 可以是一个接口的服务器,也可以是另一个接口的客户端)。
回调
“回调”一词可以指代两个不同的概念,可通过“同步回调”和“异步回调”进行区分。
“同步回调”用于返回数据的一些 HIDL 方法。返回多个值(或返回非基元类型的一个值)的 HIDL 方法会通过回调函数返回其结果。如果只返回一个值且该值是基元类型,则不使用回调且该值从方法中返回。服务器实现 HIDL 方法,而客户端实现回调。
“异步回调”允许 HIDL 接口的服务器发起调用。通过第一个接口传递第二个接口的实例即可完成此操作。第一个接口的客户端必须作为第二个接口的服务器。第一个接口的服务器可以在第二个接口对象上调用方法。例如,HAL 实现可以通过在由该进程创建和提供的接口对象上调用方法来将信息异步发送回正在使用它的进程。用于异步回调的接口中的方法可以是阻塞方法(并且可以将值返回到调用方),也可以是 oneway
方法。如需查看相关示例,请参阅 HIDL C++ 中的“异步回调”。
为了简化内存所有权,方法调用和回调只接受 in
参数,而不支持 out
或 inout
参数。
每事务限制
每事务限制不会强制限制在 HIDL 方法和回调中发送的数据量。但是,每事务调用 4KB 以上的数据便被视为过度调用。如果发生这种情况,建议重新设计给定 HIDL 接口的架构。另一个限制是可供 HIDL 基础架构处理多个同时进行的事务的资源。由于多个线程或进程向一个进程发送调用或者接收进程未能快速处理多个 oneway
调用,因此多个事务可能会同时进行。默认情况下,所有并发事务可用的最大总空间为 1MB。
在设计良好的接口中,不应出现超出这些资源限制的情况;如果超出的话,则超出资源的调用可能会阻塞,直到资源可用或发出传输错误的信号。每当因正在进行的总事务导致出现超出每事务限制或溢出 HIDL 实现资源的情况时,系统都会记录下来以方便调试。
方法实现
HIDL 生成以目标语言(C++ 或 Java)声明必要类型、方法和回调的标头文件。客户端和服务器代码的 HIDL 定义方法和回调的原型是相同的。HIDL 系统提供调用程序端(整理 IPC 传输的数据)的方法代理实现,并将代码存根到被调用程序端(将数据传递到方法的开发者实现)。
函数的调用程序(HIDL 方法或回调)拥有对传递到该函数的数据结构的所有权,并在调用后保留所有权;被调用程序在所有情况下都无需释放存储。
- 在 C++ 中,数据可能是只读的(尝试写入可能会导致细分错误),并且在调用期间有效。客户端可以深层复制数据,以在调用期间外传播。
- 在 Java 中,代码会接收数据的本地副本(普通 Java 对象),代码可以保留和修改此数据或允许垃圾回收器回收。
非 RPC 数据传输
HIDL 在不使用 RPC 调用的情况下通过两种方法来传输数据:一是共享内存;二是快速消息队列 (FMQ)。只有 C++ 支持这两种方法。
- 共享内存。内置 HIDL 类型
memory
用于传递表示已分配的共享内存的对象。可以在接收进程中用来映射共享内存。 - 快速消息队列 (FMQ)。HIDL 提供了一种可实现无等待消息传递的模板化消息队列类型。它在直通式或绑定式模式下不使用内核或调度程序(设备间通信将不具有这些属性)。通常,HAL 会设置队列的一端,从而创建一个可借助内置 HIDL 类型
MQDescriptorSync
或MQDescriptorUnsync
的参数通过 RPC 传递的对象。接收进程可使用此对象设置队列的另一端。- “已同步”队列不能溢出,且只能有一个读取器。
- “未同步”队列可以溢出,且可以有多个读取器;每个读取器必须及时读取数据,否则数据就会丢失。
如需详细了解 FMQ,请参阅快速消息队列 (FMQ)。