服務和數據傳輸

本節介紹如何註冊和發現服務,以及如何通過調用.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)