Stabilny AIDL

W systemie Android 10 dodano obsługę stabilnego języka definicji interfejsu systemu Android (AIDL), nowego sposobu śledzenia interfejsu programu aplikacji (API)/interfejsu binarnego aplikacji (ABI) udostępnianego przez interfejsy AIDL. Stabilny AIDL ma następujące kluczowe różnice w stosunku do AIDL:

  • Interfejsy są zdefiniowane w systemie kompilacji za pomocą aidl_interfaces .
  • Interfejsy mogą zawierać tylko dane strukturalne. Przesyłki reprezentujące żądane typy są tworzone automatycznie na podstawie ich definicji AIDL i są automatycznie porządkowane i nieorganizowane.
  • Interfejsy można zadeklarować jako stabilne (kompatybilne wstecz). Kiedy tak się dzieje, ich interfejs API jest śledzony i wersjonowany w pliku obok interfejsu AIDL.

Strukturalny i stabilny AIDL

Strukturalny AIDL odnosi się do typów zdefiniowanych wyłącznie w AIDL. Na przykład deklaracja umożliwiająca pakowanie (niestandardowa możliwość pakowania) nie jest ustrukturyzowanym AIDL. Przesyłki posiadające pola zdefiniowane w AIDL nazywane są przesyłkami strukturalnymi .

Stabilny AIDL wymaga ustrukturyzowanego AIDL, aby system kompilacji i kompilator mogły zrozumieć, czy zmiany wprowadzone w pakietach są kompatybilne wstecz. Jednak nie wszystkie interfejsy strukturalne są stabilne. Aby interfejs był stabilny, musi używać wyłącznie typów strukturalnych, a także musi korzystać z następujących funkcji wersjonowania. I odwrotnie, interfejs nie jest stabilny, jeśli do jego zbudowania użyto podstawowego systemu kompilacji lub jeśli ustawiono unstable:true .

Definiowanie interfejsu AIDL

Definicja aidl_interface wygląda następująco:

aidl_interface {
    name: "my-aidl",
    srcs: ["srcs/aidl/**/*.aidl"],
    local_include_dir: "srcs/aidl",
    imports: ["other-aidl"],
    versions_with_info: [
        {
            version: "1",
            imports: ["other-aidl-V1"],
        },
        {
            version: "2",
            imports: ["other-aidl-V3"],
        }
    ],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
        rust: {
            enabled: true,
        },
    },

}
  • name : Nazwa modułu interfejsu AIDL, który jednoznacznie identyfikuje interfejs AIDL.
  • srcs : Lista plików źródłowych AIDL tworzących interfejs. Ścieżka dla typu AIDL Foo zdefiniowanego w pakiecie com.acme powinna znajdować się pod <base_path>/com/acme/Foo.aidl , gdzie <base_path> może być dowolnym katalogiem powiązanym z katalogiem, w którym znajduje się Android.bp . W powyższym przykładzie <base_path> to srcs/aidl .
  • local_include_dir : Ścieżka, od której zaczyna się nazwa pakietu. Odpowiada <base_path> wyjaśnionej powyżej.
  • imports : Lista modułów aidl_interface , których używa. Jeśli jeden z twoich interfejsów AIDL korzysta z interfejsu lub pakietu z innego aidl_interface , wpisz tutaj jego nazwę. Może to być sama nazwa, odnosząca się do najnowszej wersji, lub nazwa z przyrostkiem wersji (np. -V1 ), odnosząca się do konkretnej wersji. Określanie wersji jest obsługiwane od wersji Androida 12
  • versions : poprzednie wersje interfejsu, które są zamrożone w api_dir . Począwszy od Androida 11, versions są zamrożone w aidl_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ąpione versions_with_info dla wersji 13 i nowszych.
  • versions_with_info : Lista krotek, z których każda zawiera nazwę zamrożonej wersji oraz listę importowanych wersji innych modułów interfejsu AIDS zaimportowanych przez tę wersję interfejsu AIDS. Definicja wersji V interfejsu AIDL IFACE znajduje się pod aidl_api/ IFACE / V . Pole to zostało wprowadzone w Androidzie 13 i nie powinno być modyfikowane bezpośrednio w Android.bp. Pole dodaje się lub aktualizuje poprzez wywołanie *-update-api lub *-freeze-api . Ponadto pola versions są automatycznie migrowane do versions_with_info , gdy użytkownik wywoła *-update-api lub *-freeze-api .
  • stability : Opcjonalna flaga zapewniająca stabilność tego interfejsu. Obecnie obsługuje tylko "vintf" . Jeśli ta opcja nie jest ustawiona, odpowiada to interfejsowi ze stabilnością w tym kontekście kompilacji (więc załadowanego tutaj interfejsu można używać tylko z rzeczami skompilowanymi razem, na przykład w pliku system.img). Jeśli jest ustawione na "vintf" , odpowiada to obietnicy stabilności: interfejs musi być stabilny tak długo, jak jest używany.
  • gen_trace : Opcjonalna flaga umożliwiająca włączenie lub wyłączenie śledzenia. Począwszy od Androida 14, wartość true dotyczy backendów cpp i java .
  • host_supported : opcjonalna flaga, która po ustawieniu na true udostępnia wygenerowane biblioteki środowisku hosta.
  • unstable : Opcjonalna flaga używana do zaznaczenia, że ​​ten interfejs nie musi być stabilny. Gdy ta opcja jest ustawiona na true , system kompilacji nie tworzy zrzutu interfejsu API dla interfejsu ani nie wymaga jego aktualizacji.
  • frozen : Opcjonalna flaga, której ustawienie na true oznacza, że ​​w interfejsie nie wprowadzono żadnych zmian od poprzedniej wersji interfejsu. Umożliwia to większą liczbę kontroli w czasie kompilacji. Ustawienie wartości false oznacza, że ​​interfejs jest w fazie rozwoju i wprowadzane są w nim nowe zmiany, więc uruchomienie foo-freeze-api wygeneruje nową wersję i automatycznie zmieni wartość na true . Wprowadzono w Androidzie 14.
  • backend.<type>.enabled : Te flagi przełączają każdy z backendów, dla których kompilator AIDL generuje kod. Obecnie obsługiwane są cztery backendy: Java, C++, NDK i Rust. Zaplecze Java, C++ i NDK są domyślnie włączone. Jeśli którykolwiek z tych trzech backendów nie jest potrzebny, należy go jawnie wyłączyć. Rdza jest domyślnie wyłączona.
  • backend.<type>.apex_available : Lista nazw APEX, dla których dostępna jest wygenerowana biblioteka pośrednicząca.
  • backend.[cpp|java].gen_log : Opcjonalna flaga określająca, czy generować dodatkowy kod w celu gromadzenia informacji o transakcji.
  • backend.[cpp|java].vndk.enabled : Opcjonalna flaga czyniąca ten interfejs częścią VNDK. Wartość domyślna to false .
  • backend.[cpp|ndk].additional_shared_libraries : wprowadzona w systemie Android 14, ta flaga dodaje zależności do bibliotek natywnych. Ta flaga jest użyteczna z ndk_header i cpp_header .
  • backend.java.sdk_version : opcjonalna flaga określająca wersję pakietu SDK, na podstawie którego zbudowana jest biblioteka pośrednicząca języka Java. Wartość domyślna to "system_current" . Nie należy tego ustawiać, jeśli backend.java.platform_apis ma wartość true.
  • backend.java.platform_apis : opcjonalna flaga, która powinna być ustawiona na true , gdy wygenerowane biblioteki muszą być kompilowane w oparciu o interfejs API platformy, a nie SDK.

Dla każdej kombinacji wersji i włączonych backendów tworzona jest biblioteka pośrednicząca. Aby dowiedzieć się, jak odwoływać się do konkretnej wersji biblioteki pośredniczącej dla konkretnego backendu, zobacz Reguły nazewnictwa modułów .

Zapisywanie plików AIDL

