Android 10 wprowadza obsługę stabilnego języka AIDL (Android Interface Definition Language), czyli nowego sposobu śledzenia interfejsu API i interfejsu ABI udostępnianych przez interfejsy AIDL. Stabilny AIDL działa dokładnie tak samo jak AIDL, ale system kompilacji śledzi zgodność interfejsu i istnieją ograniczenia dotyczące tego, co można zrobić:
- Interfejsy są definiowane w systemie kompilacji za pomocą
aidl_interfaces
. - Interfejsy mogą zawierać tylko dane strukturalne. Obiekty Parcelable reprezentujące preferowane typy są tworzone automatycznie na podstawie definicji AIDL i są automatycznie serializowane i deserializowane.
- Interfejsy mogą być deklarowane jako stabilne (zgodne wstecznie). W takim przypadku interfejs API jest śledzony i wersjonowany w pliku obok interfejsu AIDL.
Uporządkowany a stabilny AIDL
Uporządkowany AIDL odnosi się do typów zdefiniowanych wyłącznie w AIDL. Na przykład deklaracja obiektu Parcelable (niestandardowy obiekt Parcelable) nie jest strukturalnym językiem AIDL. Obiekty Parcelable, których pola są zdefiniowane w AIDL, są nazywane obiektami Parcelable o strukturze.
Stabilny AIDL wymaga strukturalnego AIDL, aby system kompilacji i kompilator mogły określić, czy zmiany wprowadzone w obiektach parcelable są zgodne wstecznie.
Nie wszystkie interfejsy strukturalne są jednak stabilne. Aby interfejs był stabilny, musi używać tylko typów strukturalnych, a także tych funkcji zarządzania wersjami: Z kolei interfejs nie jest stabilny, jeśli do jego utworzenia użyto podstawowego systemu kompilacji lub jeśli ustawiono wartość unstable:true
.
Definiowanie interfejsu AIDL
Definicja aidl_interface
wygląda tak:
aidl_interface {
name: "my-aidl",
srcs: ["srcs/aidl/**/*.aidl"],
local_include_dir: "srcs/aidl",
imports: ["other-aidl"],
versions_with_info: [
{
version: "1",
imports: ["other-aidl-V1"],
},
{
version: "2",
imports: ["other-aidl-V3"],
}
],
stability: "vintf",
backend: {
java: {
enabled: true,
platform_apis: true,
},
cpp: {
enabled: true,
},
ndk: {
enabled: true,
},
rust: {
enabled: true,
},
},
}
name
: nazwa modułu interfejsu AIDL, która jednoznacznie identyfikuje interfejs AIDL.srcs
: lista plików źródłowych AIDL, które składają się na interfejs. Ścieżka do typu AIDLFoo
zdefiniowanego w pakieciecom.acme
powinna mieć postać<base_path>/com/acme/Foo.aidl
, gdzie<base_path>
może być dowolnym katalogiem powiązanym z katalogiem, w którym znajduje sięAndroid.bp
. W przykładzie powyżej<base_path>
tosrcs/aidl
.local_include_dir
: ścieżka, od której zaczyna się nazwa pakietu. Odpowiada to<base_path>
wyjaśnionemu powyżej.imports
: lista modułówaidl_interface
, z których korzysta ten moduł. Jeśli jeden z Twoich interfejsów AIDL korzysta z interfejsu lub obiektu Parcelable z innego pakietu, wpisz tutaj jego nazwę.aidl_interface
Może to być sama nazwa, która odnosi się do najnowszej wersji, lub nazwa z sufiksem wersji (np.-V1
), która odnosi się do konkretnej wersji. Określanie wersji jest obsługiwane od Androida 12.versions
: poprzednie wersje interfejsu, które są zablokowane wapi_dir
. Od Androida 11versions
są zablokowane waidl_api/name
. Jeśli nie ma zamrożonych wersji interfejsu, nie należy tego określać i nie będą przeprowadzane testy zgodności. To pole zostało zastąpione wartościąversions_with_info
w przypadku Androida 13 i nowszych wersji.versions_with_info
: lista krotek, z których każda zawiera nazwę zamrożonej wersji i listę importów wersji innych modułów aidl_interface, które ta wersja aidl_interface zaimportowała. Definicja wersji V interfejsu AIDL IFACE znajduje się waidl_api/IFACE/V
. To pole zostało wprowadzone w Androidzie 13 i nie powinno być modyfikowane bezpośrednio wAndroid.bp
. Pole jest dodawane lub aktualizowane przez wywołanie*-update-api
lub*-freeze-api
. Poza tym polaversions
są automatycznie przenoszone doversions_with_info
, gdy użytkownik wywoła*-update-api
lub*-freeze-api
.stability
: opcjonalna flaga dotycząca obietnicy stabilności tego interfejsu. Obsługiwana jest tylko wartość"vintf"
. Jeśli zasadastability
nie jest ustawiona, system kompilacji sprawdza, czy interfejs jest zgodny wstecznie, chyba że określono zasadęunstable
. Stan „nieustawiony” odpowiada interfejsowi o stabilności w tym kontekście kompilacji (czyli wszystkie elementy systemowe, np. elementy wsystem.img
i powiązanych partycjach, lub wszystkie elementy dostawcy, np. elementy wvendor.img
i powiązanych partycjach). Jeśli wartośćstability
to"vintf"
, oznacza to obietnicę stabilności: interfejs musi być stabilny tak długo, jak jest używany.gen_trace
: opcjonalna flaga włączająca lub wyłączająca śledzenie. Od Androida 14 domyślnie jest ustawiona natrue
w przypadku backendówcpp
ijava
.host_supported
: opcjonalna flaga, która po ustawieniu natrue
udostępnia wygenerowane biblioteki środowisku hosta.unstable
: opcjonalna flaga używana do oznaczania, że ten interfejs nie musi być stabilny. Jeśli ta wartość jest ustawiona natrue
, system kompilacji nie tworzy zrzutu interfejsu API ani nie wymaga jego aktualizacji.frozen
: opcjonalna flaga, która po ustawieniu natrue
oznacza, że interfejs nie uległ zmianie od poprzedniej wersji. Umożliwia to przeprowadzanie większej liczby kontroli w czasie kompilacji. Jeśli ta wartość jest ustawiona nafalse
, oznacza to, że interfejs jest w trakcie opracowywania i zawiera nowe zmiany, więc uruchomienie poleceniafoo-freeze-api
spowoduje wygenerowanie nowej wersji i automatyczną zmianę wartości natrue
. Wprowadzona w Androidzie 14.backend.<type>.enabled
: te flagi włączają i wyłączają poszczególne interfejsy backendu, dla których kompilator AIDL generuje kod. Obsługiwane są 4 backendy: Java, C++, NDK i Rust. Domyślnie włączone są interfejsy Java, C++ i NDK. Jeśli któryś z tych 3 backendów nie jest potrzebny, należy go wyraźnie wyłączyć. Do Androida 15 język Rust jest domyślnie wyłączony.backend.<type>.apex_available
: Lista nazw APEX, dla których dostępna jest wygenerowana biblioteka stubów.backend.[cpp|java].gen_log
: opcjonalna flaga, która określa, czy ma być generowany dodatkowy kod do zbierania informacji o transakcji.backend.[cpp|java].vndk.enabled
: opcjonalna flaga, która sprawia, że ten interfejs jest częścią VNDK. Wartość domyślna tofalse
.backend.[cpp|ndk].additional_shared_libraries
: wprowadzony w Androidzie 14, ten flag dodaje zależności do bibliotek natywnych. Ta flaga jest przydatna w przypadkundk_header
icpp_header
.backend.java.sdk_version
: opcjonalna flaga do określania wersji pakietu SDK, na podstawie której została utworzona biblioteka stubów Java. Wartość domyślna to"system_current"
. Nie należy ustawiać tej wartości, gdybackend.java.platform_apis
ma wartośćtrue
.backend.java.platform_apis
: opcjonalna flaga, którą należy ustawić natrue
, gdy wygenerowane biblioteki mają być kompilowane na podstawie interfejsu API platformy, a nie pakietu SDK.
Dla każdej kombinacji wersji i włączonych backendów tworzona jest biblioteka stubów. Informacje o tym, jak odwoływać się do konkretnej wersji biblioteki stubów dla konkretnego backendu, znajdziesz w zasadach nazewnictwa modułów.
Pisanie plików AIDL
Interfejsy w stabilnym AIDL są podobne do tradycyjnych interfejsów, z tym wyjątkiem, że nie mogą używać nieustrukturyzowanych obiektów Parcelable (ponieważ nie są one stabilne – patrz Ustrukturyzowany a stabilny AIDL). Podstawową różnicą w przypadku stabilnego AIDL jest sposób definiowania obiektów Parcelable. Wcześniej obiekty Parcelable były deklarowane z wyprzedzeniem. W stabilnym (a więc strukturalnym) AIDL pola i zmienne obiektów Parcelable są definiowane wprost.
// in a file like 'some/package/Thing.aidl'
package some.package;
parcelable SubThing {
String a = "foo";
int b;
}
Wartość domyślna jest obsługiwana (ale nie jest wymagana) w przypadku tych pól: boolean
, char
, float
, double
, byte
, int
, long
i String
. W Androidzie 12 obsługiwane są też wartości domyślne wyliczeń zdefiniowanych przez użytkownika. Jeśli wartość domyślna nie jest określona, używana jest wartość podobna do 0 lub pusta.
Wyliczenia bez wartości domyślnej są inicjowane wartością 0, nawet jeśli nie ma wyliczenia o wartości zero.
Korzystanie z bibliotek stubów
Po dodaniu bibliotek zastępczych jako zależności do modułu możesz uwzględnić je w swoich plikach. Oto przykłady bibliotek stubów w systemie kompilacji (Android.mk
można też używać w przypadku starszych definicji modułów).
W tych przykładach nie ma wersji, więc reprezentują one użycie niestabilnego interfejsu. Nazwy interfejsów z wersjami zawierają jednak dodatkowe informacje. Więcej informacji znajdziesz w sekcji Wersjonowanie interfejsów.
cc_... {
name: ...,
// use `shared_libs:` to load your library and its transitive dependencies
// dynamically
shared_libs: ["my-module-name-cpp"],
// use `static_libs:` to include the library in this binary and drop
// transitive dependencies
static_libs: ["my-module-name-cpp"],
...
}
# or
java_... {
name: ...,
// use `static_libs:` to add all jars and classes to this jar
static_libs: ["my-module-name-java"],
// use `libs:` to make these classes available during build time, but
// not add them to the jar, in case the classes are already present on the
// boot classpath (such as if it's in framework.jar) or another jar.
libs: ["my-module-name-java"],
// use `srcs:` with `-java-sources` if you want to add classes in this
// library jar directly, but you get transitive dependencies from
// somewhere else, such as the boot classpath or another jar.
srcs: ["my-module-name-java-source", ...],
...
}
# or
rust_... {
name: ...,
rustlibs: ["my-module-name-rust"],
...
}
Przykład w C++:
#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
// use just like traditional AIDL
Przykład w Javie:
import some.package.IFoo;
import some.package.Thing;
...
// use just like traditional AIDL
Przykład w Rust:
use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
// use just like traditional AIDL
Interfejsy obsługi wersji
Zadeklarowanie modułu o nazwie foo powoduje też utworzenie w systemie kompilacji elementu docelowego, którego możesz używać do zarządzania interfejsem API modułu. Po utworzeniu foo-freeze-api dodaje nową definicję interfejsu API w folderze api_dir
lub aidl_api/name
(w zależności od wersji Androida) oraz plik .hash
. Oba te elementy reprezentują nowo zamrożoną wersję interfejsu. foo-freeze-api aktualizuje też właściwość versions_with_info
, aby odzwierciedlała dodatkową wersję, oraz imports
dla tej wersji. Zasadniczo wartość imports
w polu versions_with_info
jest kopiowana z pola imports
. Jednak w imports
w versions_with_info
dla importu określono najnowszą stabilną wersję, która nie ma wyraźnej wersji.
Po określeniu właściwości versions_with_info
system kompilacji przeprowadza testy zgodności między zamrożonymi wersjami, a także między najnowszą wersją zamrożoną a wersją Top of Tree (ToT).
Musisz też zarządzać definicją interfejsu API wersji ToT. Za każdym razem, gdy interfejs API zostanie zaktualizowany, uruchom polecenie foo-update-api, aby zaktualizować plik aidl_api/name/current
, który zawiera definicję interfejsu API w wersji ToT.
Aby zachować stabilność interfejsu, właściciele mogą dodawać nowe:
- Metody na końcu interfejsu (lub metody z jawnie zdefiniowanymi nowymi serialami)
- Elementy na końcu obiektu Parcelable (wymaga dodania wartości domyślnej dla każdego elementu)
- Wartości stałe
- W Androidzie 11 enumeratory
- W Androidzie 12 pola do końca unii
Nie można wykonywać żadnych innych działań, a nikt inny nie może modyfikować interfejsu (w przeciwnym razie może dojść do konfliktu ze zmianami wprowadzonymi przez właściciela).
Aby sprawdzić, czy wszystkie interfejsy są zamrożone na potrzeby wersji, możesz utworzyć kompilację z ustawionymi tymi zmiennymi środowiskowymi:
AIDL_FROZEN_REL=true m ...
– kompilacja wymaga zamrożenia wszystkich stabilnych interfejsów AIDL, które nie mają określonego polaowner:
.AIDL_FROZEN_OWNERS="aosp test"
– kompilacja wymaga, aby wszystkie stabilne interfejsy AIDL były zamrożone, a poleowner:
miało wartość „aosp” lub „test”.
Stabilność importu
Aktualizowanie wersji importów w przypadku zamrożonych wersji interfejsu jest wstecznie zgodne na warstwie Stable AIDL. Aktualizacja tych elementów wymaga jednak zaktualizowania wszystkich serwerów i klientów, którzy używają poprzedniej wersji interfejsu. Niektóre aplikacje mogą mieć problemy z mieszaniem różnych wersji typów. W przypadku pakietów zawierających tylko typy lub pakietów wspólnych jest to zwykle bezpieczne, ponieważ kod musi już obsługiwać nieznane typy z transakcji IPC.
W kodzie platformy Android android.hardware.graphics.common
jest największym przykładem tego typu uaktualnienia wersji.
Korzystanie z interfejsów z wersjami
Metody interfejsu
W czasie działania, gdy nowe klienty próbują wywołać nowe metody na starym serwerze, otrzymują błąd lub wyjątek w zależności od backendu.
cpp
backend otrzymuje::android::UNKNOWN_TRANSACTION
.ndk
backend otrzymujeSTATUS_UNKNOWN_TRANSACTION
.java
otrzymujeandroid.os.RemoteException
z komunikatem, że interfejs API nie jest zaimplementowany.
Strategie radzenia sobie z tym problemem znajdziesz w sekcjach Wersje zapytań i Używanie wartości domyślnych.
Parcelables
Gdy do obiektów Parcelable zostaną dodane nowe pola, stare klienty i serwery je odrzucą. Gdy nowe klienty i serwery otrzymają stare obiekty Parcelable, domyślne wartości nowych pól zostaną automatycznie wypełnione. Oznacza to, że w przypadku wszystkich nowych pól w obiekcie Parcelable należy określić wartości domyślne.
Klienci nie powinni oczekiwać, że serwery będą używać nowych pól, chyba że wiedzą, że serwer implementuje wersję, w której zdefiniowano to pole (patrz sprawdzanie wersji).
Wartości w polu enum i stałe
Podobnie klienci i serwery powinni odrzucać lub ignorować nierozpoznane wartości stałe i enumeratory, ponieważ w przyszłości mogą zostać dodane kolejne. Na przykład serwer nie powinien przerywać działania, gdy otrzyma nieznany mu element wyliczeniowy. Serwer powinien zignorować wyliczenie lub zwrócić coś, co poinformuje klienta, że nie jest ono obsługiwane w tej implementacji.
Związki zawodowe
Próba wysłania unii z nowym polem nie powiedzie się, jeśli odbiorca jest starszy i nie zna tego pola. Implementacja nigdy nie zobaczy unii z nowym polem. Niepowodzenie jest ignorowane, jeśli jest to transakcja jednokierunkowa. W przeciwnym razie błąd to BAD_VALUE
(w przypadku backendu C++ lub NDK) lub IllegalArgumentException
(w przypadku backendu Java). Błąd jest odbierany, gdy klient wysyła do starego serwera zbiór sum do nowego pola lub gdy stary klient odbiera zbiór sum z nowego serwera.
Zarządzanie wieloma wersjami
Przestrzeń nazw łącznika w Androidzie może mieć tylko 1 wersję konkretnego interfejsu aidl
, aby uniknąć sytuacji, w których wygenerowane typy aidl
mają wiele definicji. W języku C++ obowiązuje zasada jednej definicji, która wymaga tylko jednej definicji każdego symbolu.
Podczas kompilacji Androida pojawia się błąd, gdy moduł jest zależny od różnych wersji tej samej aidl_interface
biblioteki. Moduł może być zależny od tych bibliotek bezpośrednio lub pośrednio przez zależności swoich zależności. Te błędy pokazują wykres zależności od modułu, w którym wystąpił błąd, do sprzecznych wersji biblioteki aidl_interface
. Wszystkie zależności muszą zostać zaktualizowane, aby zawierały tę samą (zwykle najnowszą) wersję tych bibliotek.
Jeśli biblioteka interfejsu jest używana przez wiele różnych modułów, warto utworzyć cc_defaults
, java_defaults
i rust_defaults
dla dowolnej grupy bibliotek i procesów, które muszą używać tej samej wersji. Wprowadzając nową wersję interfejsu, można zaktualizować te wartości domyślne, a wszystkie moduły, które ich używają, zostaną zaktualizowane razem, co zapewni, że nie będą korzystać z różnych wersji interfejsu.
cc_defaults {
name: "my.aidl.my-process-group-ndk-shared",
shared_libs: ["my.aidl-V3-ndk"],
...
}
cc_library {
name: "foo",
defaults: ["my.aidl.my-process-group-ndk-shared"],
...
}
cc_binary {
name: "bar",
defaults: ["my.aidl.my-process-group-ndk-shared"],
...
}
Gdy moduły aidl_interface
importują inne moduły aidl_interface
, tworzą dodatkowe zależności, które wymagają używania określonych wersji. Zarządzanie taką sytuacją może być trudne, gdy istnieją wspólne aidl_interface
moduły importowane w wielu aidl_interface
modułach używanych
razem w tych samych procesach.
aidl_interfaces_defaults
można używać do przechowywania jednej definicji najnowszych wersji zależności dla aidl_interface
, którą można aktualizować w jednym miejscu i używać we wszystkich modułach aidl_interface
, które chcą importować ten wspólny interfejs.
aidl_interface_defaults {
name: "android.popular.common-latest-defaults",
imports: ["android.popular.common-V3"],
...
}
aidl_interface {
name: "android.foo",
defaults: ["my.aidl.latest-ndk-shared"],
...
}
aidl_interface {
name: "android.bar",
defaults: ["my.aidl.latest-ndk-shared"],
...
}
Programowanie oparte na flagach
Interfejsów w trakcie opracowywania (niezamrożonych) nie można używać na urządzeniach z wersją produkcyjną, ponieważ nie ma gwarancji, że będą one zgodne wstecznie.
AIDL obsługuje wycofywanie w czasie działania w przypadku tych niezamrożonych bibliotek interfejsów, aby kod mógł być pisany w najnowszej niezamrożonej wersji i nadal używany na urządzeniach w wersji produkcyjnej. Wsteczna zgodność działania klientów jest podobna do dotychczasowego działania, a w przypadku wycofywania implementacje muszą również być zgodne z tymi zachowaniami. Zobacz Korzystanie z interfejsów z wersjami.
Flaga kompilacji AIDL
Flaga, która kontroluje to zachowanie, to RELEASE_AIDL_USE_UNFROZEN
. Jest ona zdefiniowana w build/release/build_flags.bzl
. true
oznacza, że w czasie działania używana jest niezablokowana wersja interfejsu, a false
oznacza, że biblioteki niezablokowanych wersji zachowują się jak ich ostatnia zablokowana wersja.
W przypadku lokalnego programowania możesz zastąpić flagę wartością true
, ale przed opublikowaniem musisz przywrócić wartość false
. Zwykle prace programistyczne są prowadzone w konfiguracji, w której flaga ma wartość true
.
Matryca zgodności i pliki manifestu
Obiekty interfejsu dostawcy (obiekty VINTF) określają, jakich wersji oczekuje się po obu stronach interfejsu dostawcy i jakie wersje są udostępniane.
Większość urządzeń innych niż Cuttlefish jest zgodna z najnowszą macierzą zgodności dopiero po zamrożeniu interfejsów, więc nie ma różnicy w bibliotekach AIDL w zależności od RELEASE_AIDL_USE_UNFROZEN
.
Macierze
Interfejsy należące do partnera są dodawane do macierzy zgodności z określonym urządzeniem lub produktem, na których urządzenie jest oparte podczas prac nad nim. Gdy do macierzy zgodności zostanie dodana nowa, niezablokowana wersja interfejsu, poprzednie zablokowane wersje muszą pozostać w niej przez RELEASE_AIDL_USE_UNFROZEN=false
. Możesz to zrobić, używając różnych plików macierzy zgodności dla różnych konfiguracji RELEASE_AIDL_USE_UNFROZEN
lub zezwalając na obie wersje w jednym pliku macierzy zgodności, który jest używany we wszystkich konfiguracjach.
Na przykład podczas dodawania niezamrożonej wersji 4 użyj wartości <version>3-4</version>
.
Gdy wersja 4 zostanie zamrożona, możesz usunąć wersję 3 z macierzy zgodności, ponieważ zamrożona wersja 4 jest używana, gdy RELEASE_AIDL_USE_UNFROZEN
jest false
.
Pliki manifestu
W Androidzie 15 wprowadzono zmianę w libvintf
, która umożliwia modyfikowanie plików manifestu w czasie kompilacji na podstawie wartości RELEASE_AIDL_USE_UNFROZEN
.
Pliki manifestu i fragmenty pliku manifestu deklarują, którą wersję interfejsu implementuje usługa. Jeśli używasz najnowszej, niezamrożonej wersji interfejsu, musisz zaktualizować plik manifestu, aby odzwierciedlał tę nową wersję. Gdy
RELEASE_AIDL_USE_UNFROZEN=false
wpisy w pliku manifestu zostaną dostosowane przez
libvintf
, aby odzwierciedlały zmianę w wygenerowanej bibliotece AIDL. Wersja
została zmodyfikowana z wersji niezamrożonej N
do
ostatniej zamrożonej wersji N - 1
. Dzięki temu użytkownicy nie muszą zarządzać wieloma plikami manifestu ani fragmentami manifestu dla każdej usługi.
Zmiany w kliencie HAL
Kod klienta HAL musi być wstecznie zgodny z każdą poprzednią obsługiwaną zamrożoną wersją. Gdy RELEASE_AIDL_USE_UNFROZEN
ma wartość false
, usługi zawsze wyglądają jak ostatnia zamrożona wersja lub starsza (np. wywoływanie nowych, niezamrożonych metod zwraca UNKNOWN_TRANSACTION
, a nowe pola parcelable
mają wartości domyślne). Klienci platformy Android muszą być wstecznie zgodni z dodatkowymi poprzednimi wersjami, ale jest to nowa informacja dla klientów dostawców i klientów interfejsów należących do partnerów.
Zmiany w implementacji HAL
Największą różnicą w rozwoju HAL w porównaniu z rozwojem opartym na flagach jest wymaganie, aby implementacje HAL były wstecznie zgodne z ostatnią zamrożoną wersją, aby działać, gdy RELEASE_AIDL_USE_UNFROZEN
ma wartość false
.
Uwzględnianie zgodności wstecznej w implementacjach i kodzie urządzenia to nowe zadanie. Zobacz Używanie interfejsów z określoną wersją.
W przypadku klientów i serwerów oraz kodu platformy i kodu dostawcy kwestie związane z kompatybilnością wsteczną są zasadniczo takie same, ale istnieją subtelne różnice, o których musisz pamiętać, ponieważ wdrażasz teraz 2 wersje korzystające z tego samego kodu źródłowego (bieżącej, niezablokowanej wersji).
Przykład: interfejs ma 3 zamrożone wersje. Interfejs zostanie zaktualizowany o nową metodę. Klient i usługa zostaną zaktualizowane, aby korzystać z nowej biblioteki w wersji 4. Biblioteka V4 jest oparta na niezablokowanej wersji interfejsu, więc gdy wartość RELEASE_AIDL_USE_UNFROZEN
to false
, działa jak ostatnia zablokowana wersja, czyli wersja 3, i uniemożliwia korzystanie z nowej metody.
Gdy interfejs zostanie zamrożony, wszystkie wartości RELEASE_AIDL_USE_UNFROZEN
będą używać tej zamrożonej wersji, a kod obsługujący zgodność wsteczną będzie można usunąć.
Podczas wywoływania metod w wywołaniach zwrotnych musisz odpowiednio obsługiwać sytuację, w której zwracana jest wartość UNKNOWN_TRANSACTION
. Klienci mogą wdrażać 2 różne wersje wywołania zwrotnego w zależności od konfiguracji wersji, więc nie możesz zakładać, że klient wysyła najnowszą wersję, a nowe metody mogą zwracać tę wartość. Działa to podobnie jak w przypadku klientów AIDL, którzy zachowują zgodność wsteczną z serwerami, co opisano w artykule Korzystanie z interfejsów z określoną wersją.
// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
mMyCallback = cb;
// Get the version of the callback for later when we call methods on it
auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
return status;
}
// Example of using the callback later
void NotifyCallbackLater() {
// From the latest frozen version (V2)
mMyCallback->foo();
// Call this method from the unfrozen V3 only if the callback is at least V3
if (mMyCallbackVersion >= 3) {
mMyCallback->bar();
}
}
Nowe pola w istniejących typach (parcelable
, enum
, union
) mogą nie istnieć lub zawierać wartości domyślne, gdy RELEASE_AIDL_USE_UNFROZEN
ma wartość false
, a wartości nowych pól, które usługa próbuje wysłać, są pomijane podczas przetwarzania.
Nowych typów dodanych w tej odblokowanej wersji nie można wysyłać ani odbierać za pomocą interfejsu.
Implementacja nigdy nie otrzymuje wywołania nowych metod od żadnych klientów, gdy RELEASE_AIDL_USE_UNFROZEN
ma wartość false
.
Uważaj, aby używać nowych enumeratorów tylko w wersji, w której zostały wprowadzone, a nie w poprzedniej.
Zwykle używasz foo->getInterfaceVersion()
, aby sprawdzić, z której wersji korzysta interfejs zdalny. Dzięki obsłudze wersji opartej na flagach wdrażasz jednak 2 różne wersje, więc możesz chcieć uzyskać wersję bieżącego interfejsu. Możesz to zrobić, pobierając wersję interfejsu bieżącego obiektu, np. this->getInterfaceVersion()
lub inne metody dla my_ver
. Więcej informacji znajdziesz w sekcji Wysyłanie zapytań o wersję interfejsu obiektu zdalnego.
Nowe stabilne interfejsy VINTF
Gdy dodawany jest nowy pakiet interfejsu AIDL, nie ma ostatniej zamrożonej wersji, więc gdy RELEASE_AIDL_USE_UNFROZEN
jest false
, nie ma zachowania, do którego można by wrócić. Nie używaj tych interfejsów. Gdy wartość RELEASE_AIDL_USE_UNFROZEN
wynosi
false
, menedżer usług nie zezwoli usłudze na zarejestrowanie interfejsu, a klienci nie będą mogli go znaleźć.
Usługi możesz dodawać warunkowo na podstawie wartości flagi RELEASE_AIDL_USE_UNFROZEN
w pliku makefile urządzenia:
ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
android.hardware.health.storage-service
endif
Jeśli usługa jest częścią większego procesu, więc nie możesz dodać jej do urządzenia warunkowo, możesz sprawdzić, czy została zadeklarowana za pomocą IServiceManager::isDeclared()
. Jeśli jest zadeklarowany i nie udało się go zarejestrować, przerwij proces. Jeśli nie zostanie zadeklarowana, rejestracja prawdopodobnie się nie powiedzie.
Nowe interfejsy stabilnego rozszerzenia VINTF
Nowe interfejsy rozszerzeń nie mają poprzedniej wersji, do której można by się odwołać, a ponieważ nie są zarejestrowane w ServiceManager
ani zadeklarowane w plikach manifestu VINTF, IServiceManager::isDeclared()
nie można ich używać do określania, kiedy należy dołączyć interfejs rozszerzenia do innego interfejsu.
Zmienna RELEASE_AIDL_USE_UNFROZEN
może służyć do określania, czy dołączyć nowy interfejs rozszerzenia do istniejącego interfejsu, aby uniknąć używania go na wydanych urządzeniach. Aby można było korzystać z interfejsu na urządzeniach, które zostały wprowadzone na rynek, musi on zostać zamrożony.
Testy VTS vts_treble_vintf_vendor_test
i vts_treble_vintf_framework_test
wykrywają, kiedy na wydanym urządzeniu używany jest niezablokowany interfejs rozszerzenia, i zgłaszają błąd.
Jeśli interfejs rozszerzenia nie jest nowy i ma wcześniej zamrożoną wersję, przywróci tę wersję i nie będzie wymagać żadnych dodatkowych działań.
Cuttlefish jako narzędzie deweloperskie
Co roku po zamrożeniu VINTF dostosowujemy macierz zgodności platformy (FCM)target-level
i PRODUCT_SHIPPING_API_LEVEL
Cuttlefish, aby odzwierciedlały urządzenia wprowadzane na rynek z wersją z kolejnego roku. Dostosowujemy target-level
i PRODUCT_SHIPPING_API_LEVEL
, aby mieć pewność, że istnieje urządzenie startowe, które zostało przetestowane i spełnia nowe wymagania dotyczące przyszłorocznej wersji.
Gdy RELEASE_AIDL_USE_UNFROZEN
ma wartość true
, Cuttlefish jest używany do tworzenia przyszłych wersji Androida. Jest on kierowany na poziom FCM w przyszłorocznej wersji Androida i PRODUCT_SHIPPING_API_LEVEL
, co wymaga spełnienia przez niego wymagań dotyczących oprogramowania dostawcy (VSR) w przyszłej wersji.
Gdy RELEASE_AIDL_USE_UNFROZEN
ma wartość false
, Cuttlefish ma poprzednie wartości target-level
i PRODUCT_SHIPPING_API_LEVEL
, aby odzwierciedlać urządzenie w wersji produkcyjnej.
W Androidzie 14 i starszych wersjach to rozróżnienie byłoby realizowane za pomocą różnych gałęzi Git, które nie uwzględniają zmiany w FCMtarget-level
, poziomu interfejsu API dostawy ani żadnego innego kodu kierowanego na następną wersję.
Reguły nadawania nazw modułom
W Androidzie 11 dla każdej kombinacji wersji i włączonych backendów automatycznie tworzony jest moduł biblioteki stub. Aby odwołać się do konkretnego modułu biblioteki zastępczej na potrzeby łączenia, nie używaj nazwy modułu aidl_interface
, ale nazwy modułu biblioteki zastępczej, czyli ifacename-version-backend, gdzie
ifacename
: nazwa modułuaidl_interface
version
to jedna z tych wartości:Vversion-number
w przypadku wersji zamrożonych.Vlatest-frozen-version-number + 1
w przypadku wersji tip-of-tree (jeszcze niezamrożonej);
backend
to jedna z tych wartości:java
w przypadku backendu Java,cpp
w przypadku backendu C++,ndk
lubndk_platform
w przypadku backendu NDK. Pierwsza z nich dotyczy aplikacji, a druga – korzystania z platformy do Androida 13. Na urządzeniach z Androidem 13 lub nowszym używaj tylkondk
.rust
w przypadku backendu Rust.
Załóżmy, że istnieje moduł o nazwie foo, którego najnowsza wersja to 2, i obsługuje on zarówno NDK, jak i C++. W takim przypadku AIDL generuje te moduły:
- Na podstawie wersji 1
foo-V1-(java|cpp|ndk|ndk_platform|rust)
- Na podstawie wersji 2 (najnowszej wersji stabilnej)
foo-V2-(java|cpp|ndk|ndk_platform|rust)
- Na podstawie wersji ToT
foo-V3-(java|cpp|ndk|ndk_platform|rust)
W porównaniu z Androidem 11:
foo-backend
, która odnosiła się do najnowszej stabilnej wersji, staje sięfoo-V2-backend
foo-unstable-backend
, która odnosiła się do wersji ToT staje sięfoo-V3-backend
Nazwy plików wyjściowych są zawsze takie same jak nazwy modułów.
- Na podstawie wersji 1:
foo-V1-(cpp|ndk|ndk_platform|rust).so
- Na podstawie wersji 2:
foo-V2-(cpp|ndk|ndk_platform|rust).so
- Na podstawie wersji ToT:
foo-V3-(cpp|ndk|ndk_platform|rust).so
Pamiętaj, że kompilator AIDL nie tworzy modułu unstable
wersji ani modułu bez wersji dla stabilnego interfejsu AIDL.
Od Androida 12 nazwa modułu generowana na podstawie stabilnego interfejsu AIDL zawsze zawiera jego wersję.
Nowe metody interfejsu meta
Android 10 dodaje kilka metod interfejsu meta dla stabilnego AIDL.
Wysyłanie zapytań o wersję interfejsu obiektu zdalnego
Klienci mogą wysyłać zapytania o wersję i hash interfejsu, który implementuje obiekt zdalny, i porównywać zwrócone wartości z wartościami interfejsu, którego używa klient.
Przykład z backendem cpp
:
sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
// the remote side is using an older interface
}
std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();
Przykład z backendem ndk
(i ndk_platform
):
IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
// the remote side is using an older interface
}
std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);
Przykład z backendem java
:
IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
// the remote side is using an older interface
}
String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();
W przypadku języka Java strona zdalna MUSI implementować getInterfaceVersion()
i getInterfaceHash()
w ten sposób (super
jest używane zamiast IFoo
, aby uniknąć błędów podczas kopiowania i wklejania): Adnotacja @SuppressWarnings("static")
może być potrzebna do wyłączenia ostrzeżeń w zależności od konfiguracji javac
:
class MyFoo extends IFoo.Stub {
@Override
public final int getInterfaceVersion() { return super.VERSION; }
@Override
public final String getInterfaceHash() { return super.HASH; }
}
Dzieje się tak, ponieważ wygenerowane klasy (IFoo
, IFoo.Stub
itp.) są współdzielone między klientem a serwerem (np. klasy mogą znajdować się w ścieżce klasy rozruchowej). Gdy klasy są udostępniane, serwer jest też połączony z najnowszą wersją klas, mimo że mógł zostać utworzony przy użyciu starszej wersji interfejsu. Jeśli ten interfejs metadanych jest zaimplementowany w udostępnionej klasie, zawsze zwraca najnowszą wersję. Jednak dzięki wdrożeniu metody w sposób opisany powyżej numer wersji interfejsu jest osadzony w kodzie serwera (ponieważ IFoo.VERSION
to static final int
, który jest wstawiany w miejscu odwołania), a tym samym metoda może zwracać dokładną wersję, z której serwer został zbudowany.
Obsługa starszych interfejsów
Może się zdarzyć, że klient zostanie zaktualizowany do nowszej wersji interfejsu AIDL, ale serwer będzie używać starego interfejsu AIDL. W takich przypadkach wywołanie metody w starym interfejsie zwraca wartość UNKNOWN_TRANSACTION
.
W przypadku stabilnego AIDL klienci mają większą kontrolę. Po stronie klienta możesz ustawić domyślną implementację interfejsu AIDL. Metoda w domyślnej implementacji jest wywoływana tylko wtedy, gdy nie jest zaimplementowana po stronie zdalnej (ponieważ została utworzona w starszej wersji interfejsu). Domyślne wartości są ustawiane globalnie, więc nie należy ich używać w potencjalnie współdzielonych kontekstach.
Przykład w C++ na Androidzie 13 i nowszym:
class MyDefault : public IFooDefault {
Status anAddedMethod(...) {
// do something default
}
};
// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());
foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
// remote side is not implementing it
Przykład w Javie:
IFoo.Stub.setDefaultImpl(new IFoo.Default() {
@Override
public xxx anAddedMethod(...) throws RemoteException {
// do something default
}
}); // once per an interface in a process
foo.anAddedMethod(...);
Nie musisz podawać domyślnej implementacji wszystkich metod w interfejsie AIDL. Metody, które na pewno zostaną zaimplementowane po stronie zdalnej (ponieważ masz pewność, że zdalny interfejs został utworzony, gdy metody były w opisie interfejsu AIDL), nie muszą być zastępowane w domyślnej klasie impl
.
Konwertowanie istniejącego AIDL na strukturalny lub stabilny AIDL
Jeśli masz już interfejs AIDL i kod, który go używa, wykonaj te czynności, aby przekonwertować interfejs na stabilny interfejs AIDL.
Określ wszystkie zależności interfejsu. W przypadku każdego pakietu, od którego zależy interfejs, sprawdź, czy jest on zdefiniowany w stabilnym AIDL. Jeśli nie jest zdefiniowany, pakiet musi zostać przekonwertowany.
Przekonwertuj wszystkie obiekty Parcelable w interfejsie na stabilne obiekty Parcelable (same pliki interfejsu mogą pozostać bez zmian). Możesz to zrobić, wyrażając ich strukturę bezpośrednio w plikach AIDL. Klasy zarządzania muszą zostać przepisane, aby korzystać z tych nowych typów. Możesz to zrobić przed utworzeniem pakietu
aidl_interface
(poniżej).Utwórz
aidl_interface
pakiet (jak opisano powyżej), który zawiera nazwę modułu, jego zależności i wszelkie inne potrzebne informacje. Aby była stabilna (a nie tylko uporządkowana), musi być też wersjonowana. Więcej informacji znajdziesz w artykule Wersje interfejsów.