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) dostarczanego 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. Obiekty Parcelable reprezentujące odpowiednie typy są tworzone automatycznie na podstawie ich definicji AIDL oraz 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 powyższym 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 papierniczego z 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), która odnosi 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. To pole zostało zastąpione polem versions_with_info w przypadku aplikacji 13 i nowszych.
  • 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 modyfikować bezpośrednio w pliku 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. Obecnie 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, na przykład elementom w vendor.img i powiązanych partycjach). 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, dlatego uruchomienie funkcji foo-freeze-api spowoduje 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. Obecnie 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ć, jeśli backend.java.platform_apis ma wartość prawda.
  • 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 papierniczych o nieuporządkowanych danych (ponieważ nie są one stabilne – patrz Uporządkowane a stabilne AIDL). Podstawową różnicą w przypadku stabilnej wersji AIDL jest sposób definiowania obiektów parcelable. Wcześniej dokumenty te były deklarowane. W stabilnej (i ustrukturyzowanej) AIDL pola i zmienne papiernicze są bezpośrednio zdefiniowane.

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

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

Ustawienia domyślne są obecnie obsługiwane (ale nie wymagane) w przypadku tych usług: 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 namiarek w systemie kompilacji (Android.mk może być też używany do określania 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: ...,
    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ą zablokowaną wersję interfejsu. foo-freeze-api aktualizuje też właściwość versions_with_info, aby uwzględnić dodatkową wersję, a wartość 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 podawana w polu imports w pliku versions_with_info na potrzeby importu, który nie ma wyraźnej 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 ze starej wersji interfejsu, a mieszanie różnych wersji aplikacji może powodować dezorientację niektórych aplikacji. 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żywanie różnych wersji interfejsów

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, chyba że 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ą dodawać kolejne wartości. Na przykład serwer nie powinien przerywać działania, gdy otrzyma element wyliczający, o którym nie wie. Powinien go zignorować albo zwrócić jakiś komunikat, aby klient wiedział, że w tej implementacji nie jest to obsługiwane.

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 jednokierunkowych 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 jest odbierane, 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.

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 wszystkie biblioteki niezablokowanych wersji zachowują się tak samo jak ostatnio zablokowane. Na potrzeby programowania lokalnego możesz zmienić flagę na true, ale przed opublikowaniem wersji musisz przywrócić ją do wartości false. Programowanie zazwyczaj odbywa się przy użyciu konfiguracji z flagą ustawioną na true.

Macierz zgodności i pliki manifestu

Obiekty interfejsu dostawcy (obiekty VINTF) określają, które wersje są oczekiwane i jakie wersje są udostępniane po obu stronach interfejsu dostawcy.

Większość urządzeń innych niż Cuttlefish kieruje reklamy na najnowszą macierz zgodności dopiero po zamrożeniu interfejsów, dlatego nie ma różnicy w bibliotekach AIDL opartych na RELEASE_AIDL_USE_UNFROZEN.

Macierze

Interfejsy należące do partnerów są dodawane do macierzy zgodności specyficznych dla urządzeń lub usług, na które urządzenie jest kierowane w trakcie programowania. Gdy więc nowa, niezablokowana wersja interfejsu zostanie dodana do macierzy zgodności, wcześniej 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, 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 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 odzwierciedlić zmianę w wygenerowanej bibliotece AIDL. Wersja została 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ą wcześniej obsługiwaną blokowaną wersją. Gdy RELEASE_AIDL_USE_UNFROZEN ma wartość false, usługi zawsze wyglądają jak ostatnia zablokowana wersja lub wcześniej (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ą, aby mogły działać, gdy RELEASE_AIDL_USE_UNFROZEN ma wartość false. Uwzględnienie zgodności wstecznej w implementacjach i kodzie urządzenia to nowe ćwiczenie. Zapoznaj się z sekcją Używanie interfejsów z różnymi wersjami interfejsu.

Zasadniczo kwestie zgodności wstecznej są zasadniczo takie same w przypadku klientów i serwerów, a także kodu platformy i kodu dostawcy, ale musisz pamiętać o niewielkich różnicach, ponieważ obecnie wdrażane są 2 wersje korzystające z tego samego kodu źródłowego (obecna, niezablokowana wersja).

Przykład: interfejs ma 3 zablokowane wersje. Interfejs zostanie zaktualizowany o nową metodę. Klient i usługa zostaną zaktualizowane tak, aby korzystały z biblioteki nowej wersji 4. Ponieważ biblioteka w wersji 4 jest oparta na niezablokowanej wersji interfejsu, działa tak 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że zostać usunięty.

Gdy wywołujesz metody z wywołaniami zwrotnymi, musisz płynnie obsługiwać przypadek, gdy zwracany jest parametr UNKNOWN_TRANSACTION. Klienty mogą wdrażać 2 różne wersje wywołania zwrotnego zależnie od konfiguracji wersji, więc nie możesz zakładać, że klient wyśle najnowszą wersję, a nowe metody mogą ją zwracać. Jest to podobne do sposobu utrzymywania zgodności wstecznej z serwerami przez stabilne klienty AIDL w sposób opisany 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 to, której wersji używa interfejs zdalny, służy foo->getInterfaceVersion(). Jednak dzięki obsłudze wersji opartej na flagach implementujesz 2 różne wersje, więc może Ci się przydać wersja obecnego 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 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 i klienty nie mogą go znaleźć.

Możesz dodawać usługi warunkowo, bazując na wartości flagi RELEASE_AIDL_USE_UNFROZEN w pliku marki urządzenia:

ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
    android.hardware.health.storage-service
endif

Jeśli usługa jest częścią większego procesu, więc nie możesz dodać jej 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 korygujemy tablicę 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. Skorygujemy te zasady target-level i PRODUCT_SHIPPING_API_LEVEL, aby mieć pewność, że w przyszłorocznej wersji trafi na rynek urządzenie, które zostało przetestowane i spełnia 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 nowej wersji (VSR).

Gdy RELEASE_AIDL_USE_UNFROZEN ma wartość false, mątwy ma poprzednie target-level i PRODUCT_SHIPPING_API_LEVEL odzwierciedlające urządzenie, z którego wyprodukowano produkt. W Androidzie 14 i starszych wersjach to rozróżnienie będzie możliwe za pomocą różnych gałęzi Git, które nie przyjmują zmiany w FCM target-level, na poziomie interfejsu API dostawy ani innego kodu kierowanego na następną wersję.

Reguły nazewnictwa modułów

W Androidzie 11 dla każdej kombinacji włączonych wersji i włączonych backendów automatycznie tworzony jest moduł biblioteki skróconej. Aby odwołać się do konkretnego modułu biblioteki skróconej w celu utworzenia linku, 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 wierzchołku” (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 – platformy,
    • 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 odwołuje się do najnowszej wersji stabilnej, to „foo-V2-backend
  • Adres foo-unstable-backend, który odwołuje się do wersji Warunków korzystania z usługi, zmienia się w 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.

Wysyłanie zapytania o wersję 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 zamiast IFoo należy użyć kodu super, aby uniknąć błędów podczas kopiowania i wklejania. W zależności od konfiguracji javac do wyłączenia ostrzeżeń może być potrzebna adnotacja @SuppressWarnings("static"):

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.

Konwertuję istniejące AIDL na uporządkowane/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.