Interfejsy w stabilnym AIDL są podobne do tradycyjnych interfejsów, z tą różnicą, że nie wolno w nich używać nieustrukturyzowanych pakietów (ponieważ nie są one stabilne! zobacz Strukturalny a stabilny AIDL ). Podstawową różnicą w stabilnym AIDL jest sposób definiowania przesyłek. Wcześniej przesyłki kurierskie były deklarowane jako forward ; w stabilnym (a zatem ustrukturyzowanym) AIDL pola i zmienne parcelable są zdefiniowane jawnie.

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

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

Wartość domyślna jest obecnie obsługiwana (ale nie wymagana) dla boolean , char , float , double , byte , int , long i String . W systemie Android 12 obsługiwane są także wartości domyślne dla wyliczeń zdefiniowanych przez użytkownika. Jeśli nie określono wartości domyślnej, używana jest wartość podobna do 0 lub pusta. Wyliczenia bez wartości domyślnej są inicjowane na 0, nawet jeśli nie ma modułu wyliczającego zero.

Korzystanie z bibliotek pośredniczących

Po dodaniu bibliotek pośredniczących jako zależności do modułu, możesz dołączyć je do swoich plików. Oto przykłady bibliotek pośredniczących w systemie kompilacji ( Android.mk może być również używany w przypadku starszych definicji modułów):

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if desire is to load a library and share
    // it among multiple users or if you only need access to constants
    static_libs: ["my-module-name-java"],
    ...
}
# or
rust_... {
    name: ...,
    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

Wersjonowanie interfejsów

Zadeklarowanie modułu o nazwie foo tworzy również element docelowy w systemie kompilacji, którego można użyć do zarządzania interfejsem API modułu. Po zbudowaniu foo-freeze-api dodaje nową definicję API w api_dir lub aidl_api/ name , w zależności od wersji Androida, i dodaje plik .hash , oba reprezentujące nowo zamrożoną wersję interfejsu. foo-freeze-api aktualizuje również versions_with_info , aby odzwierciedlić dodatkową wersję i imports dla tej wersji. Zasadniczo imports w versions_with_info jest kopiowany z pola imports . Jednak najnowsza stabilna wersja jest określona w imports versions_with_info dla importu, który nie ma jawnej wersji. Po określeniu właściwości versions_with_info system kompilacji sprawdza kompatybilność między wersjami zamrożonymi, a także między wersją Top of Tree (ToT) a najnowszą wersją zamrożoną.

Dodatkowo musisz zarządzać definicją API wersji ToT. Za każdym razem, gdy interfejs API jest aktualizowany, uruchom foo-update-api , aby zaktualizować aidl_api/ name /current , który zawiera definicję API wersji ToT.

Aby zachować stabilność interfejsu, właściciele mogą dodać nowe:

  • Metody na koniec interfejsu (lub metody z jawnie zdefiniowanymi nowymi serialami)
  • Elementy na końcu działki (wymaga dodania wartości domyślnej dla każdego elementu)
  • Wartości stałe
  • W systemie Android 11 moduły wyliczające
  • W systemie Android 12 pola znajdują się na końcu związku

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

Aby sprawdzić, czy wszystkie interfejsy zostały zamrożone przed publikacją, możesz zbudować wersję z ustawionymi następującymi zmiennymi środowiskowymi:

  • AIDL_FROZEN_REL=true m ... - kompilacja wymaga zamrożenia wszystkich stabilnych interfejsów AIDL, które nie mają owner: pole określone.
  • AIDL_FROZEN_OWNERS="aosp test" - kompilacja wymaga zamrożenia wszystkich stabilnych interfejsów AIDL z owner: pole określone jako "aosp" lub "test".

Stabilność importu

Aktualizowanie wersji importów dla zamrożonych wersji interfejsu jest kompatybilne wstecz w warstwie stabilnej AIDL. Jednak ich aktualizacja wymaga aktualizacji wszystkich serwerów i klientów korzystających ze starej wersji interfejsu, a niektóre aplikacje mogą się mylić podczas mieszania różnych wersji typów. Ogólnie rzecz biorąc, w przypadku pakietów zawierających tylko typy lub pakietów wspólnych jest to bezpieczne, ponieważ kod musi już zostać napisany, aby obsługiwać nieznane typy z transakcji IPC.

W platformie Android kod android.hardware.graphics.common jest największym przykładem tego typu aktualizacji wersji.

Korzystanie z wersjonowanych interfejsów

Metody interfejsu

Podczas próby wywołania nowych metod na starym serwerze w czasie wykonywania nowi klienci otrzymują błąd lub wyjątek, w zależności od zaplecza.

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

Aby zapoznać się ze strategiami radzenia sobie z tym, zobacz odpytywanie wersji i używanie ustawień domyślnych .

Przesyłki

Kiedy do przesyłek dodawane są nowe pola, starzy klienci i serwery je usuwają. Gdy nowi klienci i serwery otrzymają stare przesyłki, domyślne wartości nowych pól zostaną automatycznie wypełnione. Oznacza to, że należy określić wartości domyślne dla wszystkich nowych pól przesyłki.

Klienci nie powinni oczekiwać, że serwery będą używać nowych pól, chyba że wiedzą, że serwer implementuje wersję, w której zdefiniowano pole (patrz wersje zapytań ).

Wyliczenia i stałe

Podobnie klienci i serwery powinni odpowiednio odrzucać lub ignorować nierozpoznane wartości stałe i moduły wyliczające, ponieważ w przyszłości mogą zostać dodane kolejne. Na przykład serwer nie powinien przerywać działania po odebraniu modułu wyliczającego, o którym nie wie. Powinien to zignorować lub zwrócić coś, aby klient wiedział, że nie jest obsługiwany w tej implementacji.

Związki

Próba wysłania unii z nowym polem kończy się niepowodzeniem, jeśli odbiorca jest stary i nie wie o tym polu. Implementacja nigdy nie spowoduje połączenia z nowym polem. Niepowodzenie jest ignorowane, jeśli jest to transakcja jednokierunkowa; w przeciwnym razie błąd to BAD_VALUE (dla zaplecza C++ lub NDK) lub IllegalArgumentException (dla zaplecza Java). Błąd pojawia się, jeśli klient wysyła zestaw unii do nowego pola na stary serwer lub gdy jest to stary klient odbierający unię z nowego serwera.

Rozwój oparty na flagach

Interfejsów w fazie rozwoju (niezamrożonych) nie można używać na urządzeniach w wersji, ponieważ nie ma gwarancji, że będą one kompatybilne wstecz.

AIDL obsługuje rezerwę czasu wykonywania dla tych niezamrożonych bibliotek interfejsów, aby kod mógł zostać napisany w oparciu o najnowszą niezamrożoną wersję i nadal mógł być używany na urządzeniach wydających. Zachowanie klientów zgodne wstecznie jest podobne do istniejącego zachowania, a w przypadku powrotu implementacje również muszą podążać za tymi zachowaniami. Zobacz Korzystanie z interfejsów wersjonowanych .

Flaga kompilacji AIDL

Flaga kontrolująca to zachowanie to RELEASE_AIDL_USE_UNFROZEN zdefiniowana w build/release/build_flags.bzl . true oznacza, że ​​w czasie wykonywania używana jest niezamrożona wersja interfejsu, a false oznacza, że ​​wszystkie biblioteki niezamrożonych wersji zachowują się jak ich ostatnia zamrożona wersja. Możesz zastąpić flagę true dla lokalnego rozwoju, ale przed wydaniem musisz przywrócić ją do false . Zazwyczaj programowanie odbywa się z konfiguracją, która ma flagę ustawioną na true .

Macierz zgodności i manifesty

Obiekty interfejsu dostawcy (obiekty VINTF) definiują oczekiwane wersje i wersje udostępniane po obu stronach interfejsu dostawcy.

Większość urządzeń innych niż mątwy korzysta z najnowszej macierzy zgodności dopiero po zamrożeniu interfejsów, więc nie ma różnicy w bibliotekach AIDL opartych na RELEASE_AIDL_USE_UNFROZEN .

Matryce

Interfejsy należące do partnerów są dodawane do macierzy zgodności specyficznych dla urządzenia lub produktu, które są przeznaczone dla urządzenia podczas programowania. Zatem kiedy do macierzy kompatybilności dodawana jest nowa, niezamrożona wersja interfejsu, poprzednie zamrożone wersje muszą pozostać przez RELEASE_AIDL_USE_UNFROZEN=false . Można sobie z tym poradzić, używając różnych plików macierzy zgodności dla różnych konfiguracji RELEASE_AIDL_USE_UNFROZEN lub zezwalając na użycie obu wersji w jednym pliku macierzy zgodności, który jest używany we wszystkich konfiguracjach.

Na przykład, dodając niezamrożoną wersję 4, użyj <version>3-4</version> .

Kiedy wersja 4 jest zablokowana, możesz usunąć wersję 3 z macierzy zgodności, ponieważ zamrożona wersja 4 jest używana, gdy RELEASE_AIDL_USE_UNFROZEN ma false .

Manifestuje

W systemie Android 15 (eksperymentalny AOSP) wprowadzono zmianę w libvintf w celu modyfikowania plików manifestu w czasie kompilacji w oparciu o wartość RELEASE_AIDL_USE_UNFROZEN .

Manifesty i fragmenty manifestu deklarują, która wersja interfejsu jest implementowana przez usługę. W przypadku korzystania z najnowszej niezamrożonej wersji interfejsu należy zaktualizować manifest, aby odzwierciedlał tę nową wersję. Gdy RELEASE_AIDL_USE_UNFROZEN=false wpisy manifestu są dostosowywane przez libvintf aby odzwierciedlić zmiany w wygenerowanej bibliotece AIDL. Wersja została zmodyfikowana z niezamrożonej wersji N do ostatniej zamrożonej wersji N - 1 . Dlatego użytkownicy nie muszą zarządzać wieloma manifestami ani fragmentami manifestów dla każdej ze swoich usług.

Zmiany klienta HAL

Kod klienta HAL musi być wstecznie kompatybilny z każdą poprzednią obsługiwaną zamrożoną wersją. Gdy RELEASE_AIDL_USE_UNFROZEN ma false , usługi zawsze wyglądają jak ostatnia wersja zablokowana lub wcześniejsza (na przykład wywołanie nowych niezamrożonych metod zwraca UNKNOWN_TRANSACTION lub nowe pola parcelable mają wartości domyślne). Klienci platformy Android muszą być kompatybilni wstecz z dodatkowymi poprzednimi wersjami, ale jest to nowy szczegół dla klientów dostawców i klientów interfejsów będących własnością partnerów.

Zmiany w implementacji HAL

Największą różnicą w rozwoju HAL w porównaniu z rozwojem opartym na flagach jest wymóg, aby implementacje HAL były kompatybilne wstecz z ostatnią zamrożoną wersją, aby działały, gdy RELEASE_AIDL_USE_UNFROZEN ma false . Uwzględnienie kompatybilności wstecznej w implementacjach i kodzie urządzenia to nowe ćwiczenie. Zobacz Korzystanie z interfejsów wersjonowanych .

Uwagi dotyczące kompatybilności wstecznej są generalnie takie same dla klientów i serwerów, a także dla kodu frameworka i kodu dostawcy, ale istnieją subtelne różnice, o których musisz wiedzieć, ponieważ teraz skutecznie wdrażasz dwie wersje korzystające z tego samego kodu źródłowego (aktualna, niezamrożona wersja).

Przykład: interfejs ma trzy zamrożone wersje. Interfejs został zaktualizowany o nową metodę. Zarówno klient, jak i usługa zostały zaktualizowane w celu korzystania z nowej biblioteki w wersji 4. Ponieważ biblioteka V4 bazuje na niezamrożonej wersji interfejsu, zachowuje się jak ostatnia zamrożona wersja, wersja 3, gdy RELEASE_AIDL_USE_UNFROZEN ma false i uniemożliwia użycie nowej metody.

Gdy interfejs jest zamrożony, wszystkie wartości RELEASE_AIDL_USE_UNFROZEN korzystają z tej zamrożonej wersji, a kod obsługujący kompatybilność wsteczną może zostać usunięty.

Wywołując metody wywołań zwrotnych, należy elegancko obsłużyć przypadek, gdy zwrócona zostanie UNKNOWN_TRANSACTION . Klienci mogą implementować dwie różne wersje wywołania zwrotnego w zależności od konfiguracji wydania, więc nie można zakładać, że klient wyśle ​​najnowszą wersję, a nowe metody mogą to zwrócić. Jest to podobne do sposobu, w jaki stabilni klienci AIDL utrzymują kompatybilność wsteczną z serwerami, co opisano w sekcji Korzystanie z interfejsów wersjonowanych .

// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
    mMyCallback = cb;
    // Get the version of the callback for later when we call methods on it
    auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
    return status;
}

