在 HIDL 套件中定義的每個介面,都會在其套件命名空間中擁有專屬的自動產生 C++ 類別。用戶端和伺服器會以不同方式處理介面:
- 伺服器會實作介面。
- 用戶端會呼叫介面上的各項方法。
伺服器可以依名稱註冊介面,也可以將介面做為參數傳遞至 HIDL 定義的方法。舉例來說,架構程式碼可以提供介面,用於接收來自 HAL 的非同步訊息,並直接將該介面傳遞至 HAL,而無須註冊。
伺服器實作
實作 IFoo
介面的伺服器必須包含自動產生的 IFoo
標頭檔案:
#include <android/hardware/samples/1.0/IFoo.h>
標頭會由 IFoo
介面的共用資料庫自動匯出,以便連結。IFoo.hal
範例:
// IFoo.hal interface IFoo { someMethod() generates (vec<uint32_t>); ... }
IFoo 介面伺服器實作範例骨架:
// From the IFoo.h header using android::hardware::samples::V1_0::IFoo; class FooImpl : public IFoo { Return<void> someMethod(foo my_foo, someMethod_cb _cb) { vec<uint32_t> return_data; // Compute return_data _cb(return_data); return Void(); } ... };
如要讓用戶端可使用伺服器介面實作項目,您可以:
- 註冊介面實作項目,並使用
hwservicemanager
(詳情請見下文),
或
- 傳遞介面實作項目做為介面方法的引數 (詳情請參閱「非同步回呼」)。
註冊介面實作項目時,hwservicemanager
程序會依名稱和版本追蹤在裝置上執行的已註冊 HIDL 介面。伺服器可以依名稱註冊 HIDL 介面實作,而用戶端可以依名稱和版本要求服務實作。這個程序會提供 HIDL 介面 android.hidl.manager@1.0::IServiceManager
。
每個自動產生的 HIDL 介面標頭檔案 (例如 IFoo.h
) 都有 registerAsService()
方法,可用於將介面實作項目註冊至 hwservicemanager
。唯一必要的引數是介面實作名稱,因為用戶端會使用這個名稱,稍後從 hwservicemanager
擷取介面:
::android::sp<IFoo> myFoo = new FooImpl(); ::android::sp<IFoo> mySecondFoo = new FooAnotherImpl(); status_t status = myFoo->registerAsService(); status_t anotherStatus = mySecondFoo->registerAsService("another_foo");
hwservicemanager
會將 [package@version::interface, instance_name]
組合視為唯一,讓不同介面 (或相同介面的不同版本) 可使用相同的例項名稱註冊,而不會發生衝突。如果您使用完全相同的套件版本、介面和執行個體名稱呼叫 registerAsService()
,hwservicemanager
就會放棄對先前註冊服務的參照,並使用新的服務。
用戶端實作
如同伺服器,用戶端必須 #include
其參照的每個介面:
#include <android/hardware/samples/1.0/IFoo.h>
用戶端可以透過兩種方式取得介面:
- 透過
I<InterfaceName>::getService
(透過hwservicemanager
) - 透過介面方法
每個自動產生的介面標頭檔案都有一個靜態 getService
方法,可用來從 hwservicemanager
擷取服務執行個體:
// getService returns nullptr if the service can't be found sp<IFoo> myFoo = IFoo::getService(); sp<IFoo> myAlternateFoo = IFoo::getService("another_foo");
如今,用戶端具有 IFoo
介面,可以呼叫方法,就像呼叫本機類別實作一樣。實際上,實作作業可以在相同或不同的程序中執行,甚至可以在其他裝置上執行 (使用 HAL 遠端處理)。由於用戶端在 1.0
版本的套件中納入的 IFoo
物件上呼叫 getService
,因此 hwservicemanager
只會在該實作與 1.0
用戶端相容時,傳回伺服器實作。實際上,這表示只有版本為 1.n
的伺服器實作項目 (介面版本 x.(y+1)
必須擴充 (繼承自) x.y
)。
此外,系統會提供 castFrom
方法,以便在不同介面之間轉換。這個方法會對遠端介面發出 IPC 呼叫,確保基礎類型與要求的類型相同。如果無法取得要求的類型,系統會傳回 nullptr
。
sp<V1_0::IFoo> foo1_0 = V1_0::IFoo::getService(); sp<V1_1::IFoo> foo1_1 = V1_1::IFoo::castFrom(foo1_0);
非同步回呼
許多現有的 HAL 實作會與非同步硬體通訊,這表示它們需要以非同步方式,向用戶端通知發生的新事件。HIDL 介面函式可使用 HIDL 介面物件做為參數,因此可用於非同步回呼。
介面檔案 IFooCallback.hal
範例:
package android.hardware.samples@1.0; interface IFooCallback { sendEvent(uint32_t event_id); sendData(vec<uint8_t> data); }
IFoo
中採用 IFooCallback
參數的新方法範例:
package android.hardware.samples@1.0; interface IFoo { struct Foo { int64_t someValue; handle myHandle; }; someMethod(Foo foo) generates (int32_t ret); anotherMethod() generates (vec<uint32_t>); registerCallback(IFooCallback callback); };
使用 IFoo
介面的用戶端是 IFooCallback
介面的伺服器;它提供 IFooCallback
的實作:
class FooCallback : public IFooCallback { Return<void> sendEvent(uint32_t event_id) { // process the event from the HAL } Return<void> sendData(const hidl_vec<uint8_t>& data) { // process data from the HAL } };
也可以直接將該值傳遞至 IFoo
介面的現有例項:
sp<IFooCallback> myFooCallback = new FooCallback(); myFoo.registerCallback(myFooCallback);
實作 IFoo
的伺服器會以 sp<IFooCallback>
物件的形式接收此值。它可以儲存回呼,並在需要使用此介面時回呼至用戶端。
死亡受益人
由於服務實作項目可以在不同的程序中執行,因此在用戶端保持運作狀態的同時,實作介面的程序可能會終止。對已終止程序中代管的介面物件呼叫,都會因傳輸錯誤而失敗 (isOK()
會傳回 false
)。要從這種失敗中復原的唯一方法,就是呼叫 I<InterfaceName>::getService()
來要求服務的新例項。只有在發生當機的程序已重新啟動,並透過 servicemanager
重新註冊其服務時,這項做法才會生效 (對於 HAL 實作通常是如此)。
除了以回應方式處理這個問題,介面用戶端也可以註冊死亡接收者,在服務終止時收到通知。如要在擷取的 IFoo
介面上註冊這類通知,用戶端可以執行下列操作:
foo->linkToDeath(recipient, 1481 /* cookie */);
recipient
參數必須是 HIDL 提供的 android::hardware::hidl_death_recipient
介面實作項目,其中包含單一方法 serviceDied()
,當代管介面的程序終止時,會從 RPC 執行緒池中的執行緒呼叫此方法:
class MyDeathRecipient : public android::hardware::hidl_death_recipient { virtual void serviceDied(uint64_t cookie, const android::wp<::android::hidl::base::V1_0::IBase>& who) { // Deal with the fact that the service died } }
cookie
參數包含透過 linkToDeath()
傳入的 Cookie,而 who
參數則包含指向用戶端中代表服務的物件的弱指標。在上述範例呼叫中,cookie
等於 1481,who
則等於 foo
。
註冊後,您也可以取消註冊死亡通知接收者:
foo->unlinkToDeath(recipient);