Stabilna wersja AIDL

Android 10 obsługuje stabilny język definiowania interfejsu Androida (AIDL), czyli nowy sposób śledzenia interfejsu aplikacji (API) i interfejsu binarnego aplikacji (ABI) udostępnianego przez interfejsy AIDL. Główne różnice między stabilną wersją AIDL a AIDL to:

  • Interfejsy są zdefiniowane w systemie kompilacji za pomocą aidl_interfaces.
  • Interfejsy mogą zawierać tylko uporządkowane dane. Interfejsy Parcelable reprezentujące preferowane typy są tworzone automatycznie na podstawie ich definicji AIDL, a także są automatycznie modyfikowane i usuwane.
  • Interfejsy mogą być zadeklarowane jako stabilne (zgodne wstecznie). W takim przypadku ich interfejs API jest śledzony i umieszczany w wersji w pliku obok interfejsu AIDL.

Uporządkowane a stabilne AIDL

Uporządkowane AIDL odnosi się do typów zdefiniowanych wyłącznie w AIDL. Na przykład deklaracja własna (niestandardowa) nie ma struktury AIDL. Materiały Parcelable z polami zdefiniowanymi w AIDL są nazywane elementami strukturalnymi.

Stabilna wersja AIDL wymaga uporządkowanych danych AIDL, tak aby system kompilacji i kompilator mógł rozpoznać, czy zmiany wprowadzone w plikach parcelable są zgodne wstecznie. Jednak nie wszystkie uporządkowane interfejsy są stabilne. Aby interfejs był stabilny, interfejs może używać tylko typów uporządkowanych i otrzymywać wymienione poniżej funkcje obsługi wersji. Interfejs nie jest też stabilny, jeśli do jego utworzenia używany jest podstawowy system kompilacji lub jeśli skonfigurowano funkcję unstable:true.

Definiowanie interfejsu AIDL

