Usługi i przenoszenie danych

Na tej stronie opisano, jak rejestrować i wykrywać usługi oraz jak wysyłać dane do usługi przez wywoływanie metod zdefiniowanych w interfejsach w plikach .hal.

Zarejestruj usługi

Serwery interfejsu HIDL (obiekty implementujące interfejs) mogą być rejestrowane jako usługi o nazwie. Zarejestrowana nazwa nie musi być powiązana z nazwą interfejsu ani nazwą pakietu. Jeśli nie zostanie określona żadna nazwa, zostanie użyta nazwa „default”. Należy jej używać w przypadku interfejsów HAL, które nie muszą rejestrować dwóch implementacji tego samego interfejsu. Na przykład wywołanie funkcji C++ do rejestracji usługi zdefiniowanej w każdym interfejsie:

status_t status = myFoo->registerAsService();
status_t anotherStatus = anotherFoo->registerAsService("another_foo_service");  // if needed

Wersja interfejsu HIDL jest zawarta w samym interfejsie. Jest on automatycznie powiązany z rejestracją usługi i można go pobrać za pomocą wywołania metody (android::hardware::IInterface::getInterfaceVersion()) w każdym interfejsie HIDL. Obiekty serwera nie muszą być rejestrowane i mogą być przekazywane za pomocą parametrów metody HIDL do innego procesu, który wywołuje metody HIDL na serwerze.

Poznawanie usług

Żądania wysyłane przez kod klienta są kierowane do danego interfejsu według nazwy i wersji. W tym celu wywoływana jest funkcja getService odpowiedniej klasy HAL:

// 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 */);

Każda wersja interfejsu HIDL jest traktowana jako osobny interfejs. W ten sposób IFooService w wersji 1.1 i IFooService w wersji 2.2 mogą być zarejestrowane jako „foo_service”, a getService("foo_service") na każdym z interfejsów uzyskuje zarejestrowaną usługę dla tego interfejsu. Dlatego w większości przypadków nie trzeba podawać parametru nazwy do rejestracji ani wykrywania (czyli nazwa „default”).

Obiekt interfejsu dostawcy ma też znaczenie dla metody transportu zwróconego interfejsu. W przypadku interfejsu IFoo w pakiecie android.hardware.foo@1.0 interfejs zwracany przez IFoo::getService zawsze używa metody transportu zadeklarowanej dla android.hardware.foo w pliku manifestu urządzenia, jeśli taki wpis istnieje. Jeśli metoda transportu jest niedostępna, zwracana jest wartość nullptr.

W niektórych przypadkach może być konieczne natychmiastowe kontynuowanie, nawet bez korzystania z usługi. Może się to zdarzyć, gdy klient chce samodzielnie zarządzać powiadomieniami o usługach lub w programie diagnostycznym (np. atrace), który musi pobrać wszystkie usługi sprzętowe i je pobrać. W takim przypadku udostępniamy dodatkowe interfejsy API, takie jak tryGetService w C++ lub getService("instance-name", false) w Java. Do powiadomień o usługach musisz też używać starszego interfejsu APIgetService w języku Java. Korzystanie z tego interfejsu API nie zapobiega sytuacji wyścigu, w której serwer rejestruje się po tym, jak klient wysłał żądanie do jednego z tych interfejsów API bez powtarzania.

Powiadomienia o zakończeniu obsługi

Klienci, którzy chcą otrzymywać powiadomienia o zakończeniu działania usługi, mogą otrzymywać powiadomienia o zakończeniu działania usługi dostarczane przez platformę. Aby otrzymywać powiadomienia, klient:

  1. Utwórz podklasę klasy/interfejsu HIDL hidl_death_recipient (w języku C++, a nie w HIDL).
  2. Zastąp metodę serviceDied().
  3. Tworzenie instancji obiektu podklasy hidl_death_recipient.
  4. Wywołaj metodę linkToDeath() w usłudze, którą chcesz monitorować, przekazując obiekt interfejsu IDeathRecipient. Pamiętaj, że ta metoda nie przejmuje własności odbiorcy wiadomości o zakończeniu działania ani od serwera proxy, na którym jest wywoływana.

