Interfejsy i pakiety

HIDL opiera się na interfejsach, czyli abstrakcyjnych typach używanych w językach obiektowych do definiowania zachowań. Każdy interfejs należy do pakietu.

Pakiety

Nazwy pakietów mogą mieć poziomy podrzędne, np. package.subpackage. Katalog główny opublikowanych pakietów HIDL to hardware/interfaceslub vendor/vendorName (na przykład vendor/google w przypadku urządzeń Pixel). Nazwa pakietu tworzy co najmniej 1 podkatalog w katalogu głównym. Wszystkie pliki definiujące pakiet znajdują się w tym samym katalogu. Na przykład pole package android.hardware.example.extension.light@2.0 może się znajdować w polu hardware/interfaces/example/extension/light/2.0.

W tabeli poniżej znajdziesz prefiksy i lokalizacje pakietów:

Prefiks pakietu Lokalizacja Typy interfejsów
android.hardware.* hardware/interfaces/* HAL
android.frameworks.* frameworks/hardware/interfaces/* frameworks/ related
android.system.* system/hardware/interfaces/* system/ related
android.hidl.* system/libhidl/transport/* core

Katalog pakietu zawiera pliki z rozszerzeniem .hal. Każdy plik musi zawierać instrukcję package, która określa pakiet i wersję, do której należy plik. Plik types.hal, jeśli występuje, nie definiuje interfejsu, ale zamiast tego definiuje typy danych dostępne dla każdego interfejsu w pakiecie.

Definicja interfejsu

Poza plikiem types.hal każdy inny plik .hal definiuje interfejs. Interfejs jest zwykle definiowany w ten sposób:

interface IBar extends IFoo { // IFoo is another interface
    // embedded types
    struct MyStruct {/*...*/};

    // interface methods
    create(int32_t id) generates (MyStruct s);
    close();
};

Interfejs bez wyraźnej deklaracji extends rozszerza się niejawnie z poziomu android.hidl.base@1.0::IBase (podobnie jak java.lang.Object w języku Java). Interfejs IBase, zaimportowany w sposób domyślny, deklaruje kilka zastrzeżonych metod, których nie należy ani nie można ponownie zadeklarować w interfejsach zdefiniowanych przez użytkownika ani używać w inny sposób. Te metody to:

  • ping
  • interfaceChain
  • interfaceDescriptor
  • notifySyspropsChanged
  • linkToDeath
  • unlinkToDeath
  • setHALInstrumentation
  • getDebugInfo
  • debug
  • getHashChain

Proces importowania

Wyrażenie import to mechanizm HIDL umożliwiający dostęp do interfejsów pakietu i typów w innym pakiecie. Operator import dotyczy 2 elementów:

  • Importowany element, który może być pakietem lub interfejsem.
  • Zaimportowana entuz, która może być pakietem lub interfejsem.

Identyfikator importującej jednostki jest określany przez lokalizację instrukcji import. Jeśli instrukcja znajduje się w poziomie types.hal pakietu, importowane dane są widoczne dla całego pakietu. Jest to import na poziomie pakietu. Jeśli instrukcja znajduje się w pliku interfejsu, importowana jednostka to sam interfejs. Jest to import na poziomie interfejsu.

Zaimportowany typ danych jest określany przez wartość po słowie kluczowym import. Wartość nie musi być pełną nazwą. Jeśli komponent jest pominięty, zostanie automatycznie wypełniony informacjami z bieżącego pakietu. W przypadku w pełni kwalifikowanych wartości obsługiwane są następujące przypadki importu:

  • Importowanie całego pakietu. Jeśli wartość to nazwa pakietu i wersja (składnia opisana poniżej), do importującej jednostki zostanie zaimportowany cały pakiet.
  • Częściowe importowanie. Jeśli wartość to:
    • Interfejs, types.hal pakietu i ten interfejs są importowane do importowanej jednostki.
    • Jeśli zdefiniowany w sekcji types.hal typ danych UDT jest importowany, to do podmiotu importującego importowany jest tylko ten typ danych UDT (inne typy z sekcji types.hal nie są importowane).
  • Importy tylko typów. Jeśli wartość używa składni importu częściowego opisanej powyżej, ale z kluczem słownym types zamiast nazwy interfejsu, importowane są tylko typy niestandardowe w types.hal wskazanego pakietu.

Podmiot importujący uzyskuje dostęp do następujących elementów:

  • wspólne typy danych UDT zaimportowanego pakietu zdefiniowane w pliku types.hal;
  • interfejsy importowanego pakietu (w przypadku importu całego pakietu) lub określony interfejs (w przypadku importu częściowego) na potrzeby ich wywoływania, przekazywania im uchwytów lub dziedziczenia po nich.

W oświadczeniu importu używa się składni pełnej nazwy typu, aby podać nazwę i wersję importowanego pakietu lub interfejsu:

import android.hardware.nfc@1.0;            // import a whole package
import android.hardware.example@1.0::IQuux; // import an interface and types.hal
import android.hardware.example@1.0::types; // import just types.hal

Dziedziczenie interfejsu

Interfejs może być rozszerzeniem wcześniej zdefiniowanego interfejsu. Rozszerzenia mogą być jednego z 3 rodzajów:

  • Interfejs może dodać funkcje do innego interfejsu, zachowując jego interfejs API.
  • Pakiet może dodać funkcje do innego pakietu, włączając jego interfejs API bez zmian.
  • Interfejs może importować typy z pakietu lub z konkretnego interfejsu.

