服務和資料移轉

本頁面說明如何註冊及探索服務,以及如何透過呼叫 .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) 需要取得並擷取所有 hwservices 時,就可能發生這種情況。在這種情況下,系統會提供其他 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 的介面物件。請注意,這個方法不會取得死亡接收者或其所呼叫的 Proxy 的擁有權。

虛擬程式碼範例 (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 檔案介面中定義的方法,將資料傳送至服務。方法分為兩種:

  • 阻斷方法會等待伺服器產生結果。
  • Oneway 方法只會單向傳送資料,且不會封鎖。如果 RPC 呼叫中傳輸的資料量超過實作限制,呼叫可能會遭到封鎖或傳回錯誤指示 (行為尚未確定)。

未傳回值但未宣告為 oneway 的方法仍會阻斷。

在 HIDL 介面中宣告的所有方法都會以單一方向呼叫,從 HAL 或進入 HAL。介面不會指定呼叫方向。需要從 HAL 發出呼叫的架構應在 HAL 套件中提供兩個 (或更多) 介面,並從每個程序提供適當的介面。用戶端伺服器一詞的用途是指介面呼叫方向 (也就是 HAL 可以是某個介面的伺服器,同時也是另一個介面的用戶端)。

回呼

「回呼」一詞指的是兩種不同的概念,分別是同步回呼非同步回呼

同步回呼會用於傳回資料的部分 HIDL 方法。傳回多個值 (或傳回非基本類型值) 的 HIDL 方法會透過回呼函式傳回結果。如果只傳回一個值,且該值為基本類型,則不會使用回呼,而是從方法傳回該值。伺服器會實作 HIDL 方法,而用戶端則會實作回呼。

非同步回呼可讓 HIDL 介面的伺服器發出呼叫。方法是透過第一個介面傳遞第二個介面的例項。第一個介面的用戶端必須充當第二個介面的伺服器。第一個介面的伺服器可以呼叫第二個介面物件的各項方法。舉例來說,HAL 實作可以透過呼叫所建立及提供的介面物件上的方法,將資訊以非同步方式傳回給使用該介面的程序。用於非同步回呼的介面中的方法可能會阻斷 (並可能傳回值給呼叫端) 或 oneway。如需範例,請參閱 HIDL C++ 中的「非同步回呼」。

為簡化記憶體擁有權,方法呼叫和回呼只會採用 in 參數,且不支援 outinout 參數。

單筆交易限制

在 HIDL 方法和回呼中傳送的資料量,不受每筆交易限制的限制。不過,如果每筆交易的呼叫超過 4 KB,就會被視為過多。如果出現這種情況,建議您重新架構指定的 HIDL 介面。另一個限制是,HIDL 基礎架構可用於處理多個同時交易的資源。由於多個執行緒或程序會將呼叫傳送至程序,或是多個 oneway 呼叫未能快速由接收程序處理,因此多筆交易可能會同時處於執行中。根據預設,所有並行交易可用的總空間上限為 1 MB。

在設計良好的介面中,超出這些資源限制的情況不應發生;如果發生這種情況,超出限制的呼叫會阻斷,直到資源可用為止,或傳送錯誤信號。系統會記錄每個超過個別交易限制的情況,或在處理中的交易匯總導致 HIDL 實作資源溢出的情況,以利偵錯。

方法實作

HIDL 會產生標頭檔案,宣告目標語言 (C++ 或 Java) 中的必要類型、方法和回呼。用戶端和伺服器程式碼的 HIDL 定義方法和回呼原型皆相同。HIDL 系統會在呼叫端提供方法的proxy 實作項目,用於整理 IPC 傳輸的資料,並在被呼叫端提供stub 程式碼,用於將資料傳遞至開發人員實作的函式。

函式 (HIDL 方法或回呼) 的呼叫端擁有傳遞至函式的資料結構,並在呼叫後保留擁有權;在所有情況下,被呼叫端都不需要釋放或釋出儲存空間。

  • 在 C++ 中,資料可能為唯讀 (嘗試寫入資料可能會導致區段錯誤),且在呼叫期間有效。用戶端可以深層複製資料,以便在呼叫之外傳播資料。
  • 在 Java 中,程式碼會接收資料的本機副本 (一般 Java 物件),並可能保留及修改該副本,或允許系統進行垃圾收集。

非 RPC 資料移轉

HIDL 有兩種方式可在不使用 RPC 呼叫的情況下傳輸資料:共用記憶體和快速訊息佇列 (FMQ),這兩種方式都僅支援 C++。

  • 共享回憶集錦。內建的 HIDL 類型 memory 用於傳遞代表已分配共用記憶體的物件。可在接收程序中用於對應共用記憶體。
  • 快速訊息佇列 (FMQ)。HIDL 提供範本訊息佇列類型,可實作不需等待的訊息傳遞功能。它不會在傳遞或繫結模式下使用核心或排程器 (裝置間通訊沒有這些屬性)。通常,HAL 會設定佇列的結尾,建立可透過 RPC 傳遞的物件,其中的參數為內建 HIDL 類型 MQDescriptorSyncMQDescriptorUnsync。接收程序可使用這個物件設定佇列的另一端。
    • Sync 佇列不允許溢位,且只能有一個讀取器。
    • Unsync 佇列可溢出,且可有許多讀取器,每個讀取器都必須及時讀取資料,否則資料就會遺失。
    兩種類型皆不允許發生下溢 (從空佇列讀取會失敗),且每個類型只能有一個寫入器。

如要進一步瞭解 FMQ,請參閱「快速訊息佇列 (FMQ)」一文。