// Example of using the callback later
void NotifyCallbackLater() {
  // From the latest frozen version (V2)
  mMyCallback->foo();
  // Call this method from the unfrozen V3 only if the callback is at least V3
  if (mMyCallbackVersion >= 3) {
    mMyCallback->bar();
  }
}

Nowe pola w istniejących typach ( parcelable , enum , union ) mogą nie istnieć lub zawierać wartości domyślne, gdy RELEASE_AIDL_USE_UNFROZEN ma wartość false , a wartości nowych pól, które usługa próbuje wysłać, są odrzucane na końcu procesu.

Nowe typy dodane w tej niezamrożonej wersji nie mogą być wysyłane ani odbierane przez interfejs.

Implementacja nigdy nie otrzymuje wywołania nowych metod od żadnego klienta, gdy RELEASE_AIDL_USE_UNFROZEN ma false .

Uważaj, aby używać nowych modułów wyliczających tylko w wersji, w której zostały wprowadzone, a nie w poprzedniej wersji.

Zwykle używasz foo->getInterfaceVersion() , aby zobaczyć, której wersji używa zdalny interfejs. Jednak w przypadku obsługi wersji opartej na flagach implementujesz dwie różne wersje, więc możesz chcieć uzyskać wersję bieżącego interfejsu. Możesz to zrobić, pobierając wersję interfejsu bieżącego obiektu, na przykład this->getInterfaceVersion() lub inne metody dla my_ver . Więcej informacji można znaleźć w sekcji Wykonywanie zapytań o wersję interfejsu obiektu zdalnego .

