服务和数据传输

本页介绍了如何注册和发现服务,以及如何通过调用 .hal 文件中定义的接口中的方法将数据发送到服务。

注册服务

HIDL 接口服务器(实现接口的对象)可以注册为命名服务。注册的名称不必与接口或软件包名称相关。如果未指定名称,则使用名称“default”;这应该用于不需要注册同一接口的两个实现的 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") 都会获取该接口的注册服务。这就是为什么在大多数情况下,不需要为注册或发现提供名称参数(表示名称“default”)。

供应商接口对象还在返回接口的传输方法中发挥作用。对于软件包 android.hardware.foo@1.0 中的接口 IFoo,如果设备清单中存在条目,则 IFoo::getService 返回的接口始终使用为 android.hardware.foo 声明的传输方法;如果传输方法不可用,则返回 nullptr。

在某些情况下,即使没有获得服务,也可能有必要立即继续。当客户端想要自行管理服务通知时,或者在诊断程序(例如 atrace)中需要获取所有 hwservice 并检索它们时,可能会发生这种情况。在这种情况下,提供了额外的 API,例如 C++ 中的 tryGetService 或 Java 中的 getService("instance-name", false)。Java 中提供的旧版 API getService 也必须与服务通知一起使用。使用此 API 并不能避免在客户端使用这些无重试 API 之一请求服务器后,服务器注册自身的情况下的竞争条件。

服务崩溃通知

希望在服务终止时收到通知的客户端可以接收框架传递的终止通知。要接收通知,客户端必须

  1. 继承 HIDL 类/接口 hidl_death_recipient(在 C++ 代码中,而不是在 HIDL 中)。
  2. 重写其 serviceDied() 方法。
  3. 实例化 hidl_death_recipient 子类的对象。
  4. 对要监控的服务调用 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 参数,并且不支持 outinout 参数。

每事务限制

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 设置其队列末端,创建一个可以通过 RPC 通过内置 HIDL 类型 MQDescriptorSyncMQDescriptorUnsync 的参数传递的对象。接收进程可以使用此对象来设置队列的另一端。
    • 同步队列不允许溢出,并且只能有一个读取器。
    • 非同步队列允许溢出,并且可以有多个读取器,每个读取器都必须及时读取数据,否则会丢失数据。
    两种类型都不允许下溢(从空队列读取会失败),并且每种类型只能有一个写入器。

有关 FMQ 的更多详细信息,请参阅 快速消息队列 (FMQ)