Definicja słowa 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 typu AIDL Foo zdefiniowanego w pakiecie com.acme powinna mieć wartość <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 poprzednim przykładzie <base_path> to srcs/aidl.
  • local_include_dir: ścieżka, od której zaczyna się nazwa pakietu. Odpowiada to <base_path> wyjaśnionemu powyżej.
  • imports: lista modułów aidl_interface używanych w tym module. Jeśli jeden z interfejsów AIDL używa interfejsu lub pakietu SDK innego aidl_interface, wpisz tutaj jego nazwę. 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 zostały zablokowane zgodnie z zasadą api_dir (począwszy od Androida 11), versions są zablokowane w okresie aidl_api/name. Jeśli nie ma zablokowanych wersji interfejsu, nie należy go określać i nie przeprowadzamy testów zgodności. W Androidzie 13 i nowszych to pole zostało zastąpione polem versions_with_info.
  • versions_with_info: lista krotek, z których każda zawiera nazwę zablokowanej wersji i listę z importami wersji innych modułów aidl_interface zaimportowanej przez tę wersję interfejsu aidl_interface. Definicja wersji V interfejsu IFACE interfejsu AIDL znajduje się na stronie aidl_api/IFACE/V. To pole zostało wprowadzone w Androidzie 13 i nie należy go bezpośrednio modyfikować w Android.bp. Pole jest dodawane lub aktualizowane przez wywołanie *-update-api lub *-freeze-api. Poza tym pola versions są automatycznie przenoszone do versions_with_info, gdy użytkownik wywoła metodę *-update-api lub *-freeze-api.
  • stability: opcjonalna flaga obietnicy stabilności interfejsu. Ta funkcja obsługuje tylko "vintf". Jeśli zasada stability nie jest skonfigurowana, system kompilacji sprawdza, czy interfejs jest zgodny wstecznie, chyba że określono parametr unstable. Brak ustawienia odpowiada stabilności interfejsu w kontekście tej kompilacji (czyli wszystkim elementom systemu, na przykład wszystkim elementom w system.img i powiązanych partycjach, lub wszystkim elementom dostawcy, takim jak vendor.img i powiązanych partycji). Jeśli stability ma wartość "vintf", odpowiada to obietnicy stabilności: interfejs musi być stabilny, dopóki jest używany.
  • gen_trace: opcjonalna flaga do włączania i wyłączania śledzenia. Od Androida 14 ustawieniem domyślnym jest true dla backendów cpp i java.
  • 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 zasada ma wartość true, system kompilacji nie tworzy zrzutu API dla interfejsu i nie wymaga jego aktualizacji.
  • frozen: opcjonalna flaga wskazująca, że wartość true oznacza, że interfejs nie uległ zmianie od poprzedniej wersji. Pozwala to na większą liczbę kontroli w czasie kompilacji. Ustawienie false oznacza, że interfejs jest w fazie opracowywania i wprowadzono w nim zmiany, w wyniku czego uruchomienie foo-freeze-api powoduje wygenerowanie nowej wersji i automatyczne zmianę wartości na true. Wprowadzone 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. Backendy Java, C++ i NDK są domyślnie włączone. Jeśli któryś z tych 3 backendów nie jest potrzebny, należy go wyłączyć bezpośrednio. Do Androida 15 (eksperyment AOSP) narzędzie Rust jest domyślnie wyłączone.
  • backend.<type>.apex_available: lista nazw APEX, dla których dostępna jest wygenerowana biblioteka krótkoterminowa.
  • backend.[cpp|java].gen_log: opcjonalna flaga określająca, czy należy wygenerować dodatkowy kod do gromadzenia informacji o transakcji.
  • backend.[cpp|java].vndk.enabled: opcjonalna flaga, która włącza ten interfejs do VNDK. Wartość domyślna to false.
  • backend.[cpp|ndk].additional_shared_libraries: ta flaga wprowadzona w Androidzie 14 dodaje zależności do bibliotek natywnych. Ta flaga jest przydatna w przypadku ndk_header i cpp_header.
  • backend.java.sdk_version: opcjonalna flaga określająca wersję pakietu SDK, na podstawie której utworzona biblioteka Javywa. Wartość domyślna to "system_current". Tej wartości nie należy ustawiać, gdy backend.java.platform_apis ma wartość true.
  • backend.java.platform_apis: opcjonalna flaga, która powinna być ustawiona na true, gdy wygenerowane biblioteki muszą używać interfejsu API platformy, a nie SDK.

Dla każdej kombinacji wersji i włączonych backendów tworzona jest biblioteka namiotowa. Aby dowiedzieć się, jak odwołać się do konkretnej wersji biblioteki skróconej w przypadku konkretnego backendu, przeczytaj artykuł Reguły nazewnictwa modułów.

Zapisywanie plików AIDL

Interfejsy w stabilnej wersji AIDL są podobne do tradycyjnych interfejsów z tą różnicą, że nie mogą używać pakietów pakietów (ponieważ nie są one stabilne – patrz Uporządkowane a stabilne AIDL). Podstawową różnicą w stabilnej wersji AIDL jest sposób definiowania obiektów parcelable. Wcześniej obiekty parcelable były deklarowane. W stabilnym (a tym samym ustrukturyzowanym) systemie AIDL pola i zmienne parcelable są bezpośrednio zdefiniowane.

// in a file like 'some/package/Thing.aidl'
package some.package;

parcelable SubThing {
    String a = "foo";
    int b;
}

Domyślne ustawienie jest obsługiwane (ale nie wymagane) dla: boolean, char, float, double, byte, int, long i String. W Androidzie 12 obsługiwane są również domyślne wyliczenia zdefiniowane 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 żadnego elementu wyliczającego.

Korzystanie z bibliotek z stubami

