Android 10 obsługuje stabilną wersję języka interfejsu AIDL (Android Interface Definition Language). Jest to nowy sposób śledzenia interfejsu programowania aplikacji (API) i interfejsu binarnego aplikacji (ABI) udostępnianego przez interfejsy AIDL. Stabilna wersja AIDL działa dokładnie tak samo jak AIDL, ale system kompilacji śledzi zgodność interfejsu, a Twoje możliwości są ograniczone:
- Interfejsy są zdefiniowane w systemie kompilacji za pomocą
aidl_interfaces
. - Interfejsy mogą zawierać tylko uporządkowane dane. Obiekty Parcelables reprezentujące preferowane typy są tworzone automatycznie na podstawie ich definicji w AIDL i automatycznie pakowane oraz rozpakowywane.
- Interfejsy mogą być deklarowane jako stabilne (zgodność wsteczna). W takim przypadku ich interfejs API jest śledzony i numerowany w pliku obok interfejsu AIDL.
Uporządkowane a stabilne AIDL
Uporządkowany AIDL odnosi się do typów zdefiniowanych wyłącznie w AIDL. Na przykład deklaracja parcelable (niestandardowa parcelable) nie jest uporządkowanym interfejsem AIDL. Materiały Parcelable z polami zdefiniowanymi w AIDL są nazywane elementami strukturalnymi.
Stabilna AIDL wymaga uporządkowanych danych AIDL, tak aby system kompilacji i kompilator mógł rozpoznać, czy zmiany wprowadzone w plikach parcelable są zgodne wstecznie.
Nie wszystkie interfejsy uporządkowane są jednak stabilne. Aby zapewnić stabilność, interfejs musi używać tylko typów ustrukturyzowanych. Musi też korzystać z tych funkcji wersji: Z drugiej strony, interfejs nie jest stabilny, jeśli do jego kompilacji używany jest podstawowy system kompilacji lub ustawiona jest opcja 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óry jednoznacznie identyfikuje interfejs AIDL.srcs
: lista plików źródłowych AIDL, które składają się na interfejs. Ścieżka do pliku AIDL typuFoo
zdefiniowanego w pakieciecom.acme
powinna znajdować się w katalogu<base_path>/com/acme/Foo.aidl
, gdzie<base_path>
może być dowolnym katalogiem powiązanym z katalogiem, w którym znajduje się plikAndroid.bp
. W powyższym przykładzie<base_path>
tosrcs/aidl
.local_include_dir
: ścieżka, od której zaczyna się nazwa pakietu. Odpowiada ona<base_path>
opisanej powyżej.imports
: lista modułówaidl_interface
, których używa ta funkcja. Jeśli jeden z interfejsów AIDL używa interfejsu lub obiektu Parcelable z innego interfejsuaidl_interface
, podaj jego nazwę tutaj. Może to być sama nazwa, która odnosi się do najnowszej wersji, lub nazwa z sufiksem wersji (np.-V1
) odnosząca się do konkretnej wersji. Określanie wersji jest obsługiwane od Androida 12.versions
: poprzednie wersje interfejsu, które są zamrożone w sekcjiapi_dir
. Począwszy od Androida 11,versions
są zamrożone w sekcjiaidl_api/name
. Jeśli nie ma żadnych zamrożonych wersji interfejsu, nie należy tego określać, a sprawdzanie zgodności nie będzie przeprowadzane. W Androidzie 13 i nowszych to pole zostało zastąpione polemversions_with_info
.versions_with_info
: lista tupla, z których każdy zawiera nazwę zamrożonej wersji i listę importowanych wersji innych modułów aidl_interface, które zostały zaimportowane przez tę wersję interfejsu aidl_interface. Definicja wersji V interfejsu IFACE interfejsu AIDL znajduje się na stronieaidl_api/IFACE/V
. To pole zostało wprowadzone w Androidzie 13 i nie należy go modyfikować bezpośrednio wAndroid.bp
. Pole jest dodawane lub aktualizowane przez wywołanie*-update-api
lub*-freeze-api
. Ponadto polaversions
są automatycznie przenoszone do polaversions_with_info
, gdy użytkownik wywoła funkcję*-update-api
lub*-freeze-api
.stability
: opcjonalna flaga gwarantująca stabilność tego interfejsu. Ta opcja obsługuje tylko"vintf"
. Jeśli zasadastability
nie jest skonfigurowana, system kompilacji sprawdza, czy interfejs jest zgodny wstecznie, chyba że podano parametrunstable
. Stan „unset” odpowiada interfejsowi ze stabilnością w kontekście tej kompilacji (czyli wszystkie rzeczy systemowe, na przykład rzeczy wsystem.img
i powiązanych partycjach, lub wszystkie rzeczy dostawcy, na przykład rzeczy wvendor.img
i powiązanych partycjach). Jeślistability
ma wartość"vintf"
, oznacza to obietnicę stabilności: interfejs musi być stabilny przez cały czas jego używania.gen_trace
: opcjonalna flaga włączająca lub wyłączająca śledzenie. Począwszy od Androida 14 domyślnie jest totrue
w przypadku backendówcpp
ijava
.host_supported
: opcjonalna flaga, która w przypadku ustawienia na wartośćtrue
udostępnia wygenerowane biblioteki w środowisku hosta.unstable
: opcjonalna flaga służąca do oznaczania interfejsu, że nie musi być on stabilny. Gdy ta opcja ma wartośćtrue
, system kompilacji nie tworzy kopii zapasowej interfejsu API ani nie wymaga jej aktualizacji.frozen
: opcjonalna flaga, która po ustawieniu natrue
oznacza, że interfejs nie zawiera żadnych zmian w porównaniu z poprzednią wersją interfejsu. Umożliwia to więcej kontroli w czasie kompilacji. Gdy wartość tofalse
, oznacza to, że interfejs jest w trakcie tworzenia i zawiera nowe zmiany. Uruchomieniefoo-freeze-api
spowoduje wygenerowanie nowej wersji i automatyczną zmianę wartości natrue
. Wprowadzona w Androidzie 14.backend.<type>.enabled
: te flagi przełączają każdy z backendów, dla których kompilator AIDL generuje kod. Obsługiwane są 4 backendy: Java, C++, NDK i Rust. Java, C++ i NDK są domyślnie włączone. Jeśli któryś z tych 3 systemów backendowych nie jest potrzebny, musisz go wyłączyć. Domyślnie funkcja Rust jest wyłączona do Androida 15.backend.<type>.apex_available
: lista nazw APEX, dla których dostępna jest wygenerowana biblioteka zastępcza.backend.[cpp|java].gen_log
: opcjonalna flaga, która określa, czy ma zostać wygenerowany dodatkowy kod do zbierania informacji o transakcji.backend.[cpp|java].vndk.enabled
: opcjonalna flaga, która powoduje, że ten interfejs jest częścią VNDK. Wartość domyślna tofalse
.backend.[cpp|ndk].additional_shared_libraries
: ta flaga została wprowadzona w Androidzie 14 i 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, dla której została skompilowana biblioteka stubów Javy. Wartość domyślna to"system_current"
. Nie należy go ustawiać, gdybackend.java.platform_apis
ma wartośćtrue
.backend.java.platform_apis
: opcjonalna flaga, która powinna być ustawiona natrue
, gdy wygenerowane biblioteki muszą być kompilowane na platformie API, a nie na pakiecie SDK.
W przypadku każdej kombinacji wersji i włączonych backendów tworzona jest biblioteka szablonów. Informacje o tym, jak odwoływać się do konkretnej wersji biblioteki zasobów dla konkretnego backendu, znajdziesz w sekcji Zasady nazewnictwa modułów.
Tworzenie plików AIDL
Interfejsy w ramach stabilnej wersji interfejsu AIDL są podobne do tradycyjnych interfejsów, z tym wyjątkiem, że nie można w nich używać nieuporządkowanych obiektów Parcelable (ponieważ nie są one stabilne; zobacz Różnica między uporządkowanymi a stabilnymi interfejsami AIDL). Główna różnica w stabilnej wersji AIDL dotyczy sposobu definiowania obiektów Parcelable. Wcześniej obiekty parcelable były deklarowane w przód. W stabilnym (a zatem ustrukturyzowanym) pliku AIDL pola i zmienna parcelable są definiowane wprost.
// in a file like 'some/package/Thing.aidl'
package some.package;
parcelable SubThing {
String a = "foo";
int b;
}
Wartości domyślne są obsługiwane (ale nie są wymagane) w przypadku pól boolean
, char
, float
, double
, byte
, int
, long
i String
. W Androidzie 12 obsługiwane są też domyślne wartości typów 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 żadnych zerowych wyliczników.
Korzystanie z bibliotek z stubami
Po dodaniu bibliotek zastępczych jako zależności do modułu możesz je uwzględnić w plikach. Oto przykłady bibliotek wersji pośredniej w systemie kompilacji (Android.mk
może być też używany do starszych definicji modułów):
cc_... {
name: ...,
shared_libs: ["my-module-name-cpp"],
...
}
# or
java_... {
name: ...,
// can also be shared_libs if your preference 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: ...,
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 umożliwiającego zarządzanie jego interfejsem API. Po skompilowaniu foo-freeze-api dodaje nową definicję interfejsu API w folderze api_dir
lub aidl_api/name
(w zależności od wersji Androida) oraz dodaje 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. Pole imports
w dokumentach versions_with_info
jest kopiowane z pola imports
. Najnowsza stabilna wersja jest jednak określona w imports
w versions_with_info
dla importu, który nie ma wyraźnej wersji.
Po określeniu właściwości versions_with_info
system kompilacji przeprowadza sprawdzanie zgodności między zamrożonymi wersjami oraz między drzewem genealogicznym (ToT) a najnowszą zamrożoną wersją.
Musisz też zarządzać definicją interfejsu API wersji ToT. Po każdej aktualizacji interfejsu API uruchom polecenie foo-update-api, aby zaktualizować aidl_api/name/current
, który zawiera definicję interfejsu API wersji ToT.
Aby zapewnić stabilność interfejsu, właściciele mogą dodawać nowe:
- metody do końca interfejsu (lub metody z wyraźnie zdefiniowanymi nowymi serialami);
- Elementy na końcu obiektu Parcelable (wymaga dodania domyślnego elementu dla każdego elementu)
- Wartości stałe
- W Androidzie 11:
- W Androidzie 12 pola do końca sumy
Nie są dozwolone żadne inne działania, a nikt inny nie może modyfikować interfejsu (w przeciwnym razie może to spowodować konflikt z modyfikacjami wprowadzonymi przez właściciela).
Aby sprawdzić, czy wszystkie interfejsy są zamrożone do wydania, możesz utworzyć kompilację z 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 zamrożenia wszystkich stabilnych interfejsów AIDL, przy czym poleowner:
musi być określone jako „aosp” lub „test”.
Stabilność importu
Aktualizowanie wersji importów w zamrożonych wersjach interfejsu jest wstecznie zgodne na poziomie stabilnego AIDL. Jednak ich aktualizacja wymaga zaktualizowania wszystkich serwerów i klientów, które korzystają z poprzedniej wersji interfejsu. Niektóre aplikacje mogą się mylić, gdy mieszają różne wersje typów. Ogólnie rzecz biorąc, w przypadku typowych lub przeznaczonych tylko typów pakietów jest to bezpieczne, ponieważ trzeba już wcześniej napisać kod do obsługi nieznanych typów transakcji IPC.
Największym przykładem tego typu
uaktualnienia wersji jest kod android.hardware.graphics.common
na Androidzie.
Korzystanie z interfejsów z wersjami
Metody interfejsu
Podczas działania, gdy nowi klienci próbują wywołać nowe metody na starym serwerze, otrzymują błąd lub wyjątek, w zależności od backendu.
cpp
backend dostaje::android::UNKNOWN_TRANSACTION
.ndk
backend dostajeSTATUS_UNKNOWN_TRANSACTION
.- Backend
java
otrzymujeandroid.os.RemoteException
z komunikatem informującym, że interfejs API nie został zaimplementowany.
Strategie radzenia sobie z tym problemem znajdziesz w artykułach zapytania o wersje i używanie domyślnych wartości.
Parcelables
Gdy do obiektów parcelable zostaną dodane nowe pola, stare klienci i serwery je pominą. Gdy nowi klienci i serwery otrzymują stare obiekty parcelables, domyślne wartości nowych pól są automatycznie wypełniane. Oznacza to, że w przypadku wszystkich nowych pól w pakiecie 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 wyszukiwanie wersji).
Wyliczenia i stałe
Podobnie klienty i serwery powinny odrzucać lub ignorować nierozpoznane wartości stałe i elementy wyliczające, ponieważ w przyszłości mogą zostać dodane kolejne. Na przykład serwer nie powinien przerywać działania, gdy otrzyma licznik, którego nie zna. Serwer powinien zignorować enumerator lub zwrócić coś, aby klient wiedział, że ta implementacja nie obsługuje tej funkcji.
Związki
Próba wysłania sumy z nowym polem kończy się niepowodzeniem, jeśli odbiorca jest stary i nie wie nic o tym polu. Implementacja nigdy nie zobaczy zbioru z nowym polem. Błąd jest ignorowany, jeśli jest to transakcja jednokierunkowa. W przeciwnym razie błąd to BAD_VALUE
(w przypadku zaplecza C++ lub NDK) lub IllegalArgumentException
(w przypadku zaplecza Java). Błąd zostanie odebrany, gdy klient wysyła sumę ustawioną w nowym polu do starego serwera lub gdy jest to stary klient odbierający połączenie z nowego serwera.
Zarządzanie wieloma wersjami
Nazwa domeny linkera w Androidzie może mieć tylko jedną wersję konkretnego interfejsu aidl
, aby uniknąć sytuacji, w której wygenerowane typy aidl
mają wiele definicji. W języku C++ obowiązuje reguła jednej definicji, która wymaga tylko jednej definicji każdego symbolu.
Kompilacja Androida wyświetla błąd, gdy moduł zależy od różnych wersji tej samej biblioteki aidl_interface
. Moduł może być zależny od tych bibliotek bezpośrednio lub pośrednio przez zależności ich zależności. Te błędy pokazują wykres zależności od modułu, który nie działa, do wersji biblioteki aidl_interface
, które się wykluczają. Wszystkie zależności muszą zostać zaktualizowane, aby uwzględniały tę samą (zwykle najnowszą) wersję tych bibliotek.
Jeśli biblioteka interfejsu jest używana przez wiele różnych modułów, warto utworzyć zmienne cc_defaults
, java_defaults
i rust_defaults
dla każdej grupy bibliotek i procesów, które muszą używać tej samej wersji. Gdy wprowadzasz nową wersję interfejsu, te domyślne wartości mogą zostać zaktualizowane, a wszystkie moduły, które ich używają, zostaną zaktualizowane razem, aby nie korzystały 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
, powoduje to dodatkowe zależności, które wymagają używania określonych wersji. Ta sytuacja może być trudna do opanowania, gdy wspólne moduły aidl_interface
są importowane w wielu modułach aidl_interface
, które są używane razem w tych samych procesach.
aidl_interfaces_defaults
można użyć do zachowania 1 definicji najnowszych wersji zależności aidl_interface
, którą można aktualizować w jednym miejscu, i używać jej przez wszystkie moduły aidl_interface
, które chcą zaimportować 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 na podstawie flag
Interfejsów w trakcie tworzenia (niezamrożonych) nie można używać na urządzeniach w wersji produkcyjnej, ponieważ nie ma gwarancji, że są one zgodne ze starszymi wersjami.
AIDL obsługuje alternatywne wersje tych odmrożonych bibliotek interfejsu, aby kod napisany na podstawie najnowszej odmrożonej wersji mógł być nadal używany na urządzeniach z wersją opublikowaną. Wsteczna kompatybilność klientów jest podobna do dotychczasowego zachowania, a w przypadku implementacji zapasowych implementacje muszą również przestrzegać tych zachowań. Zobacz Używanie interfejsów z wersjonowaniem.
Flaga kompilacji AIDL
Flaga sterująca to zachowanie to RELEASE_AIDL_USE_UNFROZEN
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 wszystkich niezablokowanych wersji zachowują się tak samo jak ostatnio zablokowane wersje.
Na potrzeby programowania lokalnego możesz zmienić flagę na true
, ale przed opublikowaniem wersji musisz przywrócić ją do wartości false
. Zazwyczaj programowanie odbywa się w ramach konfiguracji z flagą ustawioną na true
.
Tablica zgodności i pliki manifestu
Obiekty interfejsu dostawcy (obiekty VINTF) określają, jakie wersje są oczekiwane i jakie wersje są udostępniane po obu stronach interfejsu dostawcy.
Większość urządzeń innych niż Cuttlefish kieruje się na najnowszą tablicę zgodności dopiero po zamrożeniu interfejsów, więc nie ma różnicy w bibliotekach AIDL opartych na RELEASE_AIDL_USE_UNFROZEN
.
Macierze
Interfejsy należące do partnera są dodawane do matryc zgodności dla poszczególnych urządzeń lub produktów, na które urządzenie jest kierowane podczas tworzenia. Dlatego, gdy do macierzy zgodności zostanie dodana nowa, odblokowana wersja interfejsu, poprzednie zamrożone wersje muszą pozostać w przypadku RELEASE_AIDL_USE_UNFROZEN=false
. Możesz to zrobić, używając różnych plików tabeli zgodności dla różnych konfiguracji RELEASE_AIDL_USE_UNFROZEN
lub zezwalając na obie wersje w pojedynczym pliku tabeli zgodności, który jest używany we wszystkich konfiguracjach.
Na przykład podczas dodawania niezablokowanej wersji 4 użyj <version>3-4</version>
.
Gdy wersja 4 jest zablokowana, możesz usunąć wersję 3 z matrycy zgodności, ponieważ gdy RELEASE_AIDL_USE_UNFROZEN
jest false
, używana jest zablokowana wersja 4.
Pliki manifestu
W Androidzie 15 wprowadzono zmianę w wartości libvintf
, aby modyfikować pliki manifestu w czasie kompilacji na podstawie wartości RELEASE_AIDL_USE_UNFROZEN
.
Pliki manifestu i ich fragmenty określają, która wersja interfejsu jest implementowana przez usługę. Jeśli używasz najnowszej wersji odblokowanego interfejsu, manifest musi zostać zaktualizowany, aby odzwierciedlał tę nową wersję. Gdy RELEASE_AIDL_USE_UNFROZEN=false
, wpisy w pliku manifestu są dostosowywane przez libvintf
, aby odzwierciedlić zmiany w wygenerowanej bibliotece AIDL. Wersja jest modyfikowana z wersji niezamrożonej (N
) do ostatniej wersji zamrożonej (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ć zgodny wstecz z każdą poprzednią obsługiwaną wersją zamrożoną. Gdy RELEASE_AIDL_USE_UNFROZEN
to false
, usługi zawsze wyglądają jak w ostatniej zamrożonej wersji lub wcześniejszej (na przykład wywołanie nowych niezamrożonych metod zwraca UNKNOWN_TRANSACTION
, a nowe pola parcelable
mają wartości domyślne). Klienci korzystający z ram Androida muszą być zgodni wstecznie z dodatkowymi poprzednimi wersjami, ale jest to nowy szczegół dla klientów dostawców i klientów interfejsów należących do partnerów.
Zmiany w implementacji HAL
Największą różnicą w programowaniu HAL w przypadku programowania opartego na flagach jest wymóg, aby implementacje HAL były zgodne wstecznie z ostatnią zamrożoną wersją, gdy RELEASE_AIDL_USE_UNFROZEN
ma wartość false
.
Wsteczna zgodność w implementacjach i kodach urządzeń to nowe wyzwanie. Zobacz Używanie wersji interfejsów.
Zagadnienia związane z wsteczną zgodnością są w ogóle takie same w przypadku klientów i serwerów oraz kodu platformy i kodu dostawcy, ale istnieją subtelne różnice, o których musisz pamiętać, ponieważ obecnie wdrażasz 2 wersje, które korzystają z tego samego kodu źródłowego (obecna, niezamrożona wersja).
Przykład: interfejs ma 3 zablokowane wersje. Interfejs zostanie zaktualizowany o nową metodę. Klient i usługa są aktualizowane, aby używać nowej biblioteki w wersji 4. Biblioteka V4 jest oparta na niezamrożonej wersji interfejsu, dlatego zachowuje się jak ostatnia zamrożona wersja (V3), gdy RELEASE_AIDL_USE_UNFROZEN
= false
, i uniemożliwia korzystanie z nowej metody.
Gdy interfejs jest zamrożony, wszystkie wartości RELEASE_AIDL_USE_UNFROZEN
używają tej zamrożonej wersji, a kod obsługujący zgodność wsteczną można usunąć.
Gdy wywołujesz metody z wywołaniami zwrotnymi, musisz płynnie obsługiwać przypadek, gdy zwracany jest parametr UNKNOWN_TRANSACTION
. Klienty mogą implementować 2 różne wersje wywołania zwrotnego zależnie od konfiguracji wersji, więc nie możesz zakładać, że klient wysłał najnowszą wersję, a nowe metody mogą ją zwracać. Jest to podobne do sposobu, w jaki stabilne klienty AIDL zachowują zgodność wsteczną z serwerami. Opisano go w artykule Używanie interfejsów z wersjami.
// 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();
}
}
Gdy parametr RELEASE_AIDL_USE_UNFROZEN
ma wartość false
, nowe pola w dotychczasowych typach (parcelable
, enum
, union
) mogą nie istnieć lub zawierać wartości domyślne, a wartości nowych pól, które usługa próbuje wysłać, mogą zostać pominięte.
Nowych typów dodanych w tej nieodblokowanej wersji nie można wysyłać ani odbierać za pomocą interfejsu.
Gdy implementacja RELEASE_AIDL_USE_UNFROZEN
ma wartość false
, nie otrzymuje ona wywołania nowych metod od żadnego klienta.
Pamiętaj, aby używać nowych enumeratorów tylko w wersji, w której zostały wprowadzone, a nie w poprzedniej.
Aby sprawdzić, której wersji używa zdalny interfejs, użyj polecenia foo->getInterfaceVersion()
. Jeśli jednak korzystasz z obsługi wersji na podstawie flagi, wdrażasz 2 różne wersje, więc warto pobrać wersję bieżącego interfejsu. Możesz to zrobić, uzyskując wersję interfejsu bieżącego obiektu, na przykład this->getInterfaceVersion()
lub inną metodę my_ver
. Więcej informacji znajdziesz w sekcji Wysyłanie zapytań do wersji obiektu zdalnego w interfejsie.
Nowe stabilne interfejsy VINTF
Po dodaniu nowego pakietu interfejsu AIDL nie ma ostatniej zablokowanej wersji, więc nie można wrócić do momentu, gdy RELEASE_AIDL_USE_UNFROZEN
ma wartość false
. Nie używaj tych interfejsów. Gdy RELEASE_AIDL_USE_UNFROZEN
jest ustawiona na false
, menedżer usługi nie zezwala usłudze na rejestrowanie interfejsu, a klienci nie mogą go znaleźć.
Usługi możesz dodawać warunkowo na podstawie wartości flagi RELEASE_AIDL_USE_UNFROZEN
w 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żna jej bezwarunkowo dodać do urządzenia, możesz sprawdzić, czy jest ona zadeklarowana za pomocą IServiceManager::isDeclared()
. Jeśli jest zadeklarowany, ale nie udało się go zarejestrować, przerwij proces. Jeśli nie jest zadeklarowana, prawdopodobnie nie uda się jej zarejestrować.
Cuttlefish jako narzędzie do tworzenia
Co roku po zamrożeniu specyfikacji VINTF dostosowujemy tablicę zgodności frameworka (FCM) target-level
i PRODUCT_SHIPPING_API_LEVEL
Cuttlefish, aby odzwierciedlały one urządzenia wprowadzane na rynek w ramach kolejnej wersji. Dostosowujemy target-level
i PRODUCT_SHIPPING_API_LEVEL
, aby mieć pewność, że w przyszłości będzie dostępne urządzenie z nowymi funkcjami, które zostało przetestowane i spełnia nowe wymagania.
Gdy RELEASE_AIDL_USE_UNFROZEN
to true
, Cuttlefish jest używany do tworzenia przyszłych wersji Androida. Jest ona kierowana na poziom FCM w przyszłej wersji Androida (PRODUCT_SHIPPING_API_LEVEL
) i musi spełniać wymagania dotyczące oprogramowania dostawcy (VSR) w przyszłej wersji.
Gdy RELEASE_AIDL_USE_UNFROZEN
to false
, Cuttlefish ma poprzednią wersję target-level
i PRODUCT_SHIPPING_API_LEVEL
, aby odzwierciedlić urządzenie z wersją.
W Androidzie 14 i starszych wersjach to rozróżnienie powstało przez różne gałęzie Git, które nie zauważyły zmiany w FCM target-level
, na poziomie interfejsu API dostawy ani żadnego innego kodu kierowanego do następnej wersji.
Reguły nadawania nazw modułom
W Androidzie 11 dla każdej kombinacji włączonych wersji i włączonych backendów automatycznie tworzony jest moduł uniwersalnej biblioteki. Aby odwołać się do konkretnego modułu biblioteki podrzędnej na potrzeby łączenia, nie używaj nazwy modułu aidl_interface
, ale nazwy modułu biblioteki podrzędnej, który jest ifacename-version-backend, gdzie
ifacename
: nazwa modułuaidl_interface
version
może być równe:Vversion-number
w przypadku wersji zamrożonychVlatest-frozen-version-number + 1
w przypadku wersji drzewa wierzchołkowego (jeszcze niezamrożonej)
backend
może być równe:java
dla backendu Javy,cpp
dla backendu w C++,ndk
lubndk_platform
w przypadku backendu NDK. Pierwszy jest przeznaczony do aplikacji, a drugi do korzystania z platformy do Androida 13. W Androidzie 13 i nowszych używaj tylkondk
.rust
dla backendu Rust.
Załóżmy, że istnieje moduł o nazwie foo, którego najnowsza wersja to 2, a moduł obsługuje zarówno NDK, jak i C++. W tym przypadku AIDL generuje te moduły:
- Na podstawie wersji 1
foo-V1-(java|cpp|ndk|ndk_platform|rust)
- Na podstawie wersji 2 (najnowszej stabilnej wersji)
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 się do najnowszej stabilnej wersji, staje sięfoo-V2-backend
foo-unstable-backend
, która 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 Warunków korzystania z usługi:
foo-V3-(cpp|ndk|ndk_platform|rust).so
Pamiętaj, że kompilator AIDL nie tworzy ani modułu wersji unstable
, ani modułu bez wersji dla stabilnego interfejsu AIDL.
Od Androida 12 nazwa modułu wygenerowana na podstawie stabilnego interfejsu AIDL zawsze zawiera jego wersję.
Nowe metody interfejsu meta
Android 10 dodaje kilka metod metainterfejsu dla stabilnej wersji AIDL.
Przesyłanie zapytania do wersji interfejsu obiektu zdalnego
Klienci mogą wysyłać zapytania o wersję i sumę kontrolną interfejsu implementowanego przez obiekt zdalny oraz 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 (zamiast IFoo
używana jest wartość super
, aby uniknąć błędów podczas kopiowania i wklejania). W zależności od konfiguracji javac
może być potrzebna adnotacja @SuppressWarnings("static")
, aby wyłączyć ostrzeżenia:
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 przez klienta i serwer (np. klasy mogą znajdować się w ścieżce klasy rozruchowej). Gdy zajęcia są udostępniane, serwer jest również połączony z najnowszą wersją zajęć, nawet jeśli została ona utworzona za pomocą starszej wersji interfejsu. Jeśli ten interfejs meta jest zaimplementowany w wspólnej klasie, zawsze zwraca najnowszą wersję. Jednak dzięki zaimplementowaniu metody w taki sposób numer wersji interfejsu jest zakodowany w kodzie serwera (bo IFoo.VERSION
to static final int
, który jest wstawiany w miejscu odwołania), a więc metoda może zwrócić dokładną wersję, z której został skompilowany serwer.
Praca ze starszymi interfejsami
Możliwe, że klient jest zaktualizowany do nowszej wersji interfejsu AIDL, ale serwer używa starszego interfejsu AIDL. W takich przypadkach wywołanie metody w starym interfejsie zwraca wartość UNKNOWN_TRANSACTION
.
Stabilna wersja AIDL daje klientom 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 ona zaimplementowana po stronie zdalnej (ponieważ została utworzona przy użyciu starszej wersji interfejsu). Domyślne wartości są ustawiane globalnie, dlatego nie należy ich używać w kontekstach, które mogą być udostępniane.
Przykład w C++ na Androidzie 13 i nowszych:
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 udostępniać domyślnej implementacji wszystkich metod w interfejsie AIDL. Metody, które są z pewnością zaimplementowane po stronie zdalnej (ponieważ masz pewność, że zdalne urządzenie zostało utworzone, gdy metody były w opisie interfejsu AIDL), nie muszą być zastąpione w domyślnej klasie impl
.
Konwertowanie istniejących AIDL na uporządkowane lub stabilne 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.
Zidentyfikuj wszystkie zależności interfejsu. W przypadku każdego pakietu, od którego zależy interfejs, sprawdź, czy pakiet jest zdefiniowany w stabilnej wersji AIDL. Jeśli nie jest zdefiniowany, pakiet musi zostać przekonwertowany.
Przekształć wszystkie obiekty Parcelable w interfejsie na stabilne obiekty Parcelable (same pliki interfejsu mogą pozostać bez zmian). Aby to zrobić, należy określić ich strukturę bezpośrednio w plikach AIDL. Aby z nich korzystać, trzeba zmodyfikować klasy zarządzania. Możesz to zrobić przed utworzeniem pakietu
aidl_interface
(poniżej).Utwórz pakiet
aidl_interface
(w sposób opisany powyżej) zawierający nazwę modułu, jego zależności i wszelkie inne potrzebne informacje. Aby był stabilizowany (a nie tylko uporządkowany), trzeba też zmienić jego wersję. Więcej informacji znajdziesz w artykule na temat interfejsów wersji.