Nowe stabilne interfejsy VINTF

Po dodaniu nowego pakietu interfejsu AIDL nie ma ostatniej zamrożonej wersji, więc nie ma żadnego zachowania, do którego można by się odwołać, gdy RELEASE_AIDL_USE_UNFROZEN ma false . Nie używaj tych interfejsów. Gdy RELEASE_AIDL_USE_UNFROZEN ma false , Menedżer usług nie pozwoli usłudze zarejestrować interfejsu, a klienci go nie znajdą.

Możesz dodać usługi warunkowo w oparciu o wartość flagi RELEASE_AIDL_USE_UNFROZEN w pliku makefile urządzenia:

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

Jeśli usługa jest częścią większego procesu i nie można jej warunkowo dodać do urządzenia, możesz sprawdzić, czy usługa jest zadeklarowana za pomocą IServiceManager::isDeclared() . Jeśli został zadeklarowany i nie udało się go zarejestrować, przerwij proces. Jeśli nie zostanie zadeklarowany, oczekuje się, że nie zostanie zarejestrowany.

Mątwy jako narzędzie rozwojowe

Co roku po zamrożeniu VINTF dostosowujemy target-level macierzy zgodności platformy (FCM) i PRODUCT_SHIPPING_API_LEVEL mątwy, aby odzwierciedlały urządzenia, które zostaną wprowadzone na rynek wraz z przyszłoroczną premierą. Dostosowujemy target-level i PRODUCT_SHIPPING_API_LEVEL , aby upewnić się, że istnieje urządzenie uruchamiające, które zostało przetestowane i spełnia nowe wymagania dla przyszłorocznej wersji.