Przykład pseudokodu (C++ i Java są podobne):

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);

Ten sam odbiorca informacji o śmierci może być zarejestrowany w wielu różnych usługach.

Przenoszenie danych

Dane można wysyłać do usługi, wywołując metody zdefiniowane w interfejsach w plikach .hal. Istnieją 2 rodzaje metod:

  • Metody blokujące czekają, aż serwer wygeneruje wynik.
  • Metody jednokierunkowe wysyłają dane tylko w jednym kierunku i nie blokują ich. Jeśli ilość danych w wywołaniach RPC przekracza limity implementacji, wywołania mogą zostać zablokowane lub zwrócone z błędem (zachowanie nie zostało jeszcze określone).

Metoda, która nie zwraca wartości, ale nie jest zadeklarowana jako oneway, nadal blokuje.

Wszystkie metody zadeklarowane w interfejsie HIDL są wywoływane w jednym kierunku, albo z interfejsu HAL, albo do interfejsu HAL. Interfejs nie określa, w którym kierunku jest wywoływany. Architektury, które wymagają wywołań pochodzących z HAL, powinny udostępniać 2 interfejsy (lub więcej) w pakiecie HAL i zapewniać odpowiedni interfejs dla każdego procesu. Słowa klientserwer są używane w zależności od kierunku wywołania interfejsu (tzn. HAL może być serwerem jednego interfejsu i klientem innego interfejsu).

Wywołania zwrotne

Słowo wywołanie zwrotne odnosi się do 2 różnych koncepcji, które różnią się między sobą: wywołanie zwrotne synchronicznewywołanie zwrotne asynchroniczne.

Wywołania zwrotne synchroniczne są używane w niektórych metodach HIDL, które zwracają dane. Metoda HIDL, która zwraca więcej niż jedną wartość (lub zwraca jedną wartość typu niepierwotnego), zwraca wyniki za pomocą funkcji wywołania zwrotnego. Jeśli zwracana jest tylko jedna wartość i jest ona typu prymitywnego, nie jest używana funkcja wywołania zwrotnego, a wartość jest zwracana z metody. Serwer implementuje metody HIDL, a klient implementuje wywołania zwrotne.

Wywołania zwrotne asynchroniczne umożliwiają serwerowi interfejsu HIDL inicjowanie wywołań. W tym celu należy przekazać instancję drugiego interfejsu przez pierwszy interfejs. Klient pierwszego interfejsu musi działać jako serwer drugiego. Serwer pierwszego interfejsu może wywoływać metody drugiego obiektu interfejsu. Na przykład implementacja HAL może przesyłać informacje asynchronicznie z powrotem do procesu, który ich używa, przez wywoływanie metod obiektu interfejsu utworzonego i obsługiwanego przez ten proces. Metody w interfejsach używanych do wywołania zwrotnego asynchronicznego mogą być blokujące (i zwracać wartości do wywołującego) lub oneway. Przykład znajdziesz w sekcji „Wywołania asynchroniczne” w HIDL C++.

Aby uprościć zarządzanie pamięcią, wywołania metod i wywołania zwrotne przyjmują tylko parametry in i nie obsługują parametrów out ani inout.

Limity na transakcję

Limity na transakcję nie są nakładane na ilość danych wysyłanych w metodach i funkcjach wywołania zwrotnego HIDL. Jednak wywołania przekraczające 4 KB na transakcję są uznawane za nadmierne. W takim przypadku zalecamy zmianę architektury danego interfejsu HIDL. Kolejnym ograniczeniem są zasoby dostępne dla infrastruktury HIDL do obsługi wielu jednoczesnych transakcji. Wiele transakcji może być przetwarzanych jednocześnie z powodu wielu wątków lub procesów wysyłających wywołania do procesu lub wielu wywołań oneway, które nie są szybko obsługiwane przez proces odbierający. Domyślnie maksymalna łączna przestrzeń dostępna dla wszystkich równoczesnych transakcji to 1 MB.