Interfejs może rozszerzać tylko jeden inny interfejs (nie można dziedziczyć wielokrotnie). Każdy interfejs w pakiecie o numerze wersji podrzędnej innym niż 0 musi rozszerzać interfejs w poprzedniej wersji pakietu. Jeśli na przykład interfejs IBar w wersji 4.0 pakietu derivative opiera się (rozszerza) na interfejsie IFoo w wersji 1.2 pakietu original, a została utworzona wersja 1.3 pakietu original, interfejs IBar w wersji 4.1 nie może rozszerzać wersji 1.3 interfejsu IFoo. Zamiast tego wersja 4.1 rozszerzenia IBar musi być rozszerzeniem wersji 4.0 tego samego rozszerzenia, która jest powiązana z wersją 1.2 rozszerzenia IFoo.IBar IBar w wersji 5.0 może rozszerzać IFoo w wersji 1.3 (jeśli to konieczne).

Rozszerzenia interfejsu nie oznaczają zależności od biblioteki ani uwzględnienia w generowanym kodzie interfejsów HAL. Po prostu importują one strukturę danych i definicje metod na poziomie HIDL. Każda metoda w interfejsie HAL musi być w nim zaimplementowana.

Rozszerzenia dotyczące dostawców

W niektórych przypadkach rozszerzenia dostawcy są implementowane jako podklasa obiektu podstawowego, który reprezentuje interfejs podstawowy, który rozszerzają. Ten sam obiekt jest zarejestrowany pod nazwą i wersją podstawowego interfejsu HAL oraz pod nazwą i wersją interfejsu HAL rozszerzenia (dostawcy).

Obsługa wersji

Pakiety mają wersje, a interfejsy mają wersje swoich pakietów. Wersje są wyrażane za pomocą 2 liczb całkowitych: głównejmniejszej.

  • Główne wersje nie są zgodne wstecznie. Zwiększenie numeru wersji głównej powoduje zresetowanie numeru wersji podrzędnej do 0.
  • Wer. podrzędne są zgodne wstecznie. Zwiększenie numeru wersji podrzędnej oznacza, że nowsza wersja jest w pełni zgodna ze starszą wersją. Można dodawać nowe struktury danych i metody, ale nie można zmieniać istniejących struktur danych ani podpisów metod.

Na urządzeniu może być jednocześnie kilka wersji głównej lub podrzędnej HAL. Należy jednak preferować wersję podrzędną nad wersją główną, ponieważ kod klienta, który działa z poprzednią wersją podrzędną interfejsu, działa również z późniejszymi wersjami podrzędnymi tego samego interfejsu. Więcej informacji o wersjach i rozszerzeniach dostawców znajdziesz w artykule Zarządzanie wersjami w HIDL.

Podsumowanie układu interfejsu

W tej sekcji znajdziesz podsumowanie sposobów zarządzania pakietem interfejsu HIDL (np. hardware/interfaces) oraz informacje zawarte w sekcji HIDL. Zanim zaczniesz czytać, zapoznaj się z koncepcjami wersjonowania HIDL, zaszyfrowania za pomocą hidl-gen, pracy z HIDL oraz z tymi definicjami:

Termin Definicja
interfejs binarny aplikacji (ABI); interfejs programowania aplikacji oraz wszystkie wymagane połączenia binarne;
pełna i jednoznaczna nazwa (fqName) Nazwa służąca do rozróżniania różnych typów HIDL. Przykład: android.hardware.foo@1.0::IFoo.
paczka Pakiet zawierający interfejs HIDL i typy. Przykład: android.hardware.foo@1.0.
katalog główny pakietu, Pakiet główny zawierający interfejsy HIDL. Przykład: interfejs HIDL android.hardware znajduje się w korzenia package android.hardware.foo@1.0.
ścieżka główna pakietu, Lokalizacja w drzewie kodu źródłowego Androida, do której jest mapowany katalog główny pakietu.

Więcej definicji znajdziesz w terminologii HIDL.

Każdy plik można znaleźć w mapowaniu katalogu głównego pakietu i jego pełnej nazwie.

Korzenie pakietu są przekazywane do funkcji hidl-gen jako argument -r android.hardware:hardware/interfaces. Jeśli np. pakiet to vendor.awesome.foo@1.0::IFoo, a wysłano hidl-gen-r vendor.awesome:some/device/independent/path/interfaces, plik interfejsu powinien znajdować się w folderze $ANDROID_BUILD_TOP/some/device/independent/path/interfaces/foo/1.0/IFoo.hal.

W praktyce zaleca się, aby dostawca lub OEM o nazwie awesome umieszczał swoje standardowe interfejsy w folderze vendor.awesome. Po wybraniu ścieżki pakietu nie można jej zmienić, ponieważ jest ona wbudowana w interfejsie ABI.

Mapowanie ścieżki pakietu powinno być unikalne

Jeśli na przykład masz -rsome.package:$PATH_A-rsome.package:$PATH_B, $PATH_A musi być równe $PATH_B, aby zachować spójność katalogu interfejsu (ułatwia to też wersjonowanie interfejsów).

Katalog główny pakietu musi zawierać plik wersji

Jeśli utworzysz ścieżkę pakietu, np. -r vendor.awesome:vendor/awesome/interfaces, musisz też utworzyć plik $ANDROID_BUILD_TOP/vendor/awesome/interfaces/current.txt, który powinien zawierać hasze interfejsów utworzone za pomocą opcji -Lhashhidl-gen (szczegółowe informacje na ten temat znajdziesz w artykule Hashowanie za pomocą hidl-gen).

Interfejsy znajdują się w miejscach niezależnych od urządzenia.

W praktyce zalecamy udostępnianie interfejsów między oddziałami. Umożliwia to maksymalne ponowne wykorzystanie kodu i maksymalne przetestowanie kodu na różnych urządzeniach i w różnych zastosowaniach.