Po dodaniu atrapy bibliotek jako zależności do modułu możesz uwzględnić je w swoich 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 języku 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 utworzeniu foo-freeze-api do api_dir lub aidl_api/name dodaje nową definicję interfejsu API (w zależności od wersji Androida) oraz plik .hash reprezentujący nową zamrożoną wersję interfejsu. foo-freeze-api aktualizuje też właściwość versions_with_info, aby uwzględnić dodatkową wersję, oraz imports dla wersji. Ogólnie rzecz biorąc, pole imports w polu versions_with_info jest kopiowane z pola imports. Najnowsza stabilna wersja jest jednak podana w polu imports w pliku versions_with_info na potrzeby importu, który nie ma jednoznacznej wersji. Po określeniu właściwości versions_with_info system kompilacji uruchamia testy zgodności między zablokowanymi wersjami oraz między „wierzchołkiem drzewa” (ToT) a ostatnią 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 utrzymać stabilność interfejsu, właściciele mogą dodawać nowe:

  • Metody umieszczone na końcu interfejsu (lub metody ze jawnie zdefiniowanymi nowymi numerami seryjnymi)
  • Elementy na końcu przesyłki (do każdego elementu wymagane jest domyślne dodanie danych)
  • Wartości stałe
  • W Androidzie 11 liczniki
  • W Androidzie 12 pola do końca sumy

Żadne inne działania nie są dozwolone ani nikt inny nie może modyfikować interfejsu (w przeciwnym razie ryzykuje to konflikt ze zmianami wprowadzonymi przez właściciela).

Aby sprawdzić, czy wszystkie interfejsy w wersji są zablokowane w wersji, możesz utworzyć kompilację, korzystając z tego zestawu zmiennych środowiskowych:

  • AIDL_FROZEN_REL=true m ... – kompilacja wymaga zablokowania wszystkich stabilnych interfejsów AIDL, które nie mają określonego pola owner:.
  • AIDL_FROZEN_OWNERS="aosp test" – kompilacja wymaga, aby wszystkie stabilne interfejsy AIDL zostały zablokowane z polem owner: określonym jako „aosp” lub „test”.

Stabilność importu

Aktualizowanie wersji importów w przypadku zablokowanych wersji interfejsu jest zgodne wstecznie w warstwie stabilnej AIDL. Jednak ich aktualizowanie wymaga aktualizacji wszystkich serwerów i klientów korzystających z poprzedniej wersji interfejsu, co może powodować dezorientację niektórych aplikacji, gdy łączysz różne wersje danego typu. Ogólnie 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.

Używaj interfejsów z różnymi wersjami interfejsu

Metody interfejsu

Podczas próby wywołania nowych metod na starym serwerze w czasie działania nowe klienty otrzymują błąd lub wyjątek (w zależności od backendu).

  • Backend cpp otrzymuje ::android::UNKNOWN_TRANSACTION.
  • Backend ndk otrzymuje STATUS_UNKNOWN_TRANSACTION.
  • Backend java otrzymuje android.os.RemoteException z komunikatem informującym, że interfejs API nie został zaimplementowany.

Aby dowiedzieć się, jak sobie z tym poradzić, przeczytaj artykuły o wersjach zapytań i używaniu wartości domyślnych.

Parcelables

Gdy do plików parcelable są dodawane nowe pola, stare klienty i serwery pomijają je. Gdy nowe klienty i serwery otrzymują stare pakiety parcels, domyślne wartości nowych pól są automatycznie wypełniane. Oznacza to, że wartości domyślne muszą być określone dla wszystkich nowych pól w pakiecie.

Klienty nie powinny oczekiwać, że serwery będą używać nowych pól, jeśli nie będą wiedzieć, że serwer implementuje wersję, która ma zdefiniowane pole (patrz wersje zapytań).

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 element wyliczający, o którym nie wie. Serwer powinien zignorować licznik albo zwrócić jakiś element, aby klient wiedział, że nie jest on obsługiwany w tej implementacji.

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. Wdrożenie nigdy nie wykryje połączenia z nowym polem. W przypadku transakcji jednokierunkowej błąd jest ignorowany. W przeciwnym razie błąd to BAD_VALUE(w przypadku backendu C++ lub NDK) albo IllegalArgumentException(w przypadku backendu Javy). Błąd zostanie odebrany, gdy klient wysyła wartość w nowym polu do starego serwera lub gdy jest to stary klient odbierający połączenie z nowego serwera.

