W systemie Android 10 dodano obsługę stabilnego języka definicji interfejsu systemu Android (AIDL), nowego sposobu śledzenia interfejsu programu aplikacji (API)/interfejsu binarnego aplikacji (ABI) udostępnianego przez interfejsy AIDL. Stabilny AIDL ma następujące kluczowe różnice w stosunku do AIDL:
- Interfejsy są zdefiniowane w systemie kompilacji za pomocą
aidl_interfaces
. - Interfejsy mogą zawierać tylko dane strukturalne. Przesyłki reprezentujące żądane typy są tworzone automatycznie na podstawie ich definicji AIDL i są automatycznie porządkowane i nieorganizowane.
- Interfejsy można zadeklarować jako stabilne (kompatybilne wstecz). Kiedy tak się dzieje, ich interfejs API jest śledzony i wersjonowany w pliku obok interfejsu AIDL.
Strukturalny i stabilny AIDL
Strukturalny AIDL odnosi się do typów zdefiniowanych wyłącznie w AIDL. Na przykład deklaracja umożliwiająca pakowanie (niestandardowa możliwość pakowania) nie jest ustrukturyzowanym AIDL. Przesyłki posiadające pola zdefiniowane w AIDL nazywane są przesyłkami strukturalnymi .
Stabilny AIDL wymaga ustrukturyzowanego AIDL, aby system kompilacji i kompilator mogły zrozumieć, czy zmiany wprowadzone w pakietach są kompatybilne wstecz. Jednak nie wszystkie interfejsy strukturalne są stabilne. Aby interfejs był stabilny, musi używać wyłącznie typów strukturalnych, a także musi korzystać z następujących funkcji wersjonowania. I odwrotnie, interfejs nie jest stabilny, jeśli do jego zbudowania użyto podstawowego systemu kompilacji lub jeśli ustawiono unstable:true
.
Definiowanie interfejsu AIDL
Definicja aidl_interface
wygląda następująco:
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óry jednoznacznie identyfikuje interfejs AIDL. -
srcs
: Lista plików źródłowych AIDL tworzących interfejs. Ścieżka dla typu AIDLFoo
zdefiniowanego w pakieciecom.acme
powinna znajdować się pod<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 powyższym przykładzie<base_path>
tosrcs/aidl
. -
local_include_dir
: Ścieżka, od której zaczyna się nazwa pakietu. Odpowiada<base_path>
wyjaśnionej powyżej. -
imports
: Lista modułówaidl_interface
, których używa. Jeśli jeden z twoich interfejsów AIDL korzysta z interfejsu lub pakietu z innegoaidl_interface
, wpisz tutaj jego nazwę. Może to być sama nazwa, odnosząca się do najnowszej wersji, lub nazwa z przyrostkiem wersji (np.-V1
), odnosząca się do konkretnej wersji. Określanie wersji jest obsługiwane od wersji Androida 12 -
versions
: poprzednie wersje interfejsu, które są zamrożone wapi_dir
. Począwszy od Androida 11,versions
są zamrożone waidl_api/ name
. Jeśli nie ma zamrożonych wersji interfejsu, nie należy tego określać i nie będzie sprawdzania zgodności. To pole zostało zastąpioneversions_with_info
dla wersji 13 i nowszych. -
versions_with_info
: Lista krotek, z których każda zawiera nazwę zamrożonej wersji oraz listę importowanych wersji innych modułów interfejsu AIDS zaimportowanych przez tę wersję interfejsu AIDS. Definicja wersji V interfejsu AIDL IFACE znajduje się podaidl_api/ IFACE / V
. Pole to zostało wprowadzone w Androidzie 13 i nie powinno być modyfikowane bezpośrednio w Android.bp. Pole dodaje się lub aktualizuje poprzez wywołanie*-update-api
lub*-freeze-api
. Ponadto polaversions
są automatycznie migrowane doversions_with_info
, gdy użytkownik wywoła*-update-api
lub*-freeze-api
. -
stability
: Opcjonalna flaga zapewniająca stabilność tego interfejsu. Obecnie obsługuje tylko"vintf"
. Jeśli ta opcja nie jest ustawiona, odpowiada to interfejsowi ze stabilnością w tym kontekście kompilacji (więc załadowanego tutaj interfejsu można używać tylko z rzeczami skompilowanymi razem, na przykład w pliku system.img). Jeśli jest ustawione na"vintf"
, odpowiada to obietnicy stabilności: interfejs musi być stabilny tak długo, jak jest używany. -
gen_trace
: Opcjonalna flaga umożliwiająca włączenie lub wyłączenie śledzenia. Począwszy od Androida 14, wartośćtrue
dotyczy backendówcpp
ijava
. -
host_supported
: opcjonalna flaga, która po ustawieniu natrue
udostępnia wygenerowane biblioteki środowisku hosta. -
unstable
: Opcjonalna flaga używana do zaznaczenia, że ten interfejs nie musi być stabilny. Gdy ta opcja jest ustawiona natrue
, system kompilacji nie tworzy zrzutu interfejsu API dla interfejsu ani nie wymaga jego aktualizacji. -
frozen
: Opcjonalna flaga, której ustawienie natrue
oznacza, że w interfejsie nie wprowadzono żadnych zmian od poprzedniej wersji interfejsu. Umożliwia to większą liczbę kontroli w czasie kompilacji. Ustawienie wartościfalse
oznacza, że interfejs jest w fazie rozwoju i wprowadzane są w nim nowe zmiany, więc uruchomieniefoo-freeze-api
wygeneruje nową wersję i automatycznie zmieni wartość natrue
. Wprowadzono w Androidzie 14. -
backend.<type>.enabled
: Te flagi przełączają każdy z backendów, dla których kompilator AIDL generuje kod. Obecnie obsługiwane są cztery backendy: Java, C++, NDK i Rust. Zaplecze Java, C++ i NDK są domyślnie włączone. Jeśli którykolwiek z tych trzech backendów nie jest potrzebny, należy go jawnie wyłączyć. Rdza jest domyślnie wyłączona. -
backend.<type>.apex_available
: Lista nazw APEX, dla których dostępna jest wygenerowana biblioteka pośrednicząca. -
backend.[cpp|java].gen_log
: Opcjonalna flaga określająca, czy generować dodatkowy kod w celu gromadzenia informacji o transakcji. -
backend.[cpp|java].vndk.enabled
: Opcjonalna flaga czyniąca ten interfejs częścią VNDK. Wartość domyślna tofalse
. -
backend.[cpp|ndk].additional_shared_libraries
: wprowadzona w systemie Android 14, ta flaga dodaje zależności do bibliotek natywnych. Ta flaga jest użyteczna zndk_header
icpp_header
. -
backend.java.sdk_version
: opcjonalna flaga określająca wersję pakietu SDK, na podstawie którego zbudowana jest biblioteka pośrednicząca języka Java. Wartość domyślna to"system_current"
. Nie należy tego ustawiać, jeślibackend.java.platform_apis
ma wartość true. -
backend.java.platform_apis
: opcjonalna flaga, która powinna być ustawiona natrue
, gdy wygenerowane biblioteki muszą być kompilowane w oparciu o interfejs API platformy, a nie zestaw SDK.
Dla każdej kombinacji wersji i włączonych backendów tworzona jest biblioteka pośrednicząca. Aby dowiedzieć się, jak odwoływać się do konkretnej wersji biblioteki pośredniczącej dla konkretnego backendu, zobacz Reguły nazewnictwa modułów .
Zapisywanie plików AIDL
Interfejsy w stabilnym AIDL są podobne do tradycyjnych interfejsów, z tą różnicą, że nie wolno w nich używać nieustrukturyzowanych pakietów (ponieważ nie są one stabilne! zobacz Strukturalny a stabilny AIDL ). Podstawową różnicą w stabilnym AIDL jest sposób definiowania przesyłek. Wcześniej przesyłki kurierskie były deklarowane jako forward ; w stabilnym (a zatem ustrukturyzowanym) AIDL pola i zmienne parcelable są zdefiniowane jawnie.
// in a file like 'some/package/Thing.aidl'
package some.package;
parcelable SubThing {
String a = "foo";
int b;
}
Wartość domyślna jest obecnie obsługiwana (ale nie wymagana) dla boolean
, char
, float
, double
, byte
, int
, long
i String
. W systemie Android 12 obsługiwane są także wartości domyślne dla wyliczeń zdefiniowanych przez użytkownika. Jeśli nie określono wartości domyślnej, używana jest wartość podobna do 0 lub pusta. Wyliczenia bez wartości domyślnej są inicjowane na 0, nawet jeśli nie ma modułu wyliczającego zero.
Korzystanie z bibliotek pośredniczących
Po dodaniu bibliotek pośredniczących jako zależności do modułu, możesz dołączyć je do swoich plików. Oto przykłady bibliotek pośredniczących w systemie kompilacji ( Android.mk
może być również używany w przypadku starszych definicji modułów):
cc_... {
name: ...,
shared_libs: ["my-module-name-cpp"],
...
}
# or
java_... {
name: ...,
// can also be shared_libs if desire is to load a library and share
// it among multiple users or if you only need access to constants
static_libs: ["my-module-name-java"],
...
}
# or
rust_... {
name: ...,
rust_libs: ["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
Wersjonowanie interfejsów
Zadeklarowanie modułu o nazwie foo tworzy również element docelowy w systemie kompilacji, którego można użyć do zarządzania interfejsem API modułu. Po zbudowaniu foo-freeze-api dodaje nową definicję API w api_dir
lub aidl_api/ name
, w zależności od wersji Androida, i dodaje plik .hash
, oba reprezentujące nowo zamrożoną wersję interfejsu. foo-freeze-api aktualizuje również versions_with_info
, aby odzwierciedlić dodatkową wersję i imports
dla tej wersji. Zasadniczo imports
w versions_with_info
jest kopiowany z pola imports
. Jednak najnowsza stabilna wersja jest określona w imports
versions_with_info
dla importu, który nie ma jawnej wersji. Po określeniu właściwości versions_with_info
system kompilacji sprawdza kompatybilność między wersjami zamrożonymi, a także między wersją Top of Tree (ToT) a najnowszą wersją zamrożoną.
Dodatkowo musisz zarządzać definicją API wersji ToT. Za każdym razem, gdy interfejs API jest aktualizowany, uruchom foo-update-api , aby zaktualizować aidl_api/ name /current
, który zawiera definicję API wersji ToT.
Aby zachować stabilność interfejsu, właściciele mogą dodać nowe:
- Metody na koniec interfejsu (lub metody z jawnie zdefiniowanymi nowymi serialami)
- Elementy na końcu działki (wymaga dodania wartości domyślnej dla każdego elementu)
- Wartości stałe
- W Androidzie 11 moduły wyliczające
- W Androidzie 12 pola na końcu związku
Żadne inne działania nie są dozwolone i nikt inny nie może modyfikować interfejsu (w przeciwnym razie grozi to kolizją ze zmianami wprowadzonymi przez właściciela).
Aby sprawdzić, czy wszystkie interfejsy zostały zamrożone przed publikacją, możesz zbudować wersję z ustawionymi następującymi zmiennymi środowiskowymi:
-
AIDL_FROZEN_REL=true m ...
- kompilacja wymaga zamrożenia wszystkich stabilnych interfejsów AIDL, które nie mająowner:
pole określone. -
AIDL_FROZEN_OWNERS="aosp test"
- kompilacja wymaga zamrożenia wszystkich stabilnych interfejsów AIDL zowner:
pole określone jako "aosp" lub "test".
Stabilność importu
Aktualizowanie wersji importów dla zamrożonych wersji interfejsu jest kompatybilne wstecz w warstwie stabilnej AIDL. Jednak ich aktualizacja wymaga aktualizacji wszystkich serwerów i klientów korzystających ze starej wersji interfejsu, a niektóre aplikacje mogą się mylić podczas mieszania różnych wersji typów. Ogólnie rzecz biorąc, w przypadku pakietów zawierających tylko typy lub pakietów wspólnych jest to bezpieczne, ponieważ kod musi już zostać napisany, aby obsługiwać nieznane typy z transakcji IPC.
W platformie Android kod android.hardware.graphics.common
jest największym przykładem tego typu aktualizacji wersji.
Korzystanie z wersjonowanych interfejsów
Metody interfejsu
Podczas próby wywołania nowych metod na starym serwerze w czasie wykonywania nowi klienci otrzymują błąd lub wyjątek, w zależności od zaplecza.
- Zaplecze
cpp
pobiera::android::UNKNOWN_TRANSACTION
. - Zaplecze
ndk
pobieraSTATUS_UNKNOWN_TRANSACTION
. - Backend
java
otrzymujeandroid.os.RemoteException
z komunikatem informującym, że interfejs API nie jest zaimplementowany.
Aby zapoznać się ze strategiami radzenia sobie z tym, zobacz odpytywanie wersji i używanie ustawień domyślnych .
Przesyłki
Kiedy do przesyłek dodawane są nowe pola, starzy klienci i serwery je usuwają. Gdy nowi klienci i serwery otrzymają stare przesyłki, domyślne wartości nowych pól zostaną automatycznie wypełnione. Oznacza to, że należy określić wartości domyślne dla wszystkich nowych pól przesyłki.
Klienci nie powinni oczekiwać, że serwery będą używać nowych pól, chyba że wiedzą, że serwer implementuje wersję, w której zdefiniowano pole (patrz wersje zapytań ).
Wyliczenia i stałe
Podobnie klienci i serwery powinni odpowiednio odrzucać lub ignorować nierozpoznane wartości stałe i moduły wyliczające, ponieważ w przyszłości mogą zostać dodane kolejne. Na przykład serwer nie powinien przerywać działania po odebraniu modułu wyliczającego, o którym nie wie. Powinien to zignorować lub zwrócić coś, aby klient wiedział, że nie jest obsługiwany w tej implementacji.
Związki
Próba wysłania unii z nowym polem kończy się niepowodzeniem, jeśli odbiorca jest stary i nie wie o tym polu. Implementacja nigdy nie spowoduje połączenia z nowym polem. Niepowodzenie jest ignorowane, jeśli jest to transakcja jednokierunkowa; w przeciwnym razie błąd to BAD_VALUE
(dla zaplecza C++ lub NDK) lub IllegalArgumentException
(dla zaplecza Java). Błąd pojawia się, jeśli klient wysyła zestaw unii do nowego pola na stary serwer lub gdy jest to stary klient odbierający unię z nowego serwera.
Rozwój oparty na flagach
Interfejsów w fazie rozwoju (niezamrożonych) nie można używać na urządzeniach w wersji, ponieważ nie ma gwarancji, że będą one kompatybilne wstecz.
AIDL obsługuje rezerwę czasu wykonywania dla tych niezamrożonych bibliotek interfejsów, aby kod mógł zostać napisany w oparciu o najnowszą niezamrożoną wersję i nadal mógł być używany na urządzeniach wydających. Zachowanie klientów zgodne wstecznie jest podobne do istniejącego zachowania, a w przypadku powrotu implementacje również muszą podążać za tymi zachowaniami. Zobacz Korzystanie z interfejsów wersjonowanych .
Flaga kompilacji AIDL
Flaga kontrolująca to zachowanie to RELEASE_AIDL_USE_UNFROZEN
zdefiniowana w build/release/build_flags.bzl
. true
oznacza, że w czasie wykonywania używana jest niezamrożona wersja interfejsu, a false
oznacza, że wszystkie biblioteki niezamrożonych wersji zachowują się jak ich ostatnia zamrożona wersja. Możesz zastąpić flagę true
dla lokalnego rozwoju, ale przed wydaniem musisz przywrócić ją do false
. Zazwyczaj programowanie odbywa się z konfiguracją, która ma flagę ustawioną na true
.
Macierz zgodności i manifesty
Obiekty interfejsu dostawcy (obiekty VINTF) definiują oczekiwane wersje i wersje udostępniane po obu stronach interfejsu dostawcy.
Większość urządzeń innych niż mątwy korzysta z najnowszej macierzy zgodności dopiero po zamrożeniu interfejsów, więc nie ma różnicy w bibliotekach AIDL opartych na RELEASE_AIDL_USE_UNFROZEN
.
Interfejsy należące do partnerów są dodawane do macierzy zgodności specyficznych dla urządzenia lub produktu, które są przeznaczone dla urządzenia podczas programowania. Zatem kiedy do macierzy kompatybilności dodawana jest nowa, niezamrożona wersja interfejsu, poprzednie zamrożone wersje muszą pozostać przez RELEASE_AIDL_USE_UNFROZEN=false
. Można sobie z tym poradzić, używając różnych plików macierzy zgodności dla różnych konfiguracji RELEASE_AIDL_USE_UNFROZEN
lub zezwalając na użycie obu wersji w jednym pliku macierzy zgodności, który jest używany we wszystkich konfiguracjach.
Na przykład, dodając niezamrożoną wersję 4, użyj <version>3-4</version>
.
Kiedy wersja 4 jest zablokowana, możesz usunąć wersję 3 z macierzy zgodności, ponieważ zamrożona wersja 4 jest używana, gdy RELEASE_AIDL_USE_UNFROZEN
ma false
.
Zmiany klienta HAL
Kod klienta HAL musi być wstecznie kompatybilny z każdą poprzednią obsługiwaną zamrożoną wersją. Gdy RELEASE_AIDL_USE_UNFROZEN
ma false
, usługi zawsze wyglądają jak ostatnia wersja zablokowana lub wcześniejsza (na przykład wywołanie nowych niezamrożonych metod zwraca UNKNOWN_TRANSACTION
lub nowe pola parcelable
mają wartości domyślne). Klienci platformy Android muszą być kompatybilni wstecz z dodatkowymi poprzednimi wersjami, ale jest to nowy szczegół dla klientów dostawców i klientów interfejsów będących własnością partnerów.
Zmiany w implementacji HAL
Największą różnicą w rozwoju HAL w porównaniu z rozwojem opartym na flagach jest wymóg, aby implementacje HAL były kompatybilne wstecz z ostatnią zamrożoną wersją, aby działały, gdy RELEASE_AIDL_USE_UNFROZEN
ma false
. Uwzględnienie kompatybilności wstecznej w implementacjach i kodzie urządzenia to nowe ćwiczenie. Zobacz Korzystanie z interfejsów wersjonowanych .
Uwagi dotyczące kompatybilności wstecznej są generalnie takie same dla klientów i serwerów, a także dla kodu frameworka i kodu dostawcy, ale istnieją subtelne różnice, o których musisz wiedzieć, ponieważ teraz skutecznie wdrażasz dwie wersje korzystające z tego samego kodu źródłowego (aktualna, niezamrożona wersja).
Przykład: interfejs ma trzy zamrożone wersje. Interfejs został zaktualizowany o nową metodę. Zarówno klient, jak i usługa zostały zaktualizowane w celu korzystania z nowej biblioteki w wersji 4. Ponieważ biblioteka V4 bazuje na niezamrożonej wersji interfejsu, zachowuje się jak ostatnia zamrożona wersja, wersja 3, gdy RELEASE_AIDL_USE_UNFROZEN
ma false
i uniemożliwia użycie nowej metody.
Gdy interfejs jest zamrożony, wszystkie wartości RELEASE_AIDL_USE_UNFROZEN
korzystają z tej zamrożonej wersji, a kod obsługujący kompatybilność wsteczną może zostać usunięty.
Wywołując metody wywołań zwrotnych, należy elegancko obsłużyć przypadek, gdy zwrócona zostanie UNKNOWN_TRANSACTION
. Klienci mogą implementować dwie różne wersje wywołania zwrotnego w zależności od konfiguracji wydania, więc nie można zakładać, że klient wyśle najnowszą wersję, a nowe metody mogą to zwrócić. Jest to podobne do sposobu, w jaki stabilni klienci AIDL utrzymują kompatybilność wsteczną z serwerami, co opisano w sekcji Korzystanie z interfejsów wersjonowanych .
// 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ą odrzucane na końcu procesu.
Nowe typy dodane w tej niezamrożonej wersji nie mogą być wysyłane ani odbierane przez interfejs.
Implementacja nigdy nie otrzymuje wywołania nowych metod od żadnego klienta, gdy RELEASE_AIDL_USE_UNFROZEN
ma false
.
Uważaj, aby używać nowych modułów wyliczających tylko w wersji, w której zostały wprowadzone, a nie w poprzedniej wersji.
Zwykle używasz foo->getInterfaceVersion()
, aby zobaczyć, której wersji używa zdalny interfejs. Jednak w przypadku obsługi wersji opartej na flagach implementujesz dwie różne wersje, więc możesz chcieć uzyskać wersję bieżącego interfejsu. Możesz to zrobić, pobierając wersję interfejsu bieżącego obiektu, na przykład this->getInterfaceVersion()
lub inne metody dla my_ver
. Więcej informacji można znaleźć w sekcji Wykonywanie zapytań o wersję interfejsu obiektu zdalnego .
Nowe stabilne interfejsy VINTF
Po dodaniu nowego pakietu interfejsu AIDL nie ma ostatniej zamrożonej wersji, więc nie ma żadnego zachowania, do którego można by się odwołać, gdy RELEASE_AIDL_USE_UNFROZEN
ma false
. Nie używaj tych interfejsów. Gdy RELEASE_AIDL_USE_UNFROZEN
ma false
, Menedżer usług nie pozwoli usłudze zarejestrować interfejsu, a klienci go nie znajdą.
Możesz dodać usługi warunkowo w oparciu o wartość 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 i nie można jej warunkowo dodać do urządzenia, możesz sprawdzić, czy usługa jest zadeklarowana za pomocą IServiceManager::isDeclared()
. Jeśli został zadeklarowany i nie udało się go zarejestrować, przerwij proces. Jeśli nie zostanie zadeklarowany, oczekuje się, że nie zostanie zarejestrowany.
Mątwy jako narzędzie rozwojowe
Co roku po zamrożeniu VINTF dostosowujemy target-level
macierzy zgodności platformy (FCM) i PRODUCT_SHIPPING_API_LEVEL
mątwy, aby odzwierciedlały urządzenia, które zostaną wprowadzone na rynek wraz z przyszłoroczną premierą. Dostosowujemy target-level
i PRODUCT_SHIPPING_API_LEVEL
, aby upewnić się, że istnieje urządzenie uruchamiające, które zostało przetestowane i spełnia nowe wymagania dla przyszłorocznej wersji.
Gdy RELEASE_AIDL_USE_UNFROZEN
ma true
, mątwy są wykorzystywane do opracowywania przyszłych wydań Androida. Dotyczy poziomu FCM przyszłorocznej wersji Androida i PRODUCT_SHIPPING_API_LEVEL
, co wymaga spełnienia wymagań oprogramowania dostawcy (VSR) następnej wersji.
Gdy RELEASE_AIDL_USE_UNFROZEN
ma wartość false
, Mątwa ma poprzedni target-level
i PRODUCT_SHIPPING_API_LEVEL
odzwierciedlający urządzenie zwalniające. W systemie Android 14 i starszych wersjach tego rozróżnienia można dokonać za pomocą różnych gałęzi Git, które nie uwzględniają zmiany na target-level
FCM, poziomie interfejsu API wysyłki ani żadnym innym kodzie przeznaczonym dla następnej wersji.
Zasady nazewnictwa modułów
W systemie Android 11 dla każdej kombinacji wersji i włączonych backendów automatycznie tworzony jest moduł biblioteki pośredniczącej. Aby odwołać się do konkretnego modułu biblioteki pośredniczącej w celu łączenia, nie używaj nazwy modułu aidl_interface
, ale nazwę modułu biblioteki pośredniczącej, czyli ifacename - version - backend , gdzie
-
ifacename
: nazwa modułuaidl_interface
-
version
jest jedną z-
V version-number
dla wersji zamrożonych -
V latest-frozen-version-number + 1
dla wersji z wierzchołka drzewa (jeszcze do zamrożenia)
-
-
backend
to jedno z nich-
java
dla backendu Java, -
cpp
dla backendu C++, -
ndk
lubndk_platform
dla zaplecza NDK. Pierwsza przeznaczona jest do aplikacji, druga do korzystania z platformy, -
rust
dla backendu Rusta.
-
Załóżmy, że istnieje moduł o nazwie foo , a jego najnowsza wersja to 2 i obsługuje zarówno NDK, jak i C++. W tym przypadku AIDL generuje następujące moduły:
- Na podstawie wersji 1
-
foo-V1-(java|cpp|ndk|ndk_platform|rust)
-
- Na podstawie wersji 2 (najnowsza stabilna wersja)
-
foo-V2-(java|cpp|ndk|ndk_platform|rust)
-
- Na podstawie wersji ToT
-
foo-V3-(java|cpp|ndk|ndk_platform|rust)
-
W porównaniu do Androida 11,
-
foo- backend
, który odnosił się do najnowszej stabilnej wersji, staje sięfoo- V2 - backend
-
foo-unstable- backend
, który odnosił 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
Należy zauważyć, że kompilator AIDL nie tworzy ani modułu wersji unstable
, ani modułu niewersjonowanego dla stabilnego interfejsu AIDL. Począwszy od Androida 12, nazwa modułu wygenerowana ze stabilnego interfejsu AIDL zawsze zawiera jego wersję.
Nowe metody metainterfejsu
Android 10 dodaje kilka metod metainterfejsu dla stabilnego AIDL.
Zapytanie o wersję interfejsu obiektu zdalnego
Klienci mogą wysyłać zapytania o wersję i skrót interfejsu implementowanego przez obiekt zdalny i porównywać zwracane wartości z wartościami interfejsu używanego przez klienta.
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 zaimplementować getInterfaceVersion()
i getInterfaceHash()
w następujący sposób (zamiast IFoo
używa się super
, aby uniknąć błędów kopiowania/wklejania. Adnotacja @SuppressWarnings("static")
może być potrzebna do wyłączenia ostrzeżeń, w zależności od konfiguracja 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 pomiędzy klientem a serwerem (na przykład klasy mogą znajdować się w startowej ścieżce klas). Kiedy klasy są współdzielone, serwer jest również łączony z najnowszą wersją klas, mimo że mógł zostać zbudowany ze starszą wersją interfejsu. Jeśli ten metainterfejs jest zaimplementowany w klasie współdzielonej, zawsze zwraca najnowszą wersję. Jednakże, implementując metodę opisaną powyżej, numer wersji interfejsu jest osadzony w kodzie serwera (ponieważ IFoo.VERSION
jest static final int
, która jest wstawiana podczas odniesienia), dzięki czemu metoda może zwrócić dokładną wersję, w której serwer został zbudowany z.
Radzenie sobie ze starszymi interfejsami
Możliwe, że klient został zaktualizowany do nowszej wersji interfejsu AIDL, ale serwer używa starego interfejsu AIDL. W takich przypadkach wywołanie metody na starym interfejsie zwraca UNKNOWN_TRANSACTION
.
Dzięki stabilnemu 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 metoda nie jest zaimplementowana po stronie zdalnej (ponieważ została zbudowana ze starszą wersją interfejsu). Ponieważ wartości domyślne są ustawiane globalnie, nie należy ich używać w potencjalnie współdzielonych kontekstach.
Przykład w C++ w systemie Android 13 i nowszych wersjach:
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 został zbudowany, gdy metody znajdowały się w opisie interfejsu AIDL) nie muszą być nadpisywane w domyślnej klasie impl
.
Konwersja istniejącego AIDL na ustrukturyzowany/stabilny AIDL
Jeśli masz istniejący interfejs AIDL i kod, który go używa, wykonaj poniższe kroki, aby przekonwertować interfejs na stabilny interfejs AIDL.
Zidentyfikuj wszystkie zależności swojego interfejsu. Dla każdego pakietu, od którego zależy interfejs, określ, czy pakiet jest zdefiniowany w stabilnym AIDL. Jeśli nie zdefiniowano, pakiet musi zostać przekonwertowany.
Konwertuj wszystkie paczki w swoim interfejsie na stabilne paczki (same pliki interfejsu mogą pozostać niezmienione). Zrób to, wyrażając ich strukturę bezpośrednio w plikach AIDL. Aby korzystać z tych nowych typów, należy przepisać klasy zarządzania. Można to zrobić przed utworzeniem pakietu
aidl_interface
(poniżej).Utwórz pakiet
aidl_interface
(jak opisano powyżej), który zawiera nazwę modułu, jego zależności i wszelkie inne potrzebne informacje. Aby był ustabilizowany (a nie tylko ustrukturyzowany), należy go również wersjonować. Aby uzyskać więcej informacji, zobacz Interfejsy wersjonowania .