HIDL w C++

Android 8 zmienia architekturę systemu operacyjnego Androida, aby wyraźnie oddzielić interfejsy platformy Androida niezależnej od urządzenia od kodu związanego z urządzeniem i producentem. Android definiuje już wiele takich interfejsów w postaci interfejsów HAL, zdefiniowanych jako nagłówki C w pliku hardware/libhardware. HIDL zastępuje te interfejsy HAL stabilnymi interfejsami z wersjami, które mogą być interfejsami HIDL po stronie klienta i serwera w C++ (opisane poniżej) lub w Java.

Strony w tej sekcji opisują implementacje interfejsów HIDL w C++, w tym szczegóły dotyczące plików generowanych automatycznie przez kompilator hidl-gen z plików .hal HIDL, sposób ich pakowania oraz sposób ich integrowania z kodem C++, który z nich korzysta.

Implementacje po stronie klienta i serwera

Interfejsy HIDL mają implementacje klienta i serwera:

  • Klientem interfejsu HIDL jest kod, który używa interfejsu, wywołując jego metody.
  • Serwer to implementacja interfejsu HIDL, który odbiera wywołania od klientów i zwraca wyniki (w razie potrzeby).

Podczas przejścia z interfejsów HAL libhardware na interfejsy HAL HIDL implementacja interfejsu HAL staje się serwerem, a proces wywołujący interfejs HAL staje się klientem. Domyślne implementacje mogą obsługiwać zarówno przepuszczanie, jak i związane HAL-e i mogą się zmieniać w czasie:

Rysunek 1. Rozwój starszych HAL.

Tworzenie klienta HAL

Zacznij od dodania bibliotek HAL do pliku makefile:

  • Marka: LOCAL_SHARED_LIBRARIES += android.hardware.nfc@1.0
  • Soong: shared_libs: [ …, android.hardware.nfc@1.0 ]

Następnie dodaj pliki nagłówka HAL:

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

// in code:
sp<IFoo> client = IFoo::getService();
client->doThing();

Tworzenie serwera HAL

Aby utworzyć implementację HAL, musisz mieć pliki .hal, które reprezentują Twój HAL, oraz wygenerowane pliki make dla tego HAL-a za pomocą narzędzia -Lmakefile lub -Landroidbp na hidl-gen (./hardware/interfaces/update-makefiles.sh wykonuje to dla wewnętrznych plików HAL i jest dobrym źródłem informacji). Podczas przenoszenia HAL-i z libhardware możesz łatwo wykonać większość tej pracy za pomocą narzędzia c2hal.

Aby utworzyć pliki potrzebne do implementacji HAL:

PACKAGE=android.hardware.nfc@1.0
LOC=hardware/interfaces/nfc/1.0/default/
m -j hidl-gen
hidl-gen -o $LOC -Lc++-impl -randroid.hardware:hardware/interfaces \
    -randroid.hidl:system/libhidl/transport $PACKAGE
hidl-gen -o $LOC -Landroidbp-impl -randroid.hardware:hardware/interfaces \
    -randroid.hidl:system/libhidl/transport $PACKAGE

Aby HAL działał w trybie przekazywania, funkcja HIDL_FETCH_IModuleName musi znajdować się w /(system|vendor|...)/lib(64)?/hw/android.hardware.package@3.0-impl(OPTIONAL_IDENTIFIER).so, gdzie OPTIONAL_IDENTIFIER to ciąg znaków identyfikujący implementację przekazywania. Wymagania dotyczące trybu przekazywania są spełnione automatycznie przez powyższe polecenia, które tworzą też obiekt docelowy android.hardware.nfc@1.0-impl, ale można użyć dowolnego rozszerzenia. Na przykład android.hardware.nfc@1.0-impl-foo używa -foo do odróżnienia się od innych.

Jeśli HAL jest wersją podrzędną lub rozszerzeniem innego HAL, nazwa binarnego pliku powinna zawierać nazwę podstawowego HAL. Na przykład implementacje android.hardware.graphics.mapper@2.1 powinny nadal znajdować się w pliku binarnym o nazwie android.hardware.graphics.mapper@2.0-impl(OPTIONAL_IDENTIFIER). Zwykle OPTIONAL_IDENTIFIER zawierałaby rzeczywistą wersję HAL. Dzięki takiej nazwie binarnej klienci 2.0 mogą pobierać plik bezpośrednio, a klienci 2.1 mogą przekształcić implementację.

Następnie wypełnij szablony funkcjami i skonfiguruj demona. Przykładowy kod demona (obsługa przekazywania):

#include <hidl/LegacySupport.h>

int main(int /* argc */, char* /* argv */ []) {
    return defaultPassthroughServiceImplementation<INfc>("nfc");
}

defaultPassthroughServiceImplementation wywołuje dlopen() dla podanej biblioteki -impl i udostępnia ją jako usługę z binderem. Przykładowy kod demona (dla usługi binderized):

int main(int /* argc */, char* /* argv */ []) {
    // This function must be called before you join to ensure the proper
    // number of threads are created. The threadpool never exceeds
    // size one because of this call.
    ::android::hardware::configureRpcThreadpool(1 /*threads*/, true /*willJoin*/);

    sp<INfc> nfc = new Nfc();
    const status_t status = nfc->registerAsService();
    if (status != ::android::OK) {
        return 1; // or handle error
    }

    // Adds this thread to the threadpool, resulting in one total
    // thread in the threadpool. We could also do other things, but
    // would have to specify 'false' to willJoin in configureRpcThreadpool.
    ::android::hardware::joinRpcThreadpool();
    return 1; // joinRpcThreadpool should never return
}

Ten demon zwykle znajduje się w $PACKAGE + "-service-suffix" (na przykład android.hardware.nfc@1.0-service), ale może być gdziekolwiek. Atrybut hal_<module> (np. hal_nfc)) to sepolicy dla określonej klasy HAL. Ten atrybut musi być stosowany do demona, który obsługuje określony interfejs HAL (jeśli ten sam proces obsługuje wiele interfejsów HAL, można zastosować do niego wiele atrybutów).