HIDL wymaga, aby każdy interfejs napisany w języku HIDL był wersjonowany. Po opublikowaniu interfejsu HAL jest on blokowany i wszelkie dalsze zmiany należy wprowadzić w nowej wersji tego interfejsu. Choć danego opublikowanego interfejsu nie można modyfikować, można go rozbudować o inny interfejs.
Struktura kodu HIDL
Kod HIDL jest zorganizowany w typy, interfejsy i pakiety zdefiniowane przez użytkownika:
- Typy zdefiniowane przez użytkownika (UDT) . HIDL zapewnia dostęp do zestawu prymitywnych typów danych, których można używać do tworzenia bardziej złożonych typów za pomocą struktur, unii i wyliczeń. UDT są przekazywane do metod interfejsów i mogą być definiowane na poziomie pakietu (wspólnym dla wszystkich interfejsów) lub lokalnie w interfejsie.
- Interfejsy . Jako podstawowy element składowy HIDL, interfejs składa się z deklaracji UDT i metod. Interfejsy mogą również dziedziczyć z innego interfejsu.
- Pakiety . Organizuje powiązane interfejsy HIDL i typy danych, na których działają. Pakiet jest identyfikowany przez nazwę i wersję i zawiera następujące elementy:
- Plik definicji typu danych o nazwie
types.hal
. - Zero lub więcej interfejsów, każdy w osobnym pliku
.hal
.
- Plik definicji typu danych o nazwie
Plik definicji typu danych types.hal
zawiera tylko UDT (wszystkie UDT na poziomie pakietu są przechowywane w jednym pliku). Reprezentacje w języku docelowym są dostępne dla wszystkich interfejsów w pakiecie.
Filozofia wersjonowania
Pakiet HIDL (taki jak android.hardware.nfc
), po opublikowaniu dla danej wersji (np. 1.0
), jest niezmienny; nie da się tego zmienić. Modyfikacje interfejsów w pakiecie lub jakiekolwiek zmiany w jego UDT mogą nastąpić tylko w innym pakiecie.
W HIDL wersjonowanie ma zastosowanie na poziomie pakietu, a nie na poziomie interfejsu, a wszystkie interfejsy i UDT w pakiecie mają tę samą wersję. Wersje pakietów są zgodne z wersjami semantycznymi bez poziomu poprawki i komponentów metadanych kompilacji. W obrębie danego pakietu pomniejsza zmiana wersji oznacza, że nowa wersja pakietu jest wstecznie kompatybilna ze starym pakietem, a większa zmiana wersji oznacza, że nowa wersja pakietu nie jest wstecznie kompatybilna ze starym pakietem.
Koncepcyjnie pakiet może być powiązany z innym pakietem na kilka sposobów:
- Zupełnie nie .
- Rozszerzalność wsteczna na poziomie pakietu . Dzieje się tak w przypadku nowych aktualizacji mniejszych wersji (następnej zwiększonej wersji) pakietu; nowy pakiet ma tę samą nazwę i wersję główną co stary pakiet, ale jest wyższą wersją pomocniczą. Funkcjonalnie nowy pakiet jest rozszerzeniem starego pakietu, co oznacza:
- Interfejsy najwyższego poziomu pakietu nadrzędnego są obecne w nowym pakiecie, chociaż interfejsy mogą mieć nowe metody, nowe UDT lokalne dla interfejsu (rozszerzenie poziomu interfejsu opisane poniżej) i nowe UDT w
types.hal
. - Do nowego pakietu mogą zostać dodane także nowe interfejsy.
- Wszystkie typy danych pakietu nadrzędnego są obecne w nowym pakiecie i mogą być obsługiwane przez (ewentualnie ponownie zaimplementowane) metody ze starego pakietu.
- Można także dodać nowe typy danych do wykorzystania w nowych metodach udoskonalonych istniejących interfejsów lub w nowych interfejsach.
- Interfejsy najwyższego poziomu pakietu nadrzędnego są obecne w nowym pakiecie, chociaż interfejsy mogą mieć nowe metody, nowe UDT lokalne dla interfejsu (rozszerzenie poziomu interfejsu opisane poniżej) i nowe UDT w
- Rozszerzalność wstecznie kompatybilna na poziomie interfejsu . Nowy pakiet może także rozszerzyć pakiet oryginalny, składając się z logicznie oddzielnych interfejsów, które po prostu zapewniają dodatkową funkcjonalność, a nie podstawową. W tym celu pożądane mogą być:
- Interfejsy w nowym pakiecie muszą korzystać z typów danych starego pakietu.
- Interfejsy w nowym pakiecie mogą rozszerzać interfejsy jednego lub większej liczby starych pakietów.
- Rozszerz oryginalną niezgodność wsteczną . Jest to główna wersja pakietu i nie musi być między nimi żadnej korelacji. Jeśli istnieje, można to wyrazić za pomocą kombinacji typów ze starszej wersji pakietu i dziedziczenia podzbioru interfejsów ze starego pakietu.
Strukturyzacja interfejsów
W przypadku dobrze zorganizowanego interfejsu dodanie nowych typów funkcjonalności, które nie są częścią oryginalnego projektu, powinno wymagać modyfikacji interfejsu HIDL. I odwrotnie, jeśli możesz lub spodziewasz się wprowadzenia zmian po obu stronach interfejsu, wprowadzających nową funkcjonalność bez zmiany samego interfejsu, wówczas interfejs nie jest ustrukturyzowany.
Treble obsługuje osobno skompilowane komponenty dostawcy i systemu, w których vendor.img
na urządzeniu i plik system.img
można skompilować oddzielnie. Wszystkie interakcje pomiędzy vendor.img
i system.img
muszą być wyraźnie i dokładnie zdefiniowane, aby mogły działać przez wiele lat. Obejmuje to wiele powierzchni API, ale główną powierzchnią jest mechanizm IPC używany przez HIDL do komunikacji międzyprocesowej na granicy system.img
/ vendor.img
.
Wymagania
Wszystkie dane przesyłane przez HIDL muszą być jawnie zdefiniowane. Aby zapewnić, że wdrożenie i klient będą mogli nadal współpracować, nawet jeśli zostaną skompilowane osobno lub opracowane niezależnie, dane muszą spełniać następujące wymagania:
- Można go opisać bezpośrednio w języku HIDL (za pomocą wyliczeń struktur itp.) Z nazwami semantycznymi i znaczeniem.
- Można opisać za pomocą normy publicznej, takiej jak ISO/IEC 7816.
- Można opisać za pomocą standardu sprzętowego lub fizycznego układu sprzętu.
- W razie potrzeby mogą to być nieprzezroczyste dane (takie jak klucze publiczne, identyfikatory itp.).
Jeśli używane są nieprzezroczyste dane, należy je odczytać tylko z jednej strony interfejsu HIDL. Na przykład, jeśli kod vendor.img
przekazuje komponentowi w pliku system.img
komunikat w postaci ciągu znaków lub dane vec<uint8_t>
, dane te nie mogą zostać przeanalizowane przez sam system.img
; można go jedynie przekazać z powrotem do vendor.img
w celu interpretacji. Podczas przekazywania wartości z vendor.img
do kodu dostawcy w system.img
lub do innego urządzenia, format danych i sposób ich interpretacji muszą być dokładnie opisane i nadal stanowią część interfejsu .
Wytyczne
Powinieneś być w stanie napisać implementację lub klienta HAL, używając tylko plików .hal (tzn. nie powinieneś szukać źródła Androida ani publicznych standardów). Zalecamy określenie dokładnego wymaganego zachowania. Stwierdzenia takie jak „wdrożenie może zrobić A lub B” zachęcają, aby wdrożenia połączyły się z klientami, z którymi są opracowywane.
Układ kodu HIDL
HIDL obejmuje pakiety podstawowe i dostawcy.
Podstawowe interfejsy HIDL to te określone przez Google. Pakiety, do których należą, zaczynają się od android.hardware.
i są nazywane według podsystemu, potencjalnie z zagnieżdżonymi poziomami nazewnictwa. Na przykład pakiet NFC ma nazwę android.hardware.nfc
, a pakiet aparatu to android.hardware.camera
. Ogólnie rzecz biorąc, pakiet podstawowy ma nazwę android.hardware.
[ name1
].[ name2
]…. Pakiety HIDL oprócz nazwy mają wersję. Na przykład pakiet android.hardware.camera
może być w wersji 3.4
; jest to ważne, ponieważ wersja pakietu wpływa na jego umiejscowienie w drzewie źródłowym.
Wszystkie pakiety podstawowe są umieszczane w hardware/interfaces/
w systemie kompilacji. Pakiet android.hardware.
[ name1
].[ name2
]… w wersji $m.$n
znajduje się w katalogu hardware/interfaces/name1/name2/
… /$m.$n/
; pakiet android.hardware.camera
wersja 3.4
znajduje się w katalogu hardware/interfaces/camera/3.4/.
Istnieje zakodowane mapowanie pomiędzy prefiksem pakietu android.hardware.
i ścieżkę hardware/interfaces/
.
Pakiety inne niż podstawowe (dostawcy) to pakiety produkowane przez dostawcę SoC lub ODM. Przedrostek pakietów innych niż podstawowe to vendor.$(VENDOR).hardware.
gdzie $(VENDOR)
odnosi się do dostawcy SoC lub OEM/ODM. Odwzorowuje to ścieżkę vendor/$(VENDOR)/interfaces
w drzewie (to mapowanie jest również zakodowane na stałe).
W pełni kwalifikowane nazwy typów zdefiniowanych przez użytkownika
W języku HIDL każdy UDT ma w pełni kwalifikowaną nazwę, która składa się z nazwy UDT, nazwy pakietu, w którym zdefiniowano UDT, oraz wersji pakietu. W pełni kwalifikowana nazwa jest używana tylko wtedy, gdy deklarowane są instancje typu, a nie wtedy, gdy zdefiniowany jest sam typ. Załóżmy na przykład, że pakiet android.hardware.nfc,
w wersji 1.0
definiuje strukturę o nazwie NfcData
. Na stronie deklaracji (czy to w types.hal
, czy w deklaracji interfejsu) deklaracja po prostu stwierdza:
struct NfcData { vec<uint8_t> data; };
Deklarując instancję tego typu (czy to w strukturze danych, czy jako parametr metody), użyj w pełni kwalifikowanej nazwy typu:
android.hardware.nfc@1.0::NfcData
Ogólna składnia to PACKAGE @ VERSION :: UDT
, gdzie:
-
PACKAGE
to oddzielona kropkami nazwa pakietu HIDL (np.android.hardware.nfc
). -
VERSION
jest oddzielonym kropkami formatem wersji głównej.podrzędnej pakietu (np.1.0
). -
UDT
to oddzielona kropkami nazwa UDT HIDL. Ponieważ HIDL obsługuje zagnieżdżone UDT, a interfejsy HIDL mogą zawierać UDT (rodzaj zagnieżdżonej deklaracji), w celu uzyskania dostępu do nazw używane są kropki.
Na przykład, jeśli w pliku typów wspólnych w pakiecie android.hardware.example
wersja 1.0
zdefiniowano następującą deklarację zagnieżdżoną:
// types.hal package android.hardware.example@1.0; struct Foo { struct Bar { // … }; Bar cheers; };
W pełni kwalifikowana nazwa Bar
to android.hardware.example@1.0::Foo.Bar
. Jeśli oprócz tego, że znajdowała się w powyższym pakiecie, zagnieżdżona deklaracja znajdowała się w interfejsie o nazwie IQuux
:
// IQuux.hal package android.hardware.example@1.0; interface IQuux { struct Foo { struct Bar { // … }; Bar cheers; }; doSomething(Foo f) generates (Foo.Bar fb); };
W pełni kwalifikowana nazwa Bar
to android.hardware.example@1.0::IQuux.Foo.Bar
.
W obu przypadkach Bar
można nazwać Bar
jedynie w zakresie deklaracji Foo
. Na poziomie pakietu lub interfejsu musisz odwołać się do Bar
poprzez Foo
: Foo.Bar
, jak w deklaracji metody doSomething
powyżej. Alternatywnie możesz zadeklarować metodę bardziej szczegółowo jako:
// IQuux.hal doSomething(android.hardware.example@1.0::IQuux.Foo f) generates (android.hardware.example@1.0::IQuux.Foo.Bar fb);
W pełni kwalifikowane wartości wyliczenia
Jeśli UDT jest typem wyliczeniowym, każda wartość typu wyliczeniowego ma w pełni kwalifikowaną nazwę rozpoczynającą się od w pełni kwalifikowanej nazwy typu wyliczeniowego, po której następuje dwukropek, a następnie nazwa wartości wyliczeniowej. Załóżmy na przykład, że pakiet android.hardware.nfc,
w wersji 1.0
definiuje typ wyliczeniowy NfcStatus
:
enum NfcStatus { STATUS_OK, STATUS_FAILED };
Odnosząc się do STATUS_OK
, w pełni kwalifikowana nazwa to:
android.hardware.nfc@1.0::NfcStatus:STATUS_OK
Ogólna składnia to PACKAGE @ VERSION :: UDT : VALUE
, gdzie:
-
PACKAGE @ VERSION :: UDT
to dokładnie ta sama, w pełni kwalifikowana nazwa typu wyliczeniowego. -
VALUE
to nazwa wartości.
Reguły autownioskowania
Nie trzeba podawać pełnej nazwy UDT. Nazwa UDT może bezpiecznie pominąć następujące elementy:
- Pakiet, np.
@1.0::IFoo.Type
- Zarówno pakiet, jak i wersja, np.
IFoo.Type
HIDL próbuje uzupełnić nazwę, korzystając z reguł automatycznej interferencji (niższy numer reguły oznacza wyższy priorytet).
Zasada nr 1
Jeśli nie podano żadnego pakietu ani wersji, zostanie podjęta próba wyszukiwania nazw lokalnych. Przykład:
interface Nfc { typedef string NfcErrorMessage; send(NfcData d) generates (@1.0::NfcStatus s, NfcErrorMessage m); };
NfcErrorMessage
jest wyszukiwany lokalnie i znajdowany jest znajdujący się powyżej typedef
. NfcData
jest również wyszukiwana lokalnie, ale ponieważ nie jest zdefiniowana lokalnie, stosowane są reguły 2 i 3. @1.0::NfcStatus
zapewnia wersję, więc zasada 1 nie ma zastosowania.
Zasada 2
Jeśli reguła 1 zawiedzie i brakuje komponentu w pełni kwalifikowanej nazwy (pakietu, wersji lub pakietu i wersji), komponent zostanie automatycznie uzupełniony informacjami z bieżącego pakietu. Kompilator HIDL następnie przegląda bieżący plik (i wszystkie importy), aby znaleźć automatycznie uzupełnioną, w pełni kwalifikowaną nazwę. Korzystając z powyższego przykładu, załóżmy, że deklaracja ExtendedNfcData
została dokonana w tym samym pakiecie ( android.hardware.nfc
) w tej samej wersji ( 1.0
) co NfcData
w następujący sposób:
struct ExtendedNfcData { NfcData base; // … additional members };
Kompilator HIDL wypełnia nazwę pakietu i nazwę wersji z bieżącego pakietu, aby wygenerować w pełni kwalifikowaną nazwę UDT android.hardware.nfc@1.0::NfcData
. Ponieważ nazwa istnieje w bieżącym pakiecie (zakładając, że została poprawnie zaimportowana), zostanie użyta w deklaracji.
Nazwa w bieżącym pakiecie jest importowana tylko wtedy, gdy spełniony jest jeden z poniższych warunków:
- Jest importowany jawnie za pomocą instrukcji
import
. - Jest zdefiniowany w
types.hal
w bieżącym pakiecie
Ten sam proces jest wykonywany, jeśli NfcData
zostało zakwalifikowane jedynie na podstawie numeru wersji:
struct ExtendedNfcData { // autofill the current package name (android.hardware.nfc) @1.0::NfcData base; // … additional members };
Zasada 3
Jeśli reguła 2 nie znajdzie dopasowania (w bieżącym pakiecie nie zdefiniowano UDT), kompilator HIDL szuka dopasowania we wszystkich zaimportowanych pakietach. Korzystając z powyższego przykładu, załóżmy, że ExtendedNfcData
jest zadeklarowany w wersji 1.1
pakietu android.hardware.nfc
, 1.1
importuje 1.0
tak jak powinno (zobacz Rozszerzenia na poziomie pakietu ), a definicja określa tylko nazwę UDT:
struct ExtendedNfcData { NfcData base; // … additional members };
Kompilator szuka dowolnego UDT o nazwie NfcData
i znajduje go w android.hardware.nfc
w wersji 1.0
, co skutkuje w pełni kwalifikowanym UDT o android.hardware.nfc@1.0::NfcData
. Jeśli dla danego częściowo kwalifikowanego UDT zostanie znalezione więcej niż jedno dopasowanie, kompilator HIDL zgłosi błąd.
Przykład
Stosując regułę 2, importowany typ zdefiniowany w bieżącym pakiecie jest preferowany w stosunku do typu importowanego z innego pakietu:
// hardware/interfaces/foo/1.0/types.hal package android.hardware.foo@1.0; struct S {}; // hardware/interfaces/foo/1.0/IFooCallback.hal package android.hardware.foo@1.0; interface IFooCallback {}; // hardware/interfaces/bar/1.0/types.hal package android.hardware.bar@1.0; typedef string S; // hardware/interfaces/bar/1.0/IFooCallback.hal package android.hardware.bar@1.0; interface IFooCallback {}; // hardware/interfaces/bar/1.0/IBar.hal package android.hardware.bar@1.0; import android.hardware.foo@1.0; interface IBar { baz1(S s); // android.hardware.bar@1.0::S baz2(IFooCallback s); // android.hardware.foo@1.0::IFooCallback };
-
S
jest interpolowany jakoandroid.hardware.bar@1.0::S
i znajduje się wbar/1.0/types.hal
(ponieważtypes.hal
jest importowany automatycznie). -
IFooCallback
jest interpolowany jakoandroid.hardware.bar@1.0::IFooCallback
przy użyciu reguły 2, ale nie można go znaleźć, ponieważbar/1.0/IFooCallback.hal
nie jest importowany automatycznie (tak jak jest totypes.hal
). Zatem reguła 3 rozwiązuje go zamiast tego doandroid.hardware.foo@1.0::IFooCallback
, który jest importowany poprzezimport android.hardware.foo@1.0;
).
typy.hal
Każdy pakiet HIDL zawiera plik types.hal
zawierający UDT, które są współdzielone pomiędzy wszystkimi interfejsami uczestniczącymi w tym pakiecie. Typy HIDL są zawsze publiczne; niezależnie od tego, czy UDT jest zadeklarowany w types.hal
, czy w deklaracji interfejsu, typy te są dostępne poza zakresem, w którym są zdefiniowane. types.hal
nie ma na celu opisywania publicznego API pakietu, ale raczej hostuje UDT używane przez wszystkie interfejsy w pakiecie. Ze względu na naturę HIDL wszystkie UDT są częścią interfejsu.
types.hal
składa się z UDT i instrukcji import
. Ponieważ types.hal
jest dostępny dla każdego interfejsu pakietu (jest to import niejawny), te instrukcje import
z definicji dotyczą poziomu pakietu. UDT w types.hal
mogą również zawierać zaimportowane w ten sposób UDT i interfejsy.
Na przykład dla IFoo.hal
:
package android.hardware.foo@1.0; // whole package import import android.hardware.bar@1.0; // types only import import android.hardware.baz@1.0::types; // partial imports import android.hardware.qux@1.0::IQux.Quux; // partial imports import android.hardware.quuz@1.0::Quuz;
Importowane są:
-
android.hidl.base@1.0::IBase
(domyślnie) -
android.hardware.foo@1.0::types
(domyślnie) - Wszystko w
android.hardware.bar@1.0
(w tym wszystkie interfejsy i ichtypes.hal
) -
types.hal
zandroid.hardware.baz@1.0::types
(interfejsy wandroid.hardware.baz@1.0
nie są importowane) -
IQux.hal
itypes.hal
zandroid.hardware.qux@1.0
-
Quuz
zandroid.hardware.quuz@1.0
(zakładając, żeQuuz
jest zdefiniowany wtypes.hal
, analizowany jest cały pliktypes.hal
, ale typy inne niżQuuz
nie są importowane).
Wersjonowanie na poziomie interfejsu
Każdy interfejs w pakiecie znajduje się w swoim własnym pliku. Pakiet, do którego należy interfejs, jest deklarowany na górze interfejsu za pomocą instrukcji package
. Po deklaracji pakietu może zostać wyszczególniony zero lub więcej importów na poziomie interfejsu (częściowych lub całych pakietów). Na przykład:
package android.hardware.nfc@1.0;
W języku HIDL interfejsy mogą dziedziczyć z innych interfejsów za pomocą słowa kluczowego extends
. Aby interfejs mógł rozszerzać inny interfejs, musi mieć do niego dostęp poprzez instrukcję import
. Nazwa rozszerzanego interfejsu (interfejs podstawowy) jest zgodna z opisanymi powyżej zasadami kwalifikacji nazwy typu. Interfejs może dziedziczyć tylko z jednego interfejsu; HIDL nie obsługuje dziedziczenia wielokrotnego.
Poniższe przykłady wersji uprev korzystają z następującego pakietu:
// types.hal package android.hardware.example@1.0 struct Foo { struct Bar { vec<uint32_t> val; }; }; // IQuux.hal package android.hardware.example@1.0 interface IQuux { fromFooToBar(Foo f) generates (Foo.Bar b); }
Zasady uprew
Aby zdefiniować pakiet package@major.minor
, albo A, albo wszystkie B muszą być prawdziwe:
Zasada A | „To początkowa wersja dodatkowa”: Nie można zdefiniować wszystkich poprzednich wersji pomocniczych, package@major.0 , package@major.1 , …, package@major.(minor-1) . |
---|
Zasada B | Wszystkie poniższe stwierdzenia są prawdziwe:
|
---|
Ze względu na zasadę A:
- Pakiet może zaczynać się od dowolnego mniejszego numeru wersji (na przykład
android.hardware.biometrics.fingerprint
zaczyna się od@2.1
). - Wymóg „
android.hardware.foo@1.0
nie jest zdefiniowany” oznacza, że kataloghardware/interfaces/foo/1.0
w ogóle nie powinien istnieć.
Jednak reguła A nie ma wpływu na pakiet o tej samej nazwie pakietu, ale w innej wersji głównej (na przykład android.hardware.camera.device
ma zdefiniowane zarówno @1.0
, jak i @3.2
; @3.2
nie musi wchodzić w interakcję z @1.0
.) Zatem @3.2::IExtFoo
może rozszerzać @1.0::IFoo
.
Pod warunkiem, że nazwa pakietu jest inna, package@major.minor::IBar
może rozszerzać się z interfejsu o innej nazwie (na przykład android.hardware.bar@1.0::IBar
może rozszerzać android.hardware.baz@2.2::IBaz
). Jeśli interfejs nie zadeklaruje jawnie nadtypu za pomocą słowa kluczowego extend
, rozszerzy android.hidl.base@1.0::IBase
(z wyjątkiem samego IBase
).
Należy jednocześnie przestrzegać B.2 i B.3. Na przykład, nawet jeśli android.hardware.foo@1.1::IFoo
rozszerza android.hardware.foo@1.0::IFoo
, aby przekazać regułę B.2, jeśli android.hardware.foo@1.1::IExtBar
rozszerza android.hardware.foo@1.0::IBar
, to nadal nie jest poprawna wersja.
Udoskonalanie interfejsów
Aby zaktualizować android.hardware.example@1.0
(zdefiniowany powyżej) do @1.1
:
// types.hal package android.hardware.example@1.1; import android.hardware.example@1.0; // IQuux.hal package android.hardware.example@1.1 interface IQuux extends @1.0::IQuux { fromBarToFoo(Foo.Bar b) generates (Foo f); }
To jest import
wersji 1.0
pliku android.hardware.example
na poziomie pakietu do types.hal
. Chociaż w wersji 1.1
pakietu nie dodano żadnych nowych UDT, odniesienia do UDT w wersji 1.0
są nadal potrzebne, stąd import na poziomie pakietu w types.hal
. (Ten sam efekt można było osiągnąć poprzez import na poziomie interfejsu w IQuux.hal
.)
W extends @1.0::IQuux
w deklaracji IQuux
określiliśmy wersję IQuux
, która jest dziedziczona (wymagane jest ujednoznacznienie, ponieważ IQuux
służy do deklarowania interfejsu i dziedziczenia z interfejsu). Ponieważ deklaracje są po prostu nazwami, które dziedziczą wszystkie atrybuty pakietu i wersji w miejscu deklaracji, ujednoznacznienie musi dotyczyć nazwy interfejsu podstawowego; moglibyśmy także skorzystać z w pełni wykwalifikowanego UDT, ale byłoby to zbędne.
Nowy interfejs IQuux
nie deklaruje ponownie metody fromFooToBar()
dziedziczy ona z @1.0::IQuux
; po prostu wyświetla listę nowej metody, którą dodaje fromBarToFoo()
. W języku HIDL odziedziczone metody nie mogą być ponownie zadeklarowane w interfejsach potomnych, więc interfejs IQuux
nie może jawnie zadeklarować metody fromFooToBar()
.
Konwencje uprev
Czasami nazwy interfejsów muszą zmieniać nazwę interfejsu rozszerzającego. Zalecamy, aby rozszerzenia, struktury i związki wyliczeniowe miały tę samą nazwę, co rozszerzenie, chyba że różnią się na tyle, że uzasadniają nową nazwę. Przykłady:
// in parent hal file enum Brightness : uint32_t { NONE, WHITE }; // in child hal file extending the existing set with additional similar values enum Brightness : @1.0::Brightness { AUTOMATIC }; // extending the existing set with values that require a new, more descriptive name: enum Color : @1.0::Brightness { HW_GREEN, RAINBOW };
Jeśli metoda może mieć nową nazwę semantyczną (na przykład fooWithLocation
), jest to preferowane. W przeciwnym razie należy go nazwać podobnie do tego, co rozszerza. Na przykład metoda foo_1_1
w @1.1::IFoo
może zastąpić funkcjonalność metody foo
w @1.0::IFoo
, jeśli nie ma lepszej alternatywnej nazwy.
Wersjonowanie na poziomie pakietu
Wersjonowanie HIDL odbywa się na poziomie pakietu; po opublikowaniu pakietu jest on niezmienny (nie można zmieniać jego zestawu interfejsów i UDT). Pakiety mogą być ze sobą powiązane na kilka sposobów, z których każdy można wyrazić poprzez kombinację dziedziczenia na poziomie interfejsu i budowania UDT poprzez kompozycję.
Jednakże jeden typ relacji jest ściśle zdefiniowany i musi być egzekwowany: dziedziczenie wstecznie kompatybilne na poziomie pakietu . W tym scenariuszu pakiet nadrzędny jest pakietem, z którego jest dziedziczony, a pakiet podrzędny jest pakietem rozszerzającym pakiet nadrzędny. Reguły dziedziczenia kompatybilne wstecz na poziomie pakietu są następujące:
- Wszystkie interfejsy najwyższego poziomu pakietu nadrzędnego są dziedziczone z interfejsów pakietu podrzędnego.
- Do nowego pakietu można także dodać nowe interfejsy (brak ograniczeń dotyczących powiązań z innymi interfejsami w innych pakietach).
- Można także dodać nowe typy danych do wykorzystania w nowych metodach udoskonalonych istniejących interfejsów lub w nowych interfejsach.
Reguły te można zaimplementować przy użyciu dziedziczenia na poziomie interfejsu HIDL i kompozycji UDT, ale wymagają wiedzy na poziomie meta, aby wiedzieć, że te relacje stanowią rozszerzenie pakietu kompatybilne wstecz. Wiedzę tę wnioskuje się w następujący sposób:
Jeśli pakiet spełnia ten wymóg, hidl-gen
wymusza zasady zgodności wstecznej.