Gdy RELEASE_AIDL_USE_UNFROZEN ma true , mątwy są wykorzystywane do opracowywania przyszłych wydań Androida. Dotyczy poziomu FCM przyszłorocznej wersji Androida i PRODUCT_SHIPPING_API_LEVEL , co wymaga spełnienia wymagań oprogramowania dostawcy (VSR) następnej wersji.

Gdy RELEASE_AIDL_USE_UNFROZEN ma wartość false , Mątwa ma poprzedni target-level i PRODUCT_SHIPPING_API_LEVEL odzwierciedlający urządzenie zwalniające. W systemie Android 14 i starszych wersjach tego rozróżnienia można dokonać za pomocą różnych gałęzi Git, które nie uwzględniają zmiany na target-level FCM, poziomie interfejsu API wysyłki ani żadnym innym kodzie przeznaczonym dla następnej wersji.

Zasady nazewnictwa modułów

W systemie Android 11 dla każdej kombinacji wersji i włączonych backendów automatycznie tworzony jest moduł biblioteki pośredniczącej. Aby odwołać się do konkretnego modułu biblioteki pośredniczącej w celu łączenia, nie używaj nazwy modułu aidl_interface , ale nazwę modułu biblioteki pośredniczącej, czyli ifacename - version - backend , gdzie

  • ifacename : nazwa modułu aidl_interface
  • version jest jedną z
    • V version-number dla wersji zamrożonych
    • V latest-frozen-version-number + 1 dla wersji z wierzchołka drzewa (jeszcze do zamrożenia)
  • backend to jedno z nich
    • java dla backendu Java,
    • cpp dla backendu C++,
    • ndk lub ndk_platform dla zaplecza NDK. Pierwsza przeznaczona jest do aplikacji, druga do korzystania z platformy,
    • rust dla backendu Rusta.

