Android 11 wprowadza możliwość używania AIDL dla HAL w Androidzie. Umożliwia to implementację części Androida bez HIDL. Tam, gdzie jest to możliwe, przejściowe warstwy HAL mają używać wyłącznie AIDL (jeśli górne warstwy HAL używają HIDL, należy użyć HIDL).
Warstwy HAL używające AIDL do komunikacji między komponentami struktury, takimi jak system.img
, a komponentami sprzętowymi, takimi jak te w vendor.img
, muszą używać stabilnego AIDL. Jednakże, aby komunikować się w ramach partycji, na przykład z jednej warstwy HAL do drugiej, nie ma ograniczeń co do użycia mechanizmu IPC.
Motywacja
AIDL istnieje dłużej niż HIDL i jest używany w wielu innych miejscach, na przykład między komponentami systemu Android lub w aplikacjach. Teraz, gdy AIDL obsługuje stabilność, możliwe jest zaimplementowanie całego stosu za pomocą jednego środowiska wykonawczego IPC. AIDL ma również lepszy system wersjonowania niż HIDL.
- Korzystanie z jednego języka IPC oznacza konieczność uczenia się, debugowania, optymalizowania i zabezpieczania tylko jednej rzeczy.
- AIDL obsługuje wersjonowanie w miejscu dla właścicieli interfejsu:
- Właściciele mogą dodawać metody na końcu interfejsów lub pola do parcelables. Oznacza to, że łatwiej jest wersjonować kod na przestrzeni lat, a także koszt z roku na rok jest mniejszy (typy można zmieniać na miejscu i nie ma potrzeby stosowania dodatkowych bibliotek dla każdej wersji interfejsu).
- Interfejsy rozszerzeń można dołączać w czasie wykonywania, a nie w systemie typów, więc nie ma potrzeby zmiany bazy dalszych rozszerzeń na nowsze wersje interfejsów.
- Istniejący interfejs AIDL może być używany bezpośrednio, gdy jego właściciel zdecyduje się go ustabilizować. Wcześniej cała kopia interfejsu musiałaby zostać utworzona w HIDL.
Pisanie interfejsu AIDL HAL
Aby interfejs AIDL mógł być używany między systemem a dostawcą, interfejs wymaga dwóch zmian:
- Każda definicja typu musi być opatrzona adnotacją
@VintfStability
. - Deklaracja
aidl_interface
musi zawieraćstability: "vintf",
.
Tylko właściciel interfejsu może wprowadzać te zmiany.
Po wprowadzeniu tych zmian interfejs musi znajdować się w manifeście VINTF , aby działał. Przetestuj to (i powiązane wymagania, takie jak sprawdzenie, czy zwolnione interfejsy są zamrożone) za pomocą testu VTS vts_treble_vintf_vendor_test
. Możesz użyć interfejsu @VintfStability
bez tych wymagań, wywołując AIBinder_forceDowngradeToLocalStability
w zapleczu NDK, android::Stability::forceDowngradeToLocalStability
w zapleczu C++ lub android.os.Binder#forceDowngradeToSystemStability
w zapleczu Java na obiekcie wiążącym przed wysłaniem do innego procesu. Obniżenie poziomu usługi do poziomu stabilności dostawcy nie jest obsługiwane w Javie, ponieważ wszystkie aplikacje działają w kontekście systemowym.
Ponadto, aby zapewnić maksymalną przenośność kodu i uniknąć potencjalnych problemów, takich jak niepotrzebne dodatkowe biblioteki, należy wyłączyć zaplecze CPP.
Zauważ, że użycie backends
w poniższym przykładzie kodu jest poprawne, ponieważ istnieją trzy backendy (Java, NDK i CPP). Poniższy kod mówi, jak konkretnie wybrać backend CPP, aby go wyłączyć.
aidl_interface: {
...
backends: {
cpp: {
enabled: false,
},
},
}
Wyszukiwanie interfejsów AIDL HAL
AOSP Stabilne interfejsy AIDL dla warstw HAL znajdują się w tych samych katalogach podstawowych co interfejsy HIDL, w folderach aidl
.
- sprzęt/interfejsy
- frameworki/sprzęt/interfejsy
- system/sprzęt/interfejsy
Powinieneś umieścić interfejsy rozszerzeń w innych podkatalogach hardware/interfaces
w vendor
lub hardware
.
Interfejsy rozszerzeń
Android ma zestaw oficjalnych interfejsów AOSP z każdym wydaniem. Gdy partnerzy systemu Android chcą dodać funkcje do tych interfejsów, nie powinni zmieniać ich bezpośrednio, ponieważ oznaczałoby to, że ich środowisko wykonawcze systemu Android jest niezgodne ze środowiskiem wykonawczym systemu Android AOSP. W przypadku urządzeń GMS unikanie zmiany tych interfejsów jest również tym, co gwarantuje, że obraz GSI może nadal działać.
Rozszerzenia można rejestrować na dwa różne sposoby:
- w czasie wykonywania, zobacz dołączone rozszerzenia .
- samodzielny, zarejestrowany globalnie i w VINTF.
Jednak zarejestrowane jest rozszerzenie, gdy komponenty specyficzne dla dostawcy (czyli niebędące częścią nadrzędnego AOSP) używają interfejsu, nie ma możliwości konfliktu scalania. Jednak po dokonaniu dalszych modyfikacji w nadrzędnych komponentach AOSP mogą wystąpić konflikty scalania i zalecane są następujące strategie:
- dodatki interfejsu mogą zostać przesłane do AOSP w następnej wersji
- dodatki interfejsu, które zapewniają większą elastyczność, bez konfliktów scalania, mogą zostać przesłane w następnej wersji
Pakiety rozszerzeń: ParcelableHolder
ParcelableHolder
to Parcelable
, który może zawierać inny Parcelable
. Głównym przypadkiem użycia ParcelableHolder
jest uczynienie Parcelable
rozszerzalnym. Na przykład obraz, którego realizatorzy urządzeń oczekują, że będą mogli rozszerzyć zdefiniowany przez AOSP Parcelable
, AospDefinedParcelable
, aby uwzględnić ich funkcje o wartości dodanej.
Wcześniej bez ParcelableHolder
osoby wdrażające urządzenia nie mogły modyfikować stabilnego interfejsu AIDL zdefiniowanego przez AOSP, ponieważ dodanie większej liczby pól byłoby błędem:
parcelable AospDefinedParcelable {
int a;
String b;
String x; // ERROR: added by a device implementer
int[] y; // added by a device implementer
}
Jak widać w powyższym kodzie, ta praktyka jest zepsuta, ponieważ pola dodane przez implementatora urządzenia mogą powodować konflikty, gdy Parcelable zostanie poprawiony w następnych wersjach systemu Android.
Używając ParcelableHolder
, właściciel parcelable może zdefiniować punkt rozszerzenia w Parcelable
.
parcelable AospDefinedParcelable {
int a;
String b;
ParcelableHolder extension;
}
Następnie realizatorzy urządzeń mogą zdefiniować własne Parcelable
dla swojego rozszerzenia.
parcelable OemDefinedParcelable {
String x;
int[] y;
}
Na koniec nowy Parcelable
można dołączyć do oryginalnego Parcelable
za pomocą pola ParcelableHolder
.
// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;
ap.extension.setParcelable(op);
...
OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);
// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();
ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);
...
std::shared_ptr<OemDefinedParcelable> op_ptr;
ap.extension.getParcelable(&op_ptr);
// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);
...
std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);
// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });
ap.extension.set_parcelable(Rc::clone(&op));
...
let op = ap.extension.get_parcelable::<OemDefinedParcelable>();
Budowanie w stosunku do środowiska wykonawczego AIDL
AIDL ma trzy różne backendy: Java, NDK, CPP. Aby używać stabilnego AIDL, musisz zawsze używać systemowej kopii libbinder w system/lib*/libbinder.so
i rozmawiać na /dev/binder
. W przypadku kodu na obrazie dostawcy oznacza to, że nie można użyć libbinder
(z VNDK): ta biblioteka ma niestabilny interfejs API C++ i niestabilne elementy wewnętrzne. Zamiast tego natywny kod dostawcy musi korzystać z zaplecza NDK AIDL, łączyć się z libbinder_ndk
(która jest wspierana przez system libbinder.so
) i łączyć się z bibliotekami -ndk_platform
utworzonymi przez wpisy aidl_interface
.
Nazwy instancji serwera AIDL HAL
Zgodnie z konwencją usługi AIDL HAL mają nazwę instancji w formacie $package.$type/$instance
. Na przykład instancja wibratora HAL jest zarejestrowana jako android.hardware.vibrator.IVibrator/default
.
Pisanie serwera AIDL HAL
Serwery @VintfStability
AIDL muszą być zadeklarowane w manifeście VINTF, na przykład tak:
<hal format="aidl">
<name>android.hardware.vibrator</name>
<version>1</version>
<fqname>IVibrator/default</fqname>
</hal>
W przeciwnym razie powinni normalnie zarejestrować usługę AIDL. Podczas uruchamiania testów VTS oczekuje się, że wszystkie zadeklarowane warstwy AIDL HAL są dostępne.
Pisanie klienta AIDL
Klienci AIDL muszą zadeklarować się w macierzy zgodności, na przykład tak:
<hal format="aidl" optional="true">
<name>android.hardware.vibrator</name>
<version>1-2</version>
<interface>
<name>IVibrator</name>
<instance>default</instance>
</interface>
</hal>
Konwersja istniejącej warstwy HAL z HIDL na AIDL
Użyj narzędzia hidl2aidl
, aby przekonwertować interfejs HIDL na AIDL.
Funkcje hidl2aidl
:
- Utwórz pliki
.aidl
na podstawie plików.hal
dla danego pakietu - Utwórz reguły kompilacji dla nowo utworzonego pakietu AIDL z włączonymi wszystkimi backendami
- Twórz metody tłumaczenia w backendach Java, CPP i NDK do tłumaczenia z typów HIDL na typy AIDL
- Utwórz reguły kompilacji dla tłumaczących bibliotek z wymaganymi zależnościami
- Twórz statyczne asercje, aby upewnić się, że moduły wyliczające HIDL i AIDL mają te same wartości w backendach CPP i NDK
Wykonaj następujące kroki, aby przekonwertować pakiet plików .hal na pliki .aidl:
Zbuduj narzędzie znajdujące się w
system/tools/hidl/hidl2aidl
.Kompilowanie tego narzędzia z najnowszego źródła zapewnia najbardziej kompletne doświadczenie. Możesz użyć najnowszej wersji, aby przekonwertować interfejsy w starszych gałęziach z poprzednich wersji.
m hidl2aidl
Uruchom narzędzie z katalogiem wyjściowym, po którym następuje pakiet do przekonwertowania.
Opcjonalnie użyj argumentu
-l
, aby dodać zawartość nowego pliku licencji na początek wszystkich wygenerowanych plików. Upewnij się, że używasz właściwej licencji i daty.hidl2aidl -o <output directory> -l <file with license> <package>
Na przykład:
hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
Przeczytaj wygenerowane pliki i napraw wszelkie problemy z konwersją.
-
conversion.log
zawiera wszelkie nierozwiązane problemy, które należy najpierw naprawić. - Wygenerowane pliki
.aidl
mogą zawierać ostrzeżenia i sugestie, które mogą wymagać działania. Te komentarze zaczynają się od//
. - Skorzystaj z okazji, aby posprzątać i wprowadzić poprawki do pakietu.
- Sprawdź adnotację
@JavaDerive
pod kątem funkcji, które mogą być potrzebne, takich jaktoString
lubequals
.
-
Buduj tylko cele, których potrzebujesz.
- Wyłącz backendy, które nie będą używane. Preferuj backend NDK nad backendem CPP, zobacz wybieranie środowiska uruchomieniowego .
- Usuń biblioteki tłumaczeń lub dowolny wygenerowany przez nie kod, który nie będzie używany.
Zobacz Główne różnice AIDL/HIDL .
- Korzystanie z wbudowanego
Status
AIDL i wyjątków zwykle ulepsza interfejs i eliminuje potrzebę stosowania innego typu statusu specyficznego dla interfejsu. - Argumenty interfejsu AIDL w metodach nie są domyślnie
@nullable
tak jak w HIDL.
- Korzystanie z wbudowanego
Sepolicy dla AIDL HAL
Typ usługi AIDL widoczny dla kodu dostawcy musi mieć atrybut hal_service_type
. W przeciwnym razie konfiguracja sepolicy jest taka sama jak w przypadku każdej innej usługi AIDL (chociaż istnieją specjalne atrybuty warstw HAL). Oto przykładowa definicja kontekstu usługi HAL:
type hal_foo_service, service_manager_type, hal_service_type;
W przypadku większości usług zdefiniowanych przez platformę kontekst usługi z poprawnym typem jest już dodany (na przykład android.hardware.foo.IFoo/default
byłby już oznaczony jako hal_foo_service
). Jeśli jednak klient platformy obsługuje wiele nazw wystąpień, należy dodać dodatkowe nazwy wystąpień w plikach service_contexts
specyficznych dla urządzenia.
android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0
Atrybuty HAL muszą zostać dodane, gdy tworzymy nowy typ HAL. Konkretny atrybut HAL może być powiązany z wieloma typami usług (z których każdy może mieć wiele instancji, jak właśnie omówiliśmy). Dla HAL, foo
, mamy hal_attribute(foo)
. To makro definiuje atrybuty hal_foo_client
i hal_foo_server
. Dla danej domeny makra hal_client_domain
i hal_server_domain
kojarzą domenę z danym atrybutem HAL. Na przykład serwer systemowy będący klientem tej warstwy HAL odpowiada polityce hal_client_domain(system_server, hal_foo)
. Serwer HAL podobnie zawiera hal_server_domain(my_hal_domain, hal_foo)
. Zazwyczaj dla danego atrybutu HAL tworzymy również domenę taką jak hal_foo_default
w celach informacyjnych lub przykładowych HAL. Jednak niektóre urządzenia używają tych domen na potrzeby własnych serwerów. Rozróżnianie domen dla wielu serwerów ma znaczenie tylko wtedy, gdy mamy wiele serwerów, które obsługują ten sam interfejs i potrzebują innego zestawu uprawnień w swoich implementacjach. We wszystkich tych makrach hal_foo
nie jest w rzeczywistości obiektem sepolicy. Zamiast tego ten token jest używany przez te makra do odwoływania się do grupy atrybutów powiązanych z parą klient-serwer.
Jednak do tej pory nie powiązaliśmy hal_foo_service
i hal_foo
(para atrybutów z hal_attribute(foo)
). Atrybut HAL jest powiązany z usługami AIDL HAL za pomocą makra hal_attribute_service
(HIDL HAL używają makra hal_attribute_hwservice
). Na przykład hal_attribute_service(hal_foo, hal_foo_service)
. Oznacza to, że procesy hal_foo_client
mogą uzyskać dostęp do warstwy HAL, a procesy hal_foo_server
mogą ją zarejestrować. Egzekwowanie tych reguł rejestracji jest wykonywane przez menedżera kontekstu ( servicemanager
). Uwaga, nazwy usług mogą nie zawsze odpowiadać atrybutom HAL. Na przykład możemy zobaczyć hal_attribute_service(hal_foo, hal_foo2_service)
. Ogólnie rzecz biorąc, ponieważ oznacza to, że usługi są zawsze używane razem, możemy rozważyć usunięcie hal_foo2_service
i użycie hal_foo_service
we wszystkich naszych kontekstach usług. Większość warstw HAL, które ustawiają wiele hal_attribute_service
, wynika z tego, że oryginalna nazwa atrybutu HAL nie jest wystarczająco ogólna i nie można jej zmienić.
Łącząc to wszystko razem, przykład HAL wygląda tak:
public/attributes:
// define hal_foo, hal_foo_client, hal_foo_server
hal_attribute(foo)
public/service.te
// define hal_foo_service
type hal_foo_service, hal_service_type, protected_service, service_manager_type
public/hal_foo.te:
// allow binder connection from client to server
binder_call(hal_foo_client, hal_foo_server)
// allow client to find the service, allow server to register the service
hal_attribute_service(hal_foo, hal_foo_service)
// allow binder communication from server to service_manager
binder_use(hal_foo_server)
private/service_contexts:
// bind an AIDL service name to the selinux type
android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0
private/<some_domain>.te:
// let this domain use the hal service
binder_use(some_domain)
hal_client_domain(some_domain, hal_foo)
vendor/<some_hal_server_domain>.te
// let this domain serve the hal service
hal_server_domain(some_hal_server_domain, hal_foo)
Dołączone interfejsy rozszerzeń
Rozszerzenie można dołączyć do dowolnego interfejsu programu wiążącego, niezależnie od tego, czy jest to interfejs najwyższego poziomu zarejestrowany bezpośrednio w menedżerze usług, czy też interfejs podrzędny. Otrzymując rozszerzenie, musisz potwierdzić, że typ rozszerzenia jest zgodny z oczekiwaniami. Rozszerzenia można ustawić tylko z poziomu procesu obsługującego spinacz.
Załączone rozszerzenia powinny być używane zawsze, gdy rozszerzenie modyfikuje funkcjonalność istniejącej warstwy HAL. Gdy potrzebna jest całkowicie nowa funkcjonalność, mechanizm ten nie musi być używany, a interfejs rozszerzenia można zarejestrować bezpośrednio w menedżerze usług. Dołączone interfejsy rozszerzeń mają największy sens, gdy są dołączone do podinterfejsów, ponieważ te hierarchie mogą być głębokie lub obejmować wiele instancji. Użycie rozszerzenia globalnego w celu odzwierciedlenia hierarchii interfejsu bindera innej usługi wymagałoby obszernej księgowości, aby zapewnić funkcjonalność równoważną bezpośrednio dołączanym rozszerzeniom.
Aby ustawić rozszerzenie w segregatorze, użyj następujących interfejsów API:
- W zapleczu NDK:
AIBinder_setExtension
- W zapleczu Java:
android.os.Binder.setExtension
- W zapleczu CPP:
android::Binder::setExtension
- W zapleczu Rust:
binder::Binder::set_extension
Aby uzyskać rozszerzenie w segregatorze, użyj następujących interfejsów API:
- W zapleczu NDK:
AIBinder_getExtension
- W zapleczu Java:
android.os.IBinder.getExtension
- W zapleczu CPP:
android::IBinder::getExtension
- W zapleczu Rusta:
binder::Binder::get_extension
Więcej informacji na temat tych interfejsów API można znaleźć w dokumentacji funkcji getExtension
w odpowiednim zapleczu. Przykład użycia rozszerzeń można znaleźć w hardware/interfaces/tests/extension/vibrator .
Główne różnice AIDL/HIDL
Podczas korzystania z AIDL HAL lub interfejsów AIDL HAL należy pamiętać o różnicach w porównaniu z pisaniem HIDL HAL.
- Składnia języka AIDL jest bliższa Javie. Składnia HIDL jest podobna do C++.
- Wszystkie interfejsy AIDL mają wbudowane statusy błędów. Zamiast tworzyć niestandardowe typy statusu, utwórz stałe int statusu w plikach interfejsu i użyj
EX_SERVICE_SPECIFIC
w zapleczu CPP/NDK i wyjątkuServiceSpecificException
w zapleczu Java. Zobacz Obsługa błędów . - AIDL nie uruchamia automatycznie pul wątków, gdy wysyłane są obiekty wiążące. Muszą być uruchomione ręcznie (patrz zarządzanie wątkami ).
- AIDL nie przerywa w przypadku niesprawdzonych błędów transportu (
Return
HIDL przerywa w przypadku niesprawdzonych błędów). - AIDL może zadeklarować tylko jeden typ na plik.
- Argumenty AIDL można określić jako in/out/inout oprócz parametru wyjściowego (nie ma „synchronicznych wywołań zwrotnych”).
- AIDL używa fd jako typu pierwotnego zamiast uchwytu.
- HIDL używa wersji głównych w przypadku zmian niekompatybilnych i wersji pomocniczych w przypadku zmian zgodnych. W AIDL zmiany zgodne z poprzednimi wersjami są wykonywane na miejscu. AIDL nie ma wyraźnej koncepcji głównych wersji; zamiast tego jest to włączone do nazw pakietów. Na przykład AIDL może używać nazwy pakietu
bluetooth2
. - AIDL domyślnie nie dziedziczy priorytetu w czasie rzeczywistym. Aby włączyć dziedziczenie priorytetów w czasie rzeczywistym, należy użyć funkcji
setInheritRt
dla poszczególnych powiązań.