HIDL wymaga, aby każdy interfejs napisany w HIDL był oznaczony wersją. Po opublikowaniu interfejs HAL jest zablokowany i wszystkie dalsze zmiany muszą być wprowadzane w nowej wersji tego interfejsu. Opublikowanego interfejsu nie można modyfikować, ale można go rozszerzyć o inny interfejs.
Struktura kodu HIDL
Kod HIDL jest zorganizowany w zdefiniowane przez użytkownika typy, interfejsy i pakiety:
- Typy zdefiniowane przez użytkownika (UDT). HIDL zapewnia dostęp do zestawu prostych typów danych, które można wykorzystać do tworzenia bardziej złożonych typów za pomocą struktur, zjednoczeń i wyliczeń. UDT są przekazywane do metod interfejsów i mogą być definiowane na poziomie pakietu (wspólnego dla wszystkich interfejsów) lub lokalnie dla interfejsu.
- Interfejsy. Jako podstawowy element HIDL interfejs składa się z typów niestandardowych i deklaracji metod. Interfejsy mogą też dziedziczyć po innym interfejsie.
- Pakiety. porządkuje powiązane interfejsy HIDL oraz typy danych, z którymi pracują; Pakiet jest identyfikowany przez nazwę i wersję oraz zawiera:
- Plik definicji typu danych o nazwie
types.hal
. - 0 lub więcej interfejsów, każdy w oddzielnym pliku
.hal
.
- Plik definicji typu danych o nazwie
Plik definicji typu danych types.hal
zawiera tylko typy danych niestandardowych (wszystkie typy danych niestandardowych na poziomie pakietu są przechowywane w jednym pliku). Reprezentacje w języku docelowym są dostępne we wszystkich interfejsach w pakiecie.
Filozofia obsługi wersji
Pakiet HIDL (np. android.hardware.nfc
), po opublikowaniu w przypadku danej wersji (np. 1.0
) jest niezmienny i nie można go zmienić. Modyfikacje interfejsów w pakiecie lub zmiany w jego UDT mogą być wprowadzane tylko w innym pakiecie.
W HIDL wersjonowanie jest stosowane na poziomie pakietu, a nie interfejsu. Wszystkie interfejsy i typy danych użytkownika w pakiecie mają tę samą wersję. Wersje pakietów są zgodne z semantycznym wersjonowaniem bez poziomu poprawki i elementów metadanych kompilacji. W danym pakiecie przejście na wersję mniejszą oznacza, że nowa wersja pakietu jest zgodna ze starszą wersją, a przejście na wersję większą oznacza, że nowa wersja pakietu nie jest zgodna wstecznie ze starszą wersją.
Koncepcyjnie pakiet może być powiązany z innym pakietem na kilka sposobów:
- Wcale nie.
- Zgodność wsteczna na poziomie pakietu. Dzieje się tak w przypadku nowych wersji podwyższających wersję podrzędną (następna wersja zwiększona) pakietu. Nowy pakiet ma tę samą nazwę i wersję główną co stary, ale wyższą wersję podrzędną. Pod względem funkcjonalności nowy pakiet jest superzbiorem starego pakietu, co oznacza, że:
- Nowy pakiet zawiera interfejsy najwyższego poziomu pakietu nadrzędnego, ale mogą one zawierać nowe metody, nowe typy danych użytkownika w interfejsie (rozszerzenie poziomu interfejsu opisane poniżej) i nowe typy danych użytkownika w
types.hal
. - Nowe pakiety mogą też zawierać nowe interfejsy.
- Nowy pakiet zawiera wszystkie typy danych pakietu nadrzędnego i może obsługiwać metody (opcjonalnie ponownie zaimplementowane) ze starego pakietu.
- Można też dodawać nowe typy danych, aby używać ich w ramach nowych metod uaktualniania dotychczasowych interfejsów lub w ramach nowych interfejsów.
- Nowy pakiet zawiera interfejsy najwyższego poziomu pakietu nadrzędnego, ale mogą one zawierać nowe metody, nowe typy danych użytkownika w interfejsie (rozszerzenie poziomu interfejsu opisane poniżej) i nowe typy danych użytkownika w
- Rozszerzalność na poziomie interfejsu z zachowaniem zgodności wstecznej. Nowy pakiet może też rozszerzać oryginalny pakiet, ponieważ składa się z logicznie oddzielnych interfejsów, które po prostu zapewniają dodatkowe funkcje, a nie główną.
W tym celu warto:
- Interfejsy w nowym pakiecie muszą korzystać z typów danych starego pakietu.
- Interfejsy w nowym pakiecie mogą rozszerzać interfejsy co najmniej 1 starego pakietu.
- Rozszerzyć pierwotną niekompatybilność wsteczną. Jest to nowa wersja pakietu w wersji głównej, więc nie musi być między nimi żadnego powiązania. W takim przypadku można użyć kombinacji typów ze starszej wersji pakietu i odziedziczenia podzbioru interfejsów starszych pakietów.
Struktura interfejsu
W przypadku dobrze ustrukturyzowanego interfejsu dodanie nowych typów funkcji, które nie są częścią oryginalnego projektu, nie powinno wymagać modyfikacji interfejsu HIDL. Jeśli natomiast możesz lub zamierzasz wprowadzić zmiany po obu stronach interfejsu, które wprowadzają nowe funkcje bez zmiany samego interfejsu, oznacza to, że interfejs nie jest uporządkowany.
Treble obsługuje oddzielnie skompilowane komponenty dostawcy i systemu, w których vendor.img
na urządzeniu i system.img
mogą być kompilowane oddzielnie. Wszystkie interakcje między vendor.img
a system.img
muszą być wyraźnie i dokładnie zdefiniowane, aby mogły działać przez wiele lat. Obejmuje to wiele interfejsów API, ale głównym interfejsem jest mechanizm IPC używany przez HIDL do komunikacji między procesami na granicy system.img
/vendor.img
.
Wymagania
Wszystkie dane przekazywane przez HIDL muszą być zdefiniowane w sposób jednoznaczny. Aby zapewnić, że implementacja i klient będą mogły nadal współpracować nawet wtedy, gdy są kompilowane osobno lub rozwijane niezależnie, dane muszą spełniać te wymagania:
- Może być opisany bezpośrednio w HIDL (za pomocą struktur, typów itp.) za pomocą semantycznych nazw i znaczeni.
- może być opisany za pomocą standardu publicznego, takiego jak ISO/IEC 7816;
- Może być opisany za pomocą standardu sprzętowego lub fizycznej konfiguracji sprzętu.
- W razie potrzeby mogą to być dane nieprzejrzyste (np. klucze publiczne, identyfikatory itp.).
Jeśli używane są dane nieprzezroczyste, muszą one być odczytywane tylko przez jedną stronę interfejsu HIDL. Jeśli na przykład kod vendor.img
przekazuje komponentowi system.img
ciąg znaków lub dane vec<uint8_t>
, system.img
nie może go przeanalizować. Może je przekazać tylko do interpretacji komponentowi vendor.img
. Podczas przekazywania wartości z vendor.img
do kodu dostawcy na urządzeniu system.img
lub na inne urządzenie należy dokładnie opisać format danych i sposób ich interpretacji. Dane te nadal stanowią część interfejsu.
Wskazówki
Implementacja lub klient HAL powinien być tworzony tylko z wykorzystaniem plików .hal (czyli nie trzeba sprawdzać kodu źródłowego Androida ani publicznych standardów). Zalecamy określenie dokładnego wymaganego zachowania. Sformułowania takie jak „implementacja może wykonać A lub B” zachęcają do tego, aby implementacje były powiązane z klientami, dla których są tworzone.
Układ kodu HIDL
HIDL obejmuje pakiety podstawowe i pakiety dostawców.
Interfejsy podstawowe HIDL to te określone przez Google. Nazwa pakietu, do którego należą, zaczyna się od android.hardware.
i jest nazwana według nazwy podsystemu. Może mieć wbudowane poziomy nazewnictwa. Na przykład pakiet NFC ma nazwę android.hardware.nfc
, a pakiet aparatu – android.hardware.camera
. Zasadniczo pakiet podstawowy ma nazwę android.hardware.
[name1
].[name2
]…. Pakiety HIDL mają oprócz nazwy również wersję. Na przykład pakiet android.hardware.camera
może mieć wersję 3.4
. Jest to ważne, ponieważ wersja pakietu wpływa na jego położenie w drzewie źródłowym.
Wszystkie podstawowe pakiety są umieszczane w systemie kompilacji w folderze hardware/interfaces/
. Pakiet android.hardware.
[name1
].[name2
]… w wersji $m.$n
znajduje się w katalogu hardware/interfaces/name1/name2/
…/$m.$n/
; pakiet android.hardware.camera
w wersji 3.4
znajduje się w katalogu hardware/interfaces/camera/3.4/.
. Istnieje zakodowane na stałe mapowanie między prefiksem pakietu android.hardware.
a ścieżką hardware/interfaces/
.
Pakiety niebędące kluczowymi (dostawcy) to pakiety produkowane przez dostawcę SoC lub ODM. Prefiks pakietów niebędących pakietami podstawowymi to vendor.$(VENDOR).hardware.
, gdzie $(VENDOR)
oznacza dostawcę SoC lub OEM/ODM. Jest ona mapowana na ścieżkę vendor/$(VENDOR)/interfaces
w drzewie (to mapowanie jest również zakodowane na stałe).
Pełne i jednoznaczne nazwy typów zdefiniowanych przez użytkownika
W HIDL każda UDT ma pełną nazwę, która składa się z nazwy UDT, nazwy pakietu, w którym jest zdefiniowana UDT, oraz wersji pakietu. Pełna nazwa kwalifikowana jest używana tylko wtedy, gdy deklarowane są wystąpienia typu, a nie tam, gdzie definiuje się sam typ. Załóżmy na przykład, że pakiet android.hardware.nfc,
w wersji 1.0
definiuje strukturę o nazwie NfcData
. W miejscu deklaracji (czy to w types.hal
, czy w deklaracji interfejsu) należy po prostu podać:
struct NfcData { vec<uint8_t> data; };
Podczas deklarowania wystąpienia tego typu (w ramach struktury danych lub jako parametr metody) użyj pełnej nazwy typu:
android.hardware.nfc@1.0::NfcData
Ogólna składnia to:
PACKAGE@VERSION::UDT
, gdzie:
PACKAGE
to nazwa oddzielona kropkami pakietu HIDL (np.android.hardware.nfc
).VERSION
to format główny.podrzędna-wersja pakietu (np.1.0
).UDT
to nazwa rozdzielona kropkami typu danych UDT HIDL. HIDL obsługuje zagnieżdżone typy danych użytkownika, a interfejsy HIDL mogą zawierać typy danych użytkownika (jako zagnieżdżone deklaracje), więc do uzyskiwania dostępu do nazw używa się kropek.
Jeśli na przykład w pliku common.types w pakiecie android.hardware.example
w wersji 1.0
zdefiniowano następującą deklarację zagnieżdżoną:
// types.hal package android.hardware.example@1.0; struct Foo { struct Bar { // … }; Bar cheers; };
Pełna i jednoznaczna nazwa Bar
to android.hardware.example@1.0::Foo.Bar
. Jeśli deklaracja zagnieżdżoną znajdowała się nie tylko w powyższym pakiecie, ale też 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); };
Pełna i jednoznaczna nazwa Bar
to android.hardware.example@1.0::IQuux.Foo.Bar
.
W obu przypadkach Bar
może być używane jako Bar
tylko w zakresie deklaracji Foo
. Na poziomie pakietu lub interfejsu musisz odwoływać się do Bar
za pomocą Foo
:
Foo.Bar
, tak jak w deklaracji metody doSomething
powyżej. Możesz też zadeklarować metodę w bardziej rozbudowany sposób:
// IQuux.hal doSomething(android.hardware.example@1.0::IQuux.Foo f) generates (android.hardware.example@1.0::IQuux.Foo.Bar fb);
Pełne wartości wyliczenia
Jeśli typ niestandardowy jest typem wyliczeniowym, każda wartość typu wyliczeniowego ma pełną nazwę, która zaczyna się od pełnej nazwy typu wyliczeniowego, a następnie dwukropka i nazwy wartości typu wyliczeniowego. Załóżmy na przykład, że pakiet android.hardware.nfc,
w wersji 1.0
definiuje typ enumeracji NfcStatus
:
enum NfcStatus { STATUS_OK, STATUS_FAILED };
Pełna i jednoznaczna nazwa STATUS_OK
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 pełna nazwa typu enum.VALUE
to nazwa wartości.
Reguły automatycznego wnioskowania
Nie trzeba podawać pełnej nazwy typu danych użytkownika. W nazwie UDT można bezpiecznie pominąć te elementy:
- Pakiet, np.
@1.0::IFoo.Type
- Zarówno pakiet, jak i wersja, np.
IFoo.Type
HIDL próbuje uzupełnić nazwę za pomocą reguł automatycznego zakłócenia (niższa liczba reguły oznacza wyższy priorytet).
Reguła 1
Jeśli nie podano pakietu ani wersji, próba wyszukania nazwy lokalnej kończy się niepowodzeniem. Przykład:
interface Nfc { typedef string NfcErrorMessage; send(NfcData d) generates (@1.0::NfcStatus s, NfcErrorMessage m); };
NfcErrorMessage
jest wyszukiwany lokalnie, a typedef
jest znajdowany powyżej. Wartość NfcData
jest też wyszukiwana lokalnie, ale ponieważ nie jest zdefiniowana lokalnie, używane są reguły 2 i 3. @1.0::NfcStatus
pozwala na wybór wersji, więc zasada 1 nie ma zastosowania.
Reguła 2
Jeśli reguła 1 zawiedzie i w nazwie kwalifikowanej brakuje komponentu (pakiet, wersja lub pakiet i wersja), komponent zostanie automatycznie uzupełniony informacjami z bieżącego pakietu. Kompilator HIDL szuka wtedy w bieżącym pliku (oraz we wszystkich importach) automatycznie wypełnionej pełnej nazwy.
W przypadku przykładu powyżej załóżmy, że deklaracja ExtendedNfcData
została złożona w tym samym pakiecie (android.hardware.nfc
) w tej samej wersji (1.0
) co NfcData
, jak pokazano poniżej:
struct ExtendedNfcData { NfcData base; // … additional members };
Kompilator HIDL wypełnia nazwę pakietu i nazwę wersji z bieżącego pakietu, aby utworzyć pełną nazwę typu danych HIDL: android.hardware.nfc@1.0::NfcData
. Nazwa występuje w bieżącym pakiecie (zakładając, że został on prawidłowo zaimportowany), więc jest używana w deklaracji.
Nazwa w bieżącym pakiecie jest importowana tylko wtedy, gdy spełniony jest jeden z tych warunków:
- Jest ona importowana w wyraźny sposób za pomocą instrukcji
import
. - Jest zdefiniowany w pliku
types.hal
w bieżącym pakiecie
Ten sam proces jest stosowany, jeśli NfcData
został sklasyfikowany tylko na podstawie numeru wersji:
struct ExtendedNfcData { // autofill the current package name (android.hardware.nfc) @1.0::NfcData base; // … additional members };
Reguła 3
Jeśli reguła 2 nie znajdzie dopasowania (typ danych UDT nie jest zdefiniowany w bieżącym pakiecie), kompilator HIDL przeszukuje wszystkie zaimportowane pakiety w celu znalezienia dopasowania.
W przypadku powyższego przykładu załóżmy, że ExtendedNfcData
jest zadeklarowany w wersji 1.1
pakietu android.hardware.nfc
,
1.1
importuje 1.0
zgodnie z oczekiwaniami (patrz Rozszerzenia na poziomie pakietu), a definicja
określa tylko nazwę typu danych użytkownika:
struct ExtendedNfcData { NfcData base; // … additional members };
Kompilator szuka dowolnego typu danych użytkownika o nazwie NfcData
i znajduje go w android.hardware.nfc
w wersji 1.0
, co powoduje pełną nazwę typu danych użytkownika android.hardware.nfc@1.0::NfcData
. Jeśli dla danego częściowo kwalifikowanego typu UDT zostanie znalezione więcej niż jedno dopasowanie, kompilator HIDL zwróci błąd.
Przykład
Zgodnie z regułą 2 typ zaimportowany z bieżącego pakietu jest preferowany przed typem zaimportowanym 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 interpolowana jakoandroid.hardware.bar@1.0::S
i znajduje się wbar/1.0/types.hal
(ponieważtypes.hal
jest automatycznie importowana).IFooCallback
jest interpolowana jakoandroid.hardware.bar@1.0::IFooCallback
zgodnie z regułą 2, ale nie można jej znaleźć, ponieważbar/1.0/IFooCallback.hal
nie jest importowana automatycznie (jaktypes.hal
). Dlatego reguła 3 zwraca zamiast tego wartośćandroid.hardware.foo@1.0::IFooCallback
, która jest importowana za pomocą kolumnyimport android.hardware.foo@1.0;
.
types.hal
Każdy pakiet HIDL zawiera plik types.hal
zawierający UDT, które są udostępniane wszystkim interfejsom w tym pakiecie. Typy HIDL są zawsze publiczne. Niezależnie od tego, czy typ niestandardowy jest zadeklarowany w types.hal
czy w deklaracji interfejsu, jest on dostępny poza zakresem, w którym został zdefiniowany. types.hal
nie ma na celu opisywania publicznego interfejsu API pakietu, ale hostowania typów danych UDT używanych przez wszystkie interfejsy w pakiecie. Ze względu na charakter HIDL wszystkie UDT są częścią interfejsu.
types.hal
składa się z typów danych nieustrukturowanych i wykazów import
.
Funkcja types.hal
jest dostępna dla każdego interfejsu pakietu (jest to domyślny import), więc te instrukcje import
są z definicji dostępne na poziomie pakietu. UDT-y w types.hal
mogą też zawierać zaimportowane UDT-y i interfejsy.
Na przykład w przypadku 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ą te elementy:
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 zdefiniowana wtypes.hal
, cały pliktypes.hal
jest analizowany, ale typy inne niżQuuz
nie są importowane).
Wersje na poziomie interfejsu
Każdy interfejs w pakiecie znajduje się w osobnym pliku. Pakiet, do którego należy interfejs, jest deklarowany u góry interfejsu za pomocą instrukcji package
. Zgodnie z deklaracją pakietu może być podany zero lub więcej importów na poziomie interfejsu (częściowych lub pełnych pakietów). Przykład:
package android.hardware.nfc@1.0;
W HIDL interfejsy mogą dziedziczyć po innych interfejsach za pomocą słowa kluczowego extends
. Aby interfejs mógł rozszerzać inny interfejs, musi mieć do niego dostęp za pomocą instrukcji import
. Nazwa rozszerzanego interfejsu (interfejs podstawowy) podlega regułom kwalifikacji nazwy typu opisanym powyżej. Interfejs może dziedziczyć tylko z jednego interfejsu. HIDL nie obsługuje wielokrotnego dziedziczenia.
Przykłady wersji uprev poniżej używają tego 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); }
Reguły Uprev
Aby zdefiniować pakiet package@major.minor
, musi być spełniony warunek A lub wszystkie warunki B:
Reguła A | „Czy jest to pierwsza wersja podrzędna”: wszystkie poprzednie wersje podrzędne (package@major.0 , package@major.1 , …, package@major.(minor-1) ) nie mogą być zdefiniowane.
|
---|
Reguła B | Wszystkie te stwierdzenia są prawdziwe:
|
---|
Ze względu na regułę A:
- Pakiet może zaczynać się od dowolnej wersji podrzędnej (na przykład
android.hardware.biometrics.fingerprint
zaczyna się od@2.1
). - Wymaganie „
android.hardware.foo@1.0
nie jest zdefiniowane” oznacza, że kataloghardware/interfaces/foo/1.0
nie powinien w ogóle istnieć.
Jednak reguła A nie ma wpływu na pakiet o tej samej nazwie, ale innej głównej wersji (na przykład android.hardware.camera.device
ma zdefiniowane zarówno @1.0
, jak i @3.2
; @3.2
nie musi wchodzić w interakcje z @1.0
). Dlatego @3.2::IExtFoo
może rozszerzać @1.0::IFoo
.
Jeśli nazwa pakietu jest inna, package@major.minor::IBar
może rozszerzać interfejs o inną nazwę (na przykład android.hardware.bar@1.0::IBar
może rozszerzać android.hardware.baz@2.2::IBaz
). Jeśli interfejs nie deklaruje wprost supertypu za pomocą słowa kluczowego extend
, rozszerza android.hidl.base@1.0::IBase
(z wyjątkiem IBase
).
Wymagania B.2 i B.3 muszą być spełnione jednocześnie. Na przykład nawet jeśli android.hardware.foo@1.1::IFoo
rozszerza android.hardware.foo@1.0::IFoo
, aby spełnić regułę B.2, a android.hardware.foo@1.1::IExtBar
rozszerza android.hardware.foo@1.0::IBar
, nadal nie jest to prawidłowa aktualizacja.
Interfejsy Uprev
Aby zaktualizować wersję android.hardware.example@1.0
(zdefiniowaną powyżej) do wersji @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
na poziomie pakietu w wersji 1.0
w komponencie android.hardware.example
w wersji types.hal
. Chociaż w wersji 1.1
pakietu nie dodano nowych UDT, nadal potrzebne są odwołania do UDT w wersji 1.0
, dlatego importowanie na poziomie pakietu jest konieczne w wersji types.hal
. (Tego samego efektu można było osiągnąć, importując plik na poziomie interfejsu w pliku IQuux.hal
).
W deklaracji extends @1.0::IQuux
(IQuux
) określono wersję IQuux
, która jest dziedziczona (wymagane jest rozróżnienie, ponieważ IQuux
służy do deklarowania interfejsu i dziedziczenia z interfejsu). Deklaracje to po prostu nazwy, które dziedziczą wszystkie atrybuty pakietu i wersji w miejscu deklaracji, więc rozróżnienie musi być zawarte w nazwie interfejsu podstawowego. Moglibyśmy też użyć w pełni kwalifikowanej nazwy typu danych użytkownika, ale byłoby to zbędne.
Nowy interfejs IQuux
nie deklaruje ponownie metody fromFooToBar()
, którą dziedziczy z @1.0::IQuux
. Wystarczy, że wymieni nową metodę, którą dodaje fromBarToFoo()
. W HIDL dziedziczone metody nie mogą być ponownie deklarowane w interfejsach podrzędnych, więc interfejs IQuux
nie może jawnie deklarować metody fromFooToBar()
.
Konwencje Uprev
Czasami nazwy interfejsów muszą zostać zmienione w rozszerzonym interfejsie. Zalecamy, aby rozszerzenia, struktury i zbiory miały taką samą nazwę jak typ, z którego pochodzą, chyba że różnią się na tyle, że uzasadnia to nadanie im nowej nazwy. 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ę semakntyczną (np. fooWithLocation
), jest to preferowane. W przeciwnym razie powinna mieć nazwę podobną do nazwy rozszerzenia. Na przykład metoda foo_1_1
w pliku @1.1::IFoo
może zastąpić funkcjonalność metody foo
w pliku @1.0::IFoo
, jeśli nie ma lepszej nazwy alternatywnej.
Wersje na poziomie pakietu
Wersje HIDL są tworzone na poziomie pakietu. Po opublikowaniu pakietu nie można go zmienić (nie można zmienić jego zestawu interfejsów ani typów danych UDT). Pakiety mogą być powiązane ze sobą na kilka sposobów, które można wyrazić za pomocą kombinacji dziedziczenia na poziomie interfejsu i tworzenia typów danych użytkownika za pomocą kompozycji.
Jeden typ relacji jest jednak ściśle zdefiniowany i musi być stosowany: dziedziczenie wstecz na poziomie pakietu. W tym scenariuszu pakiet nadrzędny to pakiet, z którego dziedziczy się, a pakiet podrzędny to ten, który rozszerza pakiet nadrzędny. Reguły dziedziczenia zgodne wstecznie na poziomie pakietu:
- Wszystkie interfejsy najwyższego poziomu pakietu nadrzędnego są dziedziczone przez interfejsy w pakiecie podrzędnym.
- Do nowego pakietu można też dodawać nowe interfejsy (bez ograniczeń dotyczących relacji z innymi interfejsami w innych pakietach).
- Możesz też dodawać nowe typy danych, aby używać ich w ramach nowych metod istniejących interfejsów lub nowych interfejsów.
Te reguły można zaimplementować za pomocą dziedziczenia na poziomie interfejsu HIDL i kompozycji UDT, ale wymagają znajomości tych relacji na poziomie meta, aby wiedzieć, że stanowią one kompatybilne wstecz rozszerzenie pakietu. Ta wiedza jest wywnioskowana w ten sposób:
Jeśli pakiet spełnia ten wymóg, hidl-gen
stosuje reguły zgodności wstecznej.