Załóżmy, że istnieje moduł o nazwie foo , a jego najnowsza wersja to 2 i obsługuje zarówno NDK, jak i C++. W tym przypadku AIDL generuje następujące moduły:

  • Na podstawie wersji 1
    • foo-V1-(java|cpp|ndk|ndk_platform|rust)
  • Na podstawie wersji 2 (najnowsza stabilna wersja)
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • Na podstawie wersji ToT
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

W porównaniu do Androida 11,

  • foo- backend , który odnosił się do najnowszej stabilnej wersji, staje się foo- V2 - backend
  • foo-unstable- backend , który odnosił się do wersji ToT staje się foo- V3 - backend

Nazwy plików wyjściowych są zawsze takie same jak nazwy modułów.

  • Na podstawie wersji 1: foo-V1-(cpp|ndk|ndk_platform|rust).so
  • Na podstawie wersji 2: foo-V2-(cpp|ndk|ndk_platform|rust).so
  • Na podstawie wersji ToT: foo-V3-(cpp|ndk|ndk_platform|rust).so

Należy zauważyć, że kompilator AIDL nie tworzy ani modułu wersji unstable , ani modułu niewersjonowanego dla stabilnego interfejsu AIDL. Począwszy od Androida 12, nazwa modułu wygenerowana ze stabilnego interfejsu AIDL zawsze zawiera jego wersję.

Nowe metody metainterfejsu

Android 10 dodaje kilka metod metainterfejsu dla stabilnego AIDL.

Zapytanie o wersję interfejsu obiektu zdalnego

Klienci mogą wysyłać zapytania o wersję i skrót interfejsu implementowanego przez obiekt zdalny i porównywać zwracane wartości z wartościami interfejsu używanego przez klienta.

Przykład z backendem cpp :

sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();

Przykład z backendem ndk (i ndk_platform ):

IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);

Przykład z backendem java :

IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
  // the remote side is using an older interface
}

String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();

W przypadku języka Java strona zdalna MUSI zaimplementować getInterfaceVersion() i getInterfaceHash() w następujący sposób (zamiast IFoo używane jest super , aby uniknąć błędów kopiowania/wklejania. Adnotacja @SuppressWarnings("static") może być potrzebna do wyłączenia ostrzeżeń, w zależności od konfiguracja javac ):

