服务与数据传输

本节介绍如何注册和发现服务以及如何通过调用.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")都会获取该接口的注册服务。这就是为什么在大多数情况下,不需要为注册或发现提供名称参数(意味着名称“默认”)。

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

在某些情况下,即使没有获得服务,也可能需要立即继续。例如,当客户端想要自己管理服务通知或在需要获取所有硬件服务并检索它们的诊断程序(例如atrace )中管理服务通知时,可能会发生这种情况。在这种情况下,提供了额外的 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 设置其队列末尾,创建一个可以通过内置 HIDL 类型MQDescriptorSyncMQDescriptorUnsync的参数通过 RPC 传递的对象。接收进程可以使用该对象来设置队列的另一端。
    • 同步队列不允许溢出,并且只能有一个读取器。
    • 不同步队列允许溢出,并且可以有多个读取器,每个读取器都必须及时读取数据,否则就会丢失数据。
    两种类型都不允许下溢(从空队列读取将失败),并且每种类型只能有一个写入器。

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