W dobrze zaprojektowanym interfejsie nie powinno dojść do przekroczenia tych limitów zasobów. Jeśli jednak do tego dojdzie, wywołanie, które je przekroczyło, może zostać zablokowane, dopóki zasoby nie staną się dostępne, lub może sygnalizować błąd transportu. Aby ułatwić debugowanie, rejestrowane są wszystkie przypadki przekroczenia limitów na transakcję lub przepełnienia zasobów implementacji HIDL przez agregat transakcji w trakcie przetwarzania.

Implementacje metod

HIDL generuje pliki nagłówków, które deklarują niezbędne typy, metody i wywołania zwrotne w języku docelowym (C++ lub Java). Prototyp metod i wyzwań zwrotnych zdefiniowanych w HIDL jest taki sam zarówno w przypadku kodu po stronie klienta, jak i po stronie serwera. System HIDL udostępnia zastępcze implementacje metod po stronie wywołującego, które organizują dane do transportu IPC, oraz zastępczy kod po stronie wywoływanego, który przekazuje dane do implementacji metod przez programistów.

Użytkownik wywołujący funkcję (metoda HIDL lub wywołanie zwrotne) jest właścicielem struktur danych przekazanych do funkcji i zachowuje prawo własności po wywołaniu. W żadnym przypadku wywoływany nie musi zwalniać ani zwalniać miejsca na dane.

  • W języku C++ dane mogą być tylko do odczytu (próba zapisu może spowodować błąd segmentacji) i są ważne przez czas trwania wywołania. Klient może skopiować dane w głębi, aby rozpowszechnić je poza połączeniem.
  • W języku Java kod otrzymuje lokalną kopię danych (zwykły obiekt Java), którą może zachować i zmodyfikować lub zezwolić na jej usunięcie przez funkcję garbage-collection.

Przesyłanie danych bez użycia RPC

HIDL umożliwia przesyłanie danych bez wywołania RPC na 2 sposoby: za pomocą współdzielonej pamięci i szybkiej kolejki komunikatów (FMQ). Oba są obsługiwane tylko w C++.

  • Udostępnione wspomnienie. Wbudowany typ HIDL memory służy do przekazywania obiektu reprezentującego przydzieloną pamięć współdzieloną. Może być używany w procesie odbierania do mapowania współdzielonej pamięci.
  • kolejka szybkich wiadomości (FMQ). HIDL udostępnia kolejkę wiadomości typu szablonowego, która implementuje przekazywanie wiadomości bez oczekiwania. Nie używa jądra ani harmonogramu w trybie przekazywania lub binderized (komunikacja między urządzeniami nie ma tych właściwości). Zazwyczaj HAL konfiguruje swój koniec kolejki, tworząc obiekt, który można przekazać przez RPC za pomocą parametru wbudowanego typu HIDL MQDescriptorSync lub MQDescriptorUnsync. Ten obiekt może być używany przez proces odbierający do konfigurowania drugiego końca kolejki.
    • Kolejki synchronizacji nie mogą się przepełniać i mają tylko 1 czytnik.
    • Kolejki Unsync mogą się przepełniać i mieć wielu czytelników. Każdy z nich musi odczytać dane na czas, w przeciwnym razie zostaną utracone.
    Żaden z tych typów nie może mieć wartości poniżej zera (odczyt z pustej kolejki kończy się niepowodzeniem), a każdy typ może mieć tylko jednego zapisującego.

Więcej informacji o FMQ znajdziesz w artykule Fast Message Queue (FMQ).