Programowanie oparte na flagach

Na urządzeniach z wersją, które są w fazie rozwoju (niezablokowane), nie można używać, ponieważ nie mają one gwarancji zgodności wstecznej.

AIDL obsługuje zastępcze okresy działania dla tych niezablokowanych bibliotek interfejsu, aby umożliwić tworzenie kodu w najnowszej niezablokowanej wersji i nadal być używany na urządzeniach z wersją. Zachowanie zgodności wstecznej klientów jest podobne do dotychczasowego zachowania, ale w przypadku zasobów zastępczych implementacje również muszą być zgodne z tymi zachowaniami. Zapoznaj się z sekcją Używanie interfejsów z różnymi wersjami interfejsu.

Flaga kompilacji AIDL

Flaga kontrolująca to zachowanie jest 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. Programowanie odbywa się zwykle z konfiguracją, która ma flagę ustawioną na true.

Macierz 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ż mątwy używa najnowszej macierzy zgodności dopiero po zamrożeniu interfejsów, więc nie ma różnic w bibliotekach AIDL opartych na RELEASE_AIDL_USE_UNFROZEN.

Macierze

Interfejsy należące do partnerów są dodawane do macierzy zgodności określonych urządzeń lub usług, na które urządzenie jest kierowane w trakcie programowania. Gdy do macierzy zgodności zostanie dodana nowa, niezablokowana wersja interfejsu, poprzednie zablokowane wersje muszą pozostać w 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 umieszczając obie wersje w jednym pliku macierzy zgodności używanego 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 tablicy zgodności, ponieważ jest używana zablokowana wersja 4, gdy RELEASE_AIDL_USE_UNFROZEN ma wartość false.

Pliki manifestu

W Androidzie 15 (AOSP w wersji eksperymentalnej) wprowadziliśmy 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 określają, którą wersję interfejsu implementuje usługa. Jeśli używasz najnowszej nieodblokowanej wersji interfejsu, musisz zaktualizować plik manifestu, aby odzwierciedlał tę wersję. Gdy RELEASE_AIDL_USE_UNFROZEN=false wpisy pliku manifestu są dostosowywane przez libvintf, aby odzwierciedlały zmianę w wygenerowanej bibliotece AIDL. Wersja zostanie zmodyfikowana z niezablokowanej wersji (N) do ostatniej zablokowanej wersji N - 1. Dzięki temu użytkownicy nie muszą zarządzać wieloma plikami manifestu ani fragmentami manifestu dla każdej usługi.

Zmiany klienta HAL

Kod klienta HAL musi być zgodny wstecznie z każdą obsługiwaną wcześniej zamrożoną wersją. Gdy RELEASE_AIDL_USE_UNFROZEN ma wartość false, usługi zawsze wyglądają jak ostatnio zablokowana wersja lub wcześniejsza (na przykład wywołanie nowych niezablokowanych metod zwraca UNKNOWN_TRANSACTION lub nowe pola parcelable mają wartości domyślne). Klienty Android Framework muszą być zgodne wstecz z dodatkowymi poprzednimi wersjami, ale jest to nowy szczegół dla 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. Uwzględnienie zgodności wstecznej w implementacjach i kodzie urządzenia to nowy proces. Zobacz Używanie interfejsów z obsługą wersji.

Zasadniczo kwestie zgodności wstecznej są zasadniczo takie same w przypadku klientów i serwerów, jak i kodu platformy i kodu dostawcy, ale musisz pamiętać o niewielkich różnicach, ponieważ obecnie implementujesz 2 wersje korzystające z tego samego kodu źródłowego (obecną, niezablokowaną wersję).