class MyFoo extends IFoo.Stub {
    @Override
    public final int getInterfaceVersion() { return super.VERSION; }

    @Override
    public final String getInterfaceHash() { return super.HASH; }
}

Dzieje się tak, ponieważ wygenerowane klasy ( IFoo , IFoo.Stub itp.) są współdzielone pomiędzy klientem a serwerem (na przykład klasy mogą znajdować się w startowej ścieżce klas). Kiedy klasy są współdzielone, serwer jest również łączony z najnowszą wersją klas, nawet jeśli mógł zostać zbudowany ze starszą wersją interfejsu. Jeśli ten metainterfejs jest zaimplementowany w klasie współdzielonej, zawsze zwraca najnowszą wersję. Jednakże, implementując metodę opisaną powyżej, numer wersji interfejsu jest osadzony w kodzie serwera (ponieważ IFoo.VERSION jest static final int , która jest wstawiana podczas odniesienia), dzięki czemu metoda może zwrócić dokładną wersję, w której serwer został zbudowany z.

Radzenie sobie ze starszymi interfejsami

Możliwe, że klient został zaktualizowany do nowszej wersji interfejsu AIDL, ale serwer używa starego interfejsu AIDL. W takich przypadkach wywołanie metody na starym interfejsie zwraca UNKNOWN_TRANSACTION .

Dzięki stabilnemu AIDL klienci mają większą kontrolę. Po stronie klienta możesz ustawić domyślną implementację interfejsu AIDL. Metoda w domyślnej implementacji jest wywoływana tylko wtedy, gdy metoda nie jest zaimplementowana po stronie zdalnej (ponieważ została zbudowana ze starszą wersją interfejsu). Ponieważ wartości domyślne są ustawiane globalnie, nie należy ich używać w potencjalnie współdzielonych kontekstach.

Przykład w C++ w systemie Android 13 i nowszych wersjach:

class MyDefault : public IFooDefault {
  Status anAddedMethod(...) {
   // do something default
  }
};

// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());

foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
                         // remote side is not implementing it

Przykład w Javie:

IFoo.Stub.setDefaultImpl(new IFoo.Default() {
    @Override
    public xxx anAddedMethod(...)  throws RemoteException {
        // do something default
    }
}); // once per an interface in a process


foo.anAddedMethod(...);

Nie musisz podawać domyślnej implementacji wszystkich metod w interfejsie AIDL. Metody, które na pewno zostaną zaimplementowane po stronie zdalnej (ponieważ masz pewność, że zdalny został zbudowany, gdy metody znajdowały się w opisie interfejsu AIDL) nie muszą być nadpisywane w domyślnej klasie impl .

Konwersja istniejącego AIDL na ustrukturyzowany/stabilny AIDL

Jeśli masz istniejący interfejs AIDL i kod, który go używa, wykonaj poniższe kroki, aby przekonwertować interfejs na stabilny interfejs AIDL.

  1. Zidentyfikuj wszystkie zależności swojego interfejsu. Dla każdego pakietu, od którego zależy interfejs, określ, czy pakiet jest zdefiniowany w stabilnym AIDL. Jeśli nie zdefiniowano, pakiet musi zostać przekonwertowany.

  2. Konwertuj wszystkie paczki w swoim interfejsie na stabilne paczki (same pliki interfejsu mogą pozostać niezmienione). Zrób to, wyrażając ich strukturę bezpośrednio w plikach AIDL. Aby korzystać z tych nowych typów, należy przepisać klasy zarządzania. Można to zrobić przed utworzeniem pakietu aidl_interface (poniżej).

  3. Utwórz pakiet aidl_interface (jak opisano powyżej), który zawiera nazwę modułu, jego zależności i wszelkie inne potrzebne informacje. Aby był ustabilizowany (a nie tylko ustrukturyzowany), należy go również wersjonować. Aby uzyskać więcej informacji, zobacz Interfejsy wersjonowania .