Android 10 dodaje obsługę stabilnego języka Android Interface Definition Language (AIDL), nowego sposobu śledzenia interfejsu programu aplikacji (API)/interfejsu binarnego aplikacji (ABI) zapewnianego 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. Parcelables reprezentujące żądane typy są tworzone automatycznie na podstawie ich definicji AIDL i są automatycznie kierowane i unmarshallowane.
- Interfejsy można zadeklarować jako stabilne (kompatybilne wstecz). Kiedy tak się dzieje, ich interfejs API jest śledzony i wersjonowany w pliku obok interfejsu AIDL.
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: ["ohter-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 dla AIDL typuFoo
zdefiniowana w pakieciecom.acme
powinna znajdować się w<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 używanych przez to modułówaidl_interface
. Jeśli jeden z twoich interfejsów AIDL używa interfejsu lub pliku parcelable z innegoaidl_interface
, umieść tutaj jego nazwę. Może to być sama nazwa, aby odnosić się do najnowszej wersji, lub nazwa z sufiksem wersji (takim jak-V1
), aby odnosić się do określonej wersji. Określanie wersji jest obsługiwane od Androida 12 -
versions
: poprzednie wersje interfejsu, które są zamrożone podapi_dir
, Począwszy od Androida 11,versions
są zamrożone podaidl_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ę z importowanymi wersjami innych modułów aidl_interface zaimportowanych przez tę wersję interfejsu aidl_interface. Definicja wersji V interfejsu AIDL IFACE znajduje się pod adresemaidl_api/ IFACE / V
. To pole zostało wprowadzone w Androidzie 13 i nie powinno być modyfikowane bezpośrednio w Android.bp. Pole jest dodawane lub aktualizowane przez 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 obietnicy stabilności tego interfejsu. Obecnie obsługuje tylko"vintf"
. Jeśli ta opcja jest nieustawiona, odpowiada to interfejsowi ze stabilnością w tym kontekście kompilacji (tak więc załadowany tutaj interfejs może być używany tylko z rzeczami skompilowanymi razem, na przykład w pliku system.img). Jeśli jest to ustawione na"vintf"
, odpowiada to obietnicy stabilności: interfejs musi być stabilny tak długo, jak jest używany. -
gen_trace
: Opcjonalna flaga do włączania i wyłączania śledzenia. Wartość domyślna tofalse
. -
host_supported
: Opcjonalna flaga, która po ustawieniu na wartośćtrue
udostępnia wygenerowane biblioteki środowisku hosta. -
unstable
: Opcjonalna flaga używana do oznaczenia, że ten interfejs nie musi być stabilny. Gdy ta opcja ma wartośćtrue
, system kompilacji nie tworzy zrzutu interfejsu API dla interfejsu ani nie wymaga jego aktualizacji. -
frozen
: Opcjonalna flaga, której wartość ustawiona natrue
oznacza, że interfejs nie zmienił się od poprzedniej wersji interfejsu. Umożliwia to więcej kontroli w czasie kompilacji. Ustawienie wartościfalse
oznacza, że interfejs jest w fazie rozwoju i zawiera nowe zmiany, więc uruchomieniefoo-freeze-api
wygeneruje nową wersję i automatycznie zmieni wartość natrue
. Wprowadzony w Androidzie 14 (eksperymentalny AOSP). -
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. Backendy 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 do zbierania informacji o transakcji. -
backend.[cpp|java].vndk.enabled
: Opcjonalna flaga czyniąca ten interfejs częścią VNDK. Wartość domyślna tofalse
. -
backend.java.sdk_version
: Opcjonalna flaga służąca do określania wersji zestawu SDK, na podstawie której zbudowana jest biblioteka pośrednicząca języka Java. Wartość domyślna to"system_current"
. Nie należy tego ustawiać, gdybackend.java.platform_apis
ma wartość true. -
backend.java.platform_apis
: opcjonalna flaga, która powinna być ustawiona natrue
, gdy generowane biblioteki muszą być kompilowane na podstawie interfejsu API platformy, a nie zestawu 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 określonej wersji biblioteki pośredniczącej dla określonego zaplecza, zobacz Zasady nazewnictwa modułów .
Pisanie plików AIDL
Interfejsy w stabilnym AIDL są podobne do tradycyjnych interfejsów, z wyjątkiem tego, że nie wolno im używać nieustrukturyzowanych parcelables (ponieważ nie są one stabilne!). Podstawową różnicą w stabilnym AIDL jest sposób definiowania parcelables. Wcześniej parcelables były zadeklarowane w przód ; w stabilnym AIDL pola i zmienne parcelables są definiowane 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ą również wartości domyślne dla 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 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 do 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 Ruście:
use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
// use just like traditional AIDL
Interfejsy wersjonowania
Zadeklarowanie modułu o nazwie foo tworzy również cel 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 wersji. Zasadniczo imports
versions_with_info
są kopiowane z pola imports
. Ale najnowsza stabilna wersja jest określona w imports
w versions_with_info
dla importu, który nie ma jawnej wersji. Po określeniu versions_with_info
system kompilacji uruchamia testy zgodności między zamrożonymi wersjami, a także między Top of Tree (ToT) a najnowszą zamrożoną wersją.
Ponadto musisz zarządzać definicją interfejsu API wersji ToT. Za każdym razem, gdy API jest aktualizowane, 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ą dodawać nowe:
- Metody do końca interfejsu (lub metody z jawnie zdefiniowanymi nowymi numerami seryjnymi)
- Elementy do końca parcelable (wymaga dodania wartości domyślnej dla każdego elementu)
- Stałe wartości
- W systemie Android 11 moduły wyliczające
- W systemie Android 12 pola do końca unii
Żadne inne działania nie są dozwolone i nikt inny nie może modyfikować interfejsu (w przeciwnym razie istnieje ryzyko kolizji ze zmianami wprowadzonymi przez właściciela).
Aby sprawdzić, czy wszystkie interfejsy są zamrożone do wydania, możesz zbudować z następującymi zmiennymi środowiskowymi:
-
AIDL_FROZEN_REL=true m ...
- kompilacja wymaga zamrożenia wszystkich stabilnych interfejsów AIDL, które nie mają określonegoowner:
pole. -
AIDL_FROZEN_OWNERS="aosp test"
- build wymaga zamrożenia wszystkich stabilnych interfejsów AIDL zowner:
pole określone jako "aosp" lub "test".
Stabilność importu
Aktualizacja wersji importu dla zamrożonych wersji interfejsu jest wstecznie kompatybilna w stabilnej warstwie AIDL. Jednak ich aktualizacja wymaga zaktualizowania wszystkich serwerów i klientów korzystających ze starej wersji interfejsu, a niektóre aplikacje mogą być zdezorientowane podczas mieszania różnych wersji typów. Ogólnie rzecz biorąc, w przypadku pakietów zawierających tylko typy lub wspólnych pakietów jest to bezpieczne, ponieważ kod musi być już napisany, aby obsłużyć nieznane typy z transakcji IPC.
W platformie Android kod android.hardware.graphics.common
jest największym przykładem tego typu aktualizacji wersji.
Używanie wersjonowanych interfejsów
Metody interfejsu
W czasie wykonywania, podczas próby wywołania nowych metod na starym serwerze, nowi klienci otrzymują błąd lub wyjątek, w zależności od zaplecza.
- backend
cpp
pobiera::android::UNKNOWN_TRANSACTION
. - backend
ndk
pobieraSTATUS_UNKNOWN_TRANSACTION
. - backend
java
otrzymujeandroid.os.RemoteException
z komunikatem, że interfejs API nie jest zaimplementowany.
Aby zapoznać się ze strategiami radzenia sobie z tym, zobacz odpytywanie wersji i używanie wartości domyślnych .
Paczki
Kiedy nowe pola są dodawane do parcelables, starzy klienci i serwery je odrzucają. Kiedy nowi klienci i serwery otrzymują stare paczki, wartości domyślne dla nowych pól są automatycznie wypełniane. Oznacza to, że wartości domyślne muszą być określone dla wszystkich nowych pól w przesyłce.
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 (zobacz Wyszukiwanie wersji ).
Wyliczenia i stałe
Podobnie klienci i serwery powinni odpowiednio odrzucić lub zignorować nierozpoznane stałe wartości i moduły wyliczające, ponieważ w przyszłości może ich być więcej. Na przykład serwer nie powinien przerywać, gdy otrzyma moduł wyliczający, o którym nie wie. Powinien albo to zignorować, albo 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 polu. Implementacja nigdy nie zobaczy unii z nowym polem. Błąd jest ignorowany, 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 jest odbierany, 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.
Zasady nazewnictwa modułów
W systemie Android 11 dla każdej kombinacji włączonych wersji i backendów automatycznie tworzony jest moduł biblioteki pośredniczącej. Aby odwołać się do określonego modułu biblioteki pośredniczącej w celu połączenia, nie używaj nazwy modułu aidl_interface
, ale nazwy 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 wierzchołka drzewa (jeszcze do zamrożenia)
-
-
backend
jest jednym z-
java
dla backendu Javy, -
cpp
dla backendu C++, -
ndk
lubndk_platform
dla zaplecza NDK. Ta pierwsza jest przeznaczona do aplikacji, a druga do korzystania z platformy, -
rust
dla zaplecza Rust.
-
Załóżmy, że istnieje moduł o nazwie foo , którego 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 z Androidem 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 w wersji unstable
, ani modułu bez wersji dla stabilnego interfejsu AIDL. Od Androida 12 nazwa modułu generowana ze stabilnego interfejsu AIDL zawsze zawiera jego wersję.
Nowe metody interfejsu meta
Android 10 dodaje kilka metod interfejsu meta dla stabilnego AIDL.
Zapytanie o wersję interfejsu obiektu zdalnego
Klienci mogą zapytać o wersję i skrót interfejsu, który implementuje obiekt zdalny, i porównać zwrócone wartości z wartościami interfejsu używanego przez klienta.
Przykład z zapleczem 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 zapleczem 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 ( super
jest używane zamiast IFoo
, 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; }
}
Wynika to z faktu, że wygenerowane klasy ( IFoo
, IFoo.Stub
itd.) są współużytkowane przez klienta i serwer (na przykład klasy mogą znajdować się w ścieżce klas rozruchu). Gdy klasy są udostępniane, serwer jest również powiązany z najnowszą wersją klas, nawet jeśli został zbudowany ze starszą wersją interfejsu. Jeśli ten metainterfejs jest zaimplementowany w klasie udostępnionej, zawsze zwraca najnowszą wersję. Jednak implementując powyższą metodę, numer wersji interfejsu jest osadzony w kodzie serwera (ponieważ IFoo.VERSION
jest static final int
, która jest wstawiana, gdy następuje odwołanie), a zatem metoda może zwrócić dokładną wersję serwera, który 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żna 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ć z potencjalnie współdzielonych kontekstów.
Przykład w C++ w systemie Android 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 podawać domyślnej implementacji wszystkich metod w interfejsie AIDL. Metody, które są gwarantowane do zaimplementowania po stronie zdalnej (ponieważ masz pewność, że zdalny jest zbudowany, gdy metody były 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 następujące kroki, aby przekonwertować interfejs na stabilny interfejs AIDL.
Zidentyfikuj wszystkie zależności 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. Klasy zarządzania muszą zostać przepisane, aby używać tych nowych typów. Można to zrobić przed utworzeniem pakietu
aidl_interface
(poniżej).Utwórz pakiet
aidl_interface
(jak opisano powyżej), który zawiera nazwę twojego modułu, jego zależności i wszelkie inne potrzebne informacje. Aby go ustabilizować (nie tylko ustrukturyzować), należy go również wersjonować. Aby uzyskać więcej informacji, zobacz Wersjonowanie interfejsów .