Przykład: interfejs ma 3 zablokowane wersje. Interfejs zostanie zaktualizowany o nową metodę. Klient i usługa zostaną zaktualizowane tak, aby korzystały z nowej biblioteki wersji 4. Biblioteka V4 jest oparta na niezablokowanej wersji interfejsu, dlatego działa jak ostatnia zablokowana wersja (wersja 3), gdy RELEASE_AIDL_USE_UNFROZEN ma wartość false, i uniemożliwia użycie nowej metody.

Gdy interfejs jest zablokowany, wszystkie wartości RELEASE_AIDL_USE_UNFROZEN korzystają z tej zablokowanej 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ć. Przypomina to zachowanie zgodności wstecznej z serwerami stabilnych klientów AIDL z serwerami, o których mowa w artykule Używanie interfejsów z różnymi wersjami interfejsu.

// 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 RELEASE_AIDL_USE_UNFROZEN ma wartość false, nowe pola w istniejących typach (parcelable, enum, union) mogą nie istnieć lub zawierać ich wartości domyślne, a wartości nowych pól, które usługa próbuje wysłać, są usuwane, gdy kończy się proces.

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 modułów wyliczających tylko w wersji, w której zostały wprowadzone, a nie poprzedniej.

Zwykle do sprawdzenia, której wersji używa interfejs zdalny, należy użyć polecenia foo->getInterfaceVersion(). Jednak dzięki obsłudze wersji opartej na flagach implementujesz 2 różne wersje, więc warto pobrać wersję bieżącego interfejsu. Aby to zrobić, pobierz wersję interfejsu bieżącego obiektu, np. this->getInterfaceVersion(), lub inne metody dla my_ver. Więcej informacji znajdziesz w artykule Wysyłanie zapytań o wersję interfejsu obiektu zdalnego.

Nowe stabilne interfejsy VINTF

Po dodaniu nowego pakietu interfejsu AIDL nie ma żadnej 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 ma wartość false, menedżer usług nie zezwala usłudze na zarejestrowanie interfejsu, więc klienci nie mogą go znaleźć.

Możesz dodawać usługi warunkowo, opierając się na 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 i nie możesz dodać jej na urządzeniu warunkowo, możesz sprawdzić, czy usługa jest zadeklarowana za pomocą IServiceManager::isDeclared(). Jeśli jest zadeklarowany, ale nie udało się go zarejestrować, przerwij ten proces. Jeśli nie jest zadeklarowana, prawdopodobnie nie uda się jej zarejestrować.

Mątwa jako narzędzie dla programistów

Co roku po zamrożeniu VINTF dostosowujemy macierz zgodności platformy (FCM) target-level i PRODUCT_SHIPPING_API_LEVEL Cuttlefish, aby uwzględniały urządzenia, które pojawią się w przyszłorocznej premierze. Korygujemy target-level i PRODUCT_SHIPPING_API_LEVEL, aby mieć pewność, że dostępne w przyszłorocznej wersji urządzenia są testowane i spełniają nowe wymagania.

Gdy RELEASE_AIDL_USE_UNFROZEN ma wartość true, Cuttlefish jest używana do tworzenia przyszłych wersji Androida. Jest kierowana na poziom FCM i PRODUCT_SHIPPING_API_LEVEL w przyszłorocznej wersji Androida, co wymaga spełnienia wymagań dotyczących oprogramowania dostawcy kolejnej wersji (VSR).

Gdy RELEASE_AIDL_USE_UNFROZEN ma wartość false, mątwy ma poprzedni target-level i PRODUCT_SHIPPING_API_LEVEL, który odzwierciedla urządzenie wersji. 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 nazewnictwa modułów

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 skróconej na potrzeby połączenia, nie używaj nazwy modułu aidl_interface, tylko nazwy modułu biblioteki skróconej, czyli ifacename-version-backend, gdzie

  • ifacename: nazwa modułu aidl_interface
  • version należy do jednej z tych wartości:
    • Vversion-number dla zablokowanych wersji
    • Vlatest-frozen-version-number + 1 dla wersji „na czubku drzewa” (jeszcze do zamrożenia)
  • backend należy do jednej z tych wartości:
    • java dla backendu Javy,
    • cpp dla backendu C++,
    • ndk lub ndk_platform dla backendu NDK. Pierwszy z nich dotyczy aplikacji, a drugi – do Androida 13. Na Androidzie 13 i nowszych używaj tylko ndk.
    • rust dla backendu Rust.

Załóżmy, że istnieje moduł o nazwie foo, a jego najnowsza wersja to 2, który 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)
  • korzysta z wersji 2 (najnowszej stabilnej),
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • Na podstawie wersji Warunków korzystania z usługi:
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

W porównaniu z Androidem 11:

  • foo-backend, który do najnowszej wersji stabilnej odwołuje się do foo-V2-backend
  • foo-unstable-backend, który odwołuje się do wersji Warunków korzystania z usługi, stanie 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 w wersji unstable, ani modułu bez wersji dla stabilnego interfejsu AIDL. Od Androida 12 nazwa modułu wygenerowana przez stabilny interfejs AIDL zawsze zawiera jego wersję.

Nowe metody interfejsu meta

Android 10 dodaje kilka metod metainterfejsu dla stabilnej wersji AIDL.

Wyślij zapytanie do wersji interfejsu obiektu zdalnego

Klienty mogą wysyłać zapytania o wersję i hasz interfejsu implementowanego przez obiekt zdalny oraz porównać zwrócone wartości z wartościami interfejsu, z którego korzysta 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 następujący sposób (zamiast IFoo należy użyć super, aby uniknąć błędów podczas kopiowania i wklejania. W zależności od konfiguracji javac może być potrzebna adnotacja @SuppressWarnings("static") do wyłączenia ostrzeżeń:

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). Podczas udostępniania klas serwer jest również połączony z najnowszą wersją tych klas, mimo że mógł zostać utworzony przy użyciu starszej wersji interfejsu. Jeśli ten metainterfejs jest zaimplementowany w klasie współdzielonej, zawsze zwraca najnowszą wersję. Jednak po wdrożeniu metody opisanej powyżej numer wersji interfejsu jest osadzony w kodzie serwera (ponieważ IFoo.VERSION to static final int, który jest wstawiony w przypadku odwołania) i dzięki temu metoda może zwrócić dokładną wersję, z którą zbudowano serwer.

Radzenie sobie ze starszymi interfejsami

Może się zdarzyć, że klient zostanie zaktualizowany do nowszej wersji interfejsu AIDL, ale serwer używa starego interfejsu AIDL. W takich przypadkach wywołanie metody w starym interfejsie zwraca 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 zaimplementowana po stronie zdalnej (ponieważ została utworzona przy użyciu starszej wersji interfejsu). Ponieważ wartości domyślne są ustawiane globalnie, nie należy ich używać z kontekstów potencjalnie wspólnych.

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 podawać domyślnej implementacji wszystkich metod w interfejsie AIDL. Metody, które gwarantują, że zostaną wdrożone na serwerze zdalnym (ponieważ masz pewność, że pilot został utworzony, gdy znajdują się w opisie interfejsu AIDL), nie musisz zastępować go w domyślnej klasie impl.

Przekonwertuj istniejące AIDL na uporządkowane lub stabilne AIDL

Jeśli masz interfejs AIDL i kod, który z niego korzysta, wykonaj poniższe czynności, aby przekonwertować interfejs na stabilny interfejs AIDL.

  1. Określ wszystkie zależności interfejsu. Sprawdź, czy pakiet jest zdefiniowany w stabilnej wersji AIDL dla każdego pakietu, od którego zależy interfejs. Jeśli pakiet nie zostanie określony, musi zostać przekonwertowany.

  2. Przekonwertuj wszystkie pakiety pakietów w interfejsie na pliki stabilne (sam pliki interfejsu mogą pozostać niezmienione). Można to zrobić przez wyrażenie ich struktury 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).

  3. 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.