Interfejsy

Każdy interfejs zdefiniowany w pakiecie HIDL ma własną, automatycznie wygenerowaną klasę C++ w przestrzeni nazw pakietu. Klienci i serwery radzą sobie z interfejsami na różne sposoby:

  • Serwery implementują interfejsy.
  • Klienci wywołują metody na interfejsach.

Interfejsy mogą być rejestrowane przez serwer według nazwy lub przekazywane jako parametry do metod zdefiniowanych w języku HIDL. Na przykład kod struktury może służyć jako interfejs do odbierania asynchronicznych komunikatów z warstwy HAL i przekazywania tego interfejsu bezpośrednio do warstwy HAL bez jej rejestracji.

Implementacja serwera

Serwer implementujący interfejs IFoo musi zawierać plik nagłówkowy IFoo , który został wygenerowany automatycznie:

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

Nagłówek jest automatycznie eksportowany przez współdzieloną bibliotekę interfejsu IFoo w celu połączenia. Przykład IFoo.hal :

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

Przykładowy szkielet serwerowej implementacji interfejsu 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();
    }
    ...
};

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

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

    LUB

  2. Przekaż implementację interfejsu jako argument metody interfejsu (szczegóły można znaleźć w artykule Asynchroniczne wywołania 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ług według nazwy i wersji. Proces ten obsługuje interfejs HIDL android.hidl.manager@1.0::IServiceManager .

Każdy automatycznie wygenerowany plik nagłówkowy interfejsu HIDL (taki jak IFoo.h ) zawiera metodę registerAsService() , której można użyć do zarejestrowania implementacji interfejsu w hwservicemanager . Jedynym wymaganym argumentem jest nazwa implementacji interfejsu, ponieważ klienci będą używać tej nazwy do późniejszego pobrania interfejsu z 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 traktuje kombinację [package@version::interface, instance_name] jako unikatową, aby umożliwić różnym interfejsom (lub różnym wersjom tego samego interfejsu) rejestrację z identycznymi nazwami instancji bez konfliktów. Jeśli wywołasz funkcję registerAsService() z dokładnie tą samą wersją pakietu, interfejsem i nazwą instancji, hwservicemanager porzuci odniesienie do wcześniej zarejestrowanej usługi i użyje nowej.

Wdrożenie klienta

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 dwa sposoby:

  • Przez I<InterfaceName>::getService (przez hwservicemanager )
  • Poprzez metodę interfejsu

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

// getService will return 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ć do niego metody tak, jakby była to implementacja klasy lokalnej. W rzeczywistości wdrożenie może przebiegać w tym samym procesie, w innym procesie, a nawet na innym urządzeniu (przy zdalnym korzystaniu z HAL). Ponieważ klient wywołał getService na obiekcie IFoo zawartym w wersji 1.0 pakietu, hwservicemanager zwraca implementację serwera tylko wtedy, gdy ta implementacja jest kompatybilna z klientami w wersji 1.0 . W praktyce oznacza to tylko implementacje serwerowe w wersji 1.n (wersja x.(y+1) interfejsu musi rozszerzać (dziedziczyć po) xy ).

Dodatkowo dostępna jest metoda castFrom umożliwiająca rzutowanie pomiędzy różnymi interfejsami. Ta metoda polega na wykonaniu wywołania IPC do zdalnego interfejsu, aby upewnić się, że typ bazowy jest taki sam, jak typ żądany. Jeśli żądany typ jest niedostępny, zwracana jest nullptr .

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

Asynchroniczne wywołania zwrotne

Wiele istniejących implementacji HAL komunikuje się ze sprzętem asynchronicznym, co oznacza, że ​​potrzebują asynchronicznego sposobu powiadamiania klientów o nowych zdarzeniach, które miały miejsce. Interfejsu HIDL można używać jako asynchronicznego wywołania zwrotnego, 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ładowa nowa metoda 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 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że także po prostu przekazać to przez istniejącą instancję interfejsu IFoo :

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

Serwer implementujący IFoo odbiera to jako obiekt sp<IFooCallback> . Może przechowywać wywołanie zwrotne i oddzwaniać do klienta, gdy tylko chce skorzystać z tego interfejsu.

Odbiorcy śmierci

Ponieważ implementacje usług mogą przebiegać w ramach różnych procesów, może się zdarzyć, że proces implementujący interfejs zakończy się, podczas gdy klient pozostanie przy życiu. Wszelkie wywołania obiektu interfejsu hostowanego w procesie, który zakończył działanie, zakończą się niepowodzeniem z powodu błędu transportu ( isOK() zwróci wartość false). Jedynym sposobem na odzyskanie sprawności po takiej awarii jest zażądanie nowej instancji usługi poprzez 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 prawdą w przypadku implementacji HAL).

Zamiast zajmować się tym reaktywnie, klienci interfejsu mogą również zarejestrować odbiorcę śmierci , aby otrzymać powiadomienie w przypadku śmierci usługi. Aby zarejestrować się w celu otrzymywania takich powiadomień w pobranym interfejsie IFoo , klient może wykonać następujące czynności:

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

Parametr recipient musi być implementacją interfejsu android::hardware::hidl_death_recipient dostarczonego przez HIDL, który zawiera pojedynczą metodę serviceDied() , która zostanie wywołana z wątku w puli wątków RPC, gdy proces obsługujący interfejs umrze:

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 za pomocą linkToDeath() , natomiast parametr who zawiera słaby wskaźnik do obiektu reprezentującego usługę w kliencie. W przypadku przykładowego wywołania podanego powyżej cookie wynosi 1481, a who jest równy foo .

Możliwe jest także wyrejestrowanie zmarłego po jego zarejestrowaniu:

foo->unlinkToDeath(recipient);