本頁面說明如何註冊及探索服務,以及如何透過呼叫 .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 要求後,伺服器會自行註冊的情況。
服務終止通知
如要收到服務終止時的通知,用戶端可以接收架構提供的終止通知。如要接收通知,用戶端必須:
- 將 HIDL 類別/介面
hidl_death_recipient
設為子類別 (在 C++ 程式碼中,而非在 HIDL 中)。 - 覆寫其
serviceDied()
方法。 - 將
hidl_death_recipient
子類別的物件例項化。 - 對要監控的服務呼叫
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
參數,且不支援 out
或 inout
參數。
單筆交易限制
在 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 類型
MQDescriptorSync
或MQDescriptorUnsync
。接收程序可使用這個物件設定佇列的另一端。- Sync 佇列不允許溢位,且只能有一個讀取器。
- Unsync 佇列可溢出,且可有許多讀取器,每個讀取器都必須及時讀取資料,否則資料就會遺失。
如要進一步瞭解 FMQ,請參閱「快速訊息佇列 (FMQ)」一文。