服務與資料傳輸

本節介紹如何註冊和發現服務以及如何透過呼叫.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)