Interfejsy

Każdy interfejs zdefiniowany w pakiecie HIDL ma własną wygenerowaną klasę C++ w przestrzeni nazw pakietu. Klienci i serwery obsługują interfejsy na różne sposoby:

  • Serwery implementują interfejsy.
  • Klienci wywołują metody interfejsów.

Interfejsy mogą być rejestrowane przez serwer według nazwy lub przekazywane jako parametry do metod zdefiniowanych w HIDL. Na przykład kod frameworka może udostępniać interfejs do odbierania asynchronicznych komunikatów z HAL i przekazywać ten interfejs bezpośrednio do HAL bez rejestracji.

Implementacja po stronie serwera

Serwer implementujący interfejs IFoo musi zawierać wygenerowany automatycznie plik nagłówka IFoo:

#include <android/hardware/samples/1.0/IFoo.h>

Nagłówek jest automatycznie eksportowany przez udostępnioną bibliotekę interfejsu IFoo, aby można było go połączyć. Przykład IFoo.hal:

// IFoo.hal
interface IFoo {
    someMethod() generates (vec<uint32_t>);
    ...
}

Przykładowy szkielet implementacji interfejsu IFoo na serwerze:

// 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();
    }
    ...
};

Aby udostępnić klientowi implementację interfejsu serwera, możesz:

  1. Zarejestruj implementację interfejsu w hwservicemanager (szczegóły znajdziesz poniżej).

    LUB

  2. Przekazuj implementację interfejsu jako argument metody interfejsu (szczegóły znajdziesz w sekcji Asynchroniczne funkcje zwrotne).

Podczas rejestrowania implementacji interfejsu proces hwservicemanager śledzi zarejestrowane interfejsy HIDL działające na urządzeniu według nazwy i wersji. Serwery mogą rejestrować implementację interfejsu HIDL według nazwy, a klienci mogą żądać implementacji usługi według nazwy i wersji. Ten proces obsługuje interfejs HIDL.android.hidl.manager@1.0::IServiceManager

Każdy automatycznie wygenerowany plik nagłówka interfejsu HIDL (np. IFoo.h) ma metodę registerAsService(), której można użyć do zarejestrowania implementacji interfejsu w interfejsie hwservicemanager. Jedynym wymaganym argumentem jest nazwa implementacji interfejsu, ponieważ klienci używają tej nazwy do pobierania interfejsu z interfejsu 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");

hwservicemanagertraktuje kombinację [package@version::interface, instance_name] jako unikalną, aby umożliwić rejestrowanie różnych interfejsów (lub różnych wersji tego samego interfejsu) z identycznymi nazwami instancji bez konfliktów. Jeśli wywołasz registerAsService() z tą samą wersją pakietu, interfejsem i nazwą instancji, hwservicemanager usuwa odwołanie do wcześniej zarejestrowanej usługi i używa nowej.

Wdrożenie na kliencie

Podobnie jak serwer, klient musi #include każdy interfejs, do którego się odnosi:

#include <android/hardware/samples/1.0/IFoo.h>

Klient może uzyskać interfejs na 2 sposoby:

  • Przez I<InterfaceName>::getService (poprzez hwservicemanager)
  • Za pomocą metody interfejsu

Każdy wygenerowany automatycznie plik nagłówka interfejsu zawiera stałą metodę getService, której można użyć do pobrania instancji usługi z poziomu hwservicemanager:

// getService returns nullptr if the service can't be found
sp<IFoo> myFoo = IFoo::getService();
sp<IFoo> myAlternateFoo = IFoo::getService("another_foo");

Teraz klient ma interfejs IFoo i może wywoływać metody tak, jakby były one implementowane lokalnie. W praktyce implementacja może działać w ramach tego samego procesu, innego procesu lub nawet na innym urządzeniu (za pomocą funkcji zdalnego sterowania HAL). Ponieważ klient wywołał funkcję getService na obiekcie IFoo zawartym w wersji 1.0 pakietu, funkcja hwservicemanager zwraca implementację serwera tylko wtedy, gdy jest ona zgodna z klientami 1.0. W praktyce oznacza to, że tylko implementacje serwera w wersji 1.n (wersja x.(y+1) interfejsu musi rozszerzać (dziedzić z) x.y.

Dodatkowo metoda castFrom umożliwia przekształcanie między różnymi interfejsami. Ta metoda polega na wywołaniu interfejsu IPC na zdalnym interfejsie, aby upewnić się, że podstawowy typ jest taki sam jak typ, którego dotyczy żądanie. Jeśli żądany typ jest niedostępny, zwracana jest wartość nullptr.

sp<V1_0::IFoo> foo1_0 = V1_0::IFoo::getService();
sp<V1_1::IFoo> foo1_1 = V1_1::IFoo::castFrom(foo1_0);

Wywołania zwrotne asynchroniczne

Wiele istniejących implementacji HAL komunikuje się z niesynchronizowanym sprzętem, co oznacza, że potrzebują asynchronicznego sposobu powiadamiania klientów o wydarzeniach. Interfejs HIDL może być używany jako asynchroniczny wywołanie zwrotne, ponieważ funkcje interfejsu HIDL mogą przyjmować obiekty interfejsu HIDL jako parametry.

Przykładowy plik interfejsu IFooCallback.hal:

package android.hardware.samples@1.0;
interface IFooCallback {
    sendEvent(uint32_t event_id);
    sendData(vec<uint8_t> data);
}

Przykład nowej metody w IFoo, która przyjmuje parametr 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);
};

Klient korzystający z interfejsu IFoo jest serwerem interfejsu IFooCallback. Zapewnia on implementację 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
    }
};

Możesz też po prostu przekazać to do istniejącej instancji interfejsu IFoo:

sp<IFooCallback> myFooCallback = new FooCallback();
myFoo.registerCallback(myFooCallback);

Serwer implementujący IFoo otrzymuje to jako obiekt sp<IFooCallback>. Może ona przechowywać wywołanie zwrotne i wywoływać klienta, gdy tylko chce użyć tego interfejsu.

Odbiorcy powiadomień o śmierci

Implementacje usług mogą działać w ramach innego procesu, więc może się zdarzyć, że proces implementujący interfejs zostanie przerwany, a klient pozostanie aktywny. Każde wywołanie obiektu interfejsu hostowanego w procesie, który został zamknięty, kończy się błędem transportu (isOK() zwraca false). Jedynym sposobem na odzyskanie po takim błędzie jest wysłanie żądania dotyczącego nowego wystąpienia usługi przez wywołanie I<InterfaceName>::getService(). Działa to tylko wtedy, gdy proces, który uległ awarii, został ponownie uruchomiony i ponownie zarejestrował swoje usługi w servicemanager (co jest ogólnie prawdziwe w przypadku implementacji HAL).

Zamiast reagować na problemy, klienci interfejsu mogą też zarejestrować odbiorcę powiadomień o awarii, aby otrzymywać powiadomienia o awarii usługi. Aby zarejestrować się w interfejsie IFoo, klient może wykonać te czynności:

foo->linkToDeath(recipient, 1481 /* cookie */);

Parametr recipient musi być implementacją interfejsu android::hardware::hidl_death_recipient udostępnianego przez HIDL, który zawiera jedną metodę serviceDied() wywoływaną z wątku w zbiorze wątków RPC, gdy proces hostujący interfejs zostanie przerwany:

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
    }
}

Parametr cookie zawiera plik cookie przekazany z parametrem linkToDeath(), a parametr who zawiera słaby wskaźnik do obiektu reprezentującego usługę w kliencie. W tym przykładzie cookie = 1481, a who = foo.

Po zarejestrowaniu możesz też wyrejestrować odbiorcę informacji o śmierci:

foo->unlinkToDeath(recipient);