Stabilna wersja AIDL

Android 10 obsługuje stabilną wersję języka interfejsu AIDL (Android Interface Definition Language). Jest to nowy sposób śledzenia interfejsu programowania aplikacji (API) i interfejsu binarnego aplikacji (ABI) udostępnianego przez interfejsy AIDL. Stabilna wersja AIDL działa dokładnie tak samo jak AIDL, ale system kompilacji śledzi zgodność interfejsu, a Twoje możliwości są ograniczone:

  • Interfejsy są zdefiniowane w systemie kompilacji za pomocą aidl_interfaces.
  • Interfejsy mogą zawierać tylko uporządkowane dane. Obiekty Parcelables reprezentujące preferowane typy są tworzone automatycznie na podstawie ich definicji w AIDL i automatycznie pakowane oraz rozpakowywane.
  • Interfejsy mogą być deklarowane jako stabilne (zgodność wsteczna). W takim przypadku ich interfejs API jest śledzony i numerowany w pliku obok interfejsu AIDL.

Uporządkowane a stabilne AIDL

Uporządkowany AIDL odnosi się do typów zdefiniowanych wyłącznie w AIDL. Na przykład deklaracja parcelable (niestandardowa parcelable) nie jest uporządkowanym interfejsem AIDL. Materiały Parcelable z polami zdefiniowanymi w AIDL są nazywane elementami strukturalnymi.

Stabilna AIDL wymaga uporządkowanych danych AIDL, tak aby system kompilacji i kompilator mógł rozpoznać, czy zmiany wprowadzone w plikach parcelable są zgodne wstecznie. Nie wszystkie interfejsy uporządkowane są jednak stabilne. Aby zapewnić stabilność, interfejs musi używać tylko typów ustrukturyzowanych. Musi też korzystać z tych funkcji wersji: Z drugiej strony, interfejs nie jest stabilny, jeśli do jego kompilacji używany jest podstawowy system kompilacji lub ustawiona jest opcja unstable:true.

Definiowanie interfejsu AIDL

Definicja aidl_interface wygląda tak:

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

}
  • name: nazwa modułu interfejsu AIDL, który jednoznacznie identyfikuje interfejs AIDL.
  • srcs: lista plików źródłowych AIDL, które składają się na interfejs. Ścieżka do pliku AIDL typu Foo zdefiniowanego w pakiecie com.acme powinna znajdować się w katalogu <base_path>/com/acme/Foo.aidl, gdzie <base_path> może być dowolnym katalogiem powiązanym z katalogiem, w którym znajduje się plik 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 ona <base_path> opisanej powyżej.
  • imports: lista modułów aidl_interface, których używa ta funkcja. Jeśli jeden z interfejsów AIDL używa interfejsu lub obiektu Parcelable z innego interfejsu aidl_interface, podaj jego nazwę tutaj. Może to być sama nazwa, która odnosi się do najnowszej wersji, lub nazwa z sufiksem wersji (np. -V1) odnosząca się do konkretnej wersji. Określanie wersji jest obsługiwane od Androida 12.
  • versions: poprzednie wersje interfejsu, które są zamrożone w sekcji api_dir. Począwszy od Androida 11, versions są zamrożone w sekcji aidl_api/name. Jeśli nie ma żadnych zamrożonych wersji interfejsu, nie należy tego określać, a sprawdzanie zgodności nie będzie przeprowadzane. W Androidzie 13 i nowszych to pole zostało zastąpione polem versions_with_info.
  • versions_with_info: lista tupla, z których każdy zawiera nazwę zamrożonej wersji i listę importowanych wersji innych modułów aidl_interface, które zostały zaimportowane przez tę wersję interfejsu aidl_interface. Definicja wersji V interfejsu IFACE interfejsu AIDL znajduje się na stronie aidl_api/IFACE/V. To pole zostało wprowadzone w Androidzie 13 i nie należy go modyfikować bezpośrednio w Android.bp. Pole jest dodawane lub aktualizowane przez wywołanie *-update-api lub *-freeze-api. Ponadto pola versions są automatycznie przenoszone do pola versions_with_info, gdy użytkownik wywoła funkcję *-update-api lub *-freeze-api.
  • stability: opcjonalna flaga gwarantująca stabilność tego interfejsu. Ta opcja obsługuje tylko "vintf". Jeśli zasada stability nie jest skonfigurowana, system kompilacji sprawdza, czy interfejs jest zgodny wstecznie, chyba że podano parametr unstable. Stan „unset” odpowiada interfejsowi ze stabilnością w kontekście tej kompilacji (czyli wszystkie rzeczy systemowe, na przykład rzeczy w system.img i powiązanych partycjach, lub wszystkie rzeczy dostawcy, na przykład rzeczy w vendor.img i powiązanych partycjach). Jeśli stability ma wartość "vintf", oznacza to obietnicę stabilności: interfejs musi być stabilny przez cały czas jego używania.
  • gen_trace: opcjonalna flaga włączająca lub wyłączająca śledzenie. Począwszy od Androida 14 domyślnie jest to true w przypadku backendów cppjava.
  • host_supported: opcjonalna flaga, która w przypadku ustawienia na wartość true udostępnia wygenerowane biblioteki w środowisku hosta.
  • unstable: opcjonalna flaga służąca do oznaczania interfejsu, że nie musi być on stabilny. Gdy ta opcja ma wartość true, system kompilacji nie tworzy kopii zapasowej interfejsu API ani nie wymaga jej aktualizacji.
  • frozen: opcjonalna flaga, która po ustawieniu na true oznacza, że interfejs nie zawiera żadnych zmian w porównaniu z poprzednią wersją interfejsu. Umożliwia to więcej kontroli w czasie kompilacji. Gdy wartość to false, oznacza to, że interfejs jest w trakcie tworzenia i zawiera nowe zmiany. Uruchomienie foo-freeze-api spowoduje wygenerowanie nowej wersji i automatyczną zmianę wartości na true. Wprowadzona w Androidzie 14.
  • backend.<type>.enabled: te flagi przełączają każdy z backendów, dla których kompilator AIDL generuje kod. Obsługiwane są 4 backendy: Java, C++, NDK i Rust. Java, C++ i NDK są domyślnie włączone. Jeśli któryś z tych 3 systemów backendowych nie jest potrzebny, musisz go wyłączyć. Domyślnie funkcja Rust jest wyłączona do Androida 15.
  • backend.<type>.apex_available: lista nazw APEX, dla których dostępna jest wygenerowana biblioteka zastępcza.
  • backend.[cpp|java].gen_log: opcjonalna flaga, która określa, czy ma zostać wygenerowany dodatkowy kod do zbierania informacji o transakcji.
  • backend.[cpp|java].vndk.enabled: opcjonalna flaga, która powoduje, że ten interfejs jest częścią VNDK. Wartość domyślna to false.
  • backend.[cpp|ndk].additional_shared_libraries: ta flaga została wprowadzona w Androidzie 14 i dodaje zależności do bibliotek natywnych. Ta flaga jest przydatna w przypadku ndk_header i cpp_header.
  • backend.java.sdk_version: opcjonalna flaga do określania wersji pakietu SDK, dla której została skompilowana biblioteka stubów Javy. Wartość domyślna to "system_current". Nie należy go ustawiać, 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ą być kompilowane na platformie API, a nie na pakiecie SDK.

W przypadku każdej kombinacji wersji i włączonych backendów tworzona jest biblioteka szablonów. Informacje o tym, jak odwoływać się do konkretnej wersji biblioteki zasobów dla konkretnego backendu, znajdziesz w sekcji Zasady nazewnictwa modułów.

Tworzenie plików AIDL

Interfejsy w ramach stabilnej wersji interfejsu AIDL są podobne do tradycyjnych interfejsów, z tym wyjątkiem, że nie można w nich używać nieuporządkowanych obiektów Parcelable (ponieważ nie są one stabilne; zobacz Różnica między uporządkowanymi a stabilnymi interfejsami AIDL). Główna różnica w stabilnej wersji AIDL dotyczy sposobu definiowania obiektów Parcelable. Wcześniej obiekty parcelable były deklarowane w przód. W stabilnym (a zatem ustrukturyzowanym) pliku AIDL pola i zmienna parcelable są definiowane wprost.

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

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

Wartości domyślne są obsługiwane (ale nie są wymagane) w przypadku pól boolean, char, float, double, byte, int, longString. W Androidzie 12 obsługiwane są też domyślne wartości typów zdefiniowanych przez użytkownika. Jeśli wartość domyślna nie jest określona, używana jest wartość podobna do 0 lub pusta. Wyliczenia bez wartości domyślnej są inicjowane wartością 0, nawet jeśli nie ma żadnych zerowych wyliczników.

Korzystanie z bibliotek z stubami

Po dodaniu bibliotek zastępczych jako zależności do modułu możesz je uwzględnić w plikach. Oto przykłady bibliotek wersji pośredniej w systemie kompilacji (Android.mk może być też używany do starszych definicji modułów):

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if your preference is to load a library and share
    // it among multiple users or if you only need access to constants
    static_libs: ["my-module-name-java"],
    ...
}
# or
rust_... {
    name: ...,
    rustlibs: ["my-module-name-rust"],
    ...
}

Przykład w C++:

#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
    // use just like traditional AIDL

Przykład w Javie:

import some.package.IFoo;
import some.package.Thing;
...
    // use just like traditional AIDL

Przykład w Rust:

use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
    // use just like traditional AIDL

Interfejsy obsługi wersji

Zadeklarowanie modułu o nazwie foo powoduje też utworzenie w systemie kompilacji elementu docelowego umożliwiającego zarządzanie jego interfejsem API. Po skompilowaniu foo-freeze-api dodaje nową definicję interfejsu API w folderze api_dir lub aidl_api/name (w zależności od wersji Androida) oraz dodaje plik .hash. Oba te elementy reprezentują nowo zamrożoną wersję interfejsu. foo-freeze-api aktualizuje też właściwość versions_with_info, aby odzwierciedlała dodatkową wersję, oraz imports dla tej wersji. Pole imports w dokumentach versions_with_info jest kopiowane z pola imports. Najnowsza stabilna wersja jest jednak określona w importsversions_with_info dla importu, który nie ma wyraźnej wersji. Po określeniu właściwości versions_with_info system kompilacji przeprowadza sprawdzanie zgodności między zamrożonymi wersjami oraz między drzewem genealogicznym (ToT) a najnowszą zamrożoną wersją.

Musisz też zarządzać definicją interfejsu API wersji ToT. Po każdej aktualizacji interfejsu API uruchom polecenie foo-update-api, aby zaktualizować aidl_api/name/current, który zawiera definicję interfejsu API wersji ToT.

Aby zapewnić stabilność interfejsu, właściciele mogą dodawać nowe:

  • metody do końca interfejsu (lub metody z wyraźnie zdefiniowanymi nowymi serialami);
  • Elementy na końcu obiektu Parcelable (wymaga dodania domyślnego elementu dla każdego elementu)
  • Wartości stałe
  • W Androidzie 11:
  • W Androidzie 12 pola do końca sumy

Nie są dozwolone żadne inne działania, a nikt inny nie może modyfikować interfejsu (w przeciwnym razie może to spowodować konflikt z modyfikacjami wprowadzonymi przez właściciela).

Aby sprawdzić, czy wszystkie interfejsy są zamrożone do wydania, możesz utworzyć kompilację z tymi zmiennymi środowiskowymi:

  • AIDL_FROZEN_REL=true m ... – kompilacja wymaga zamrożenia wszystkich stabilnych interfejsów AIDL, które nie mają określonego pola owner:.
  • AIDL_FROZEN_OWNERS="aosp test" – kompilacja wymaga zamrożenia wszystkich stabilnych interfejsów AIDL, przy czym pole owner: musi być określone jako „aosp” lub „test”.

Stabilność importu

Aktualizowanie wersji importów w zamrożonych wersjach interfejsu jest wstecznie zgodne na poziomie stabilnego AIDL. Jednak ich aktualizacja wymaga zaktualizowania wszystkich serwerów i klientów, które korzystają z poprzedniej wersji interfejsu. Niektóre aplikacje mogą się mylić, gdy mieszają różne wersje typów. Ogólnie rzecz biorąc, w przypadku typowych lub przeznaczonych tylko typów pakietów jest to bezpieczne, ponieważ trzeba już wcześniej napisać kod do obsługi nieznanych typów transakcji IPC.

Największym przykładem tego typu uaktualnienia wersji jest kod android.hardware.graphics.common na Androidzie.

Korzystanie z interfejsów z wersjami

Metody interfejsu

Podczas działania, gdy nowi klienci próbują wywołać nowe metody na starym serwerze, otrzymują błąd lub wyjątek, w zależności od backendu.

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

Strategie radzenia sobie z tym problemem znajdziesz w artykułach zapytania o wersjeużywanie domyślnych wartości.

Parcelables

Gdy do obiektów parcelable zostaną dodane nowe pola, stare klienci i serwery je pominą. Gdy nowi klienci i serwery otrzymują stare obiekty parcelables, domyślne wartości nowych pól są automatycznie wypełniane. Oznacza to, że w przypadku wszystkich nowych pól w pakiecie należy określić wartości domyślne.

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

Wyliczenia i stałe

Podobnie klienty i serwery powinny odrzucać lub ignorować nierozpoznane wartości stałe i elementy wyliczające, ponieważ w przyszłości mogą zostać dodane kolejne. Na przykład serwer nie powinien przerywać działania, gdy otrzyma licznik, którego nie zna. Serwer powinien zignorować enumerator lub zwrócić coś, aby klient wiedział, że ta implementacja nie obsługuje tej funkcji.

Związki

Próba wysłania sumy z nowym polem kończy się niepowodzeniem, jeśli odbiorca jest stary i nie wie nic o tym polu. Implementacja nigdy nie zobaczy zbioru z nowym polem. Błąd jest ignorowany, jeśli jest to transakcja jednokierunkowa. W przeciwnym razie błąd to BAD_VALUE(w przypadku zaplecza C++ lub NDK) lub IllegalArgumentException(w przypadku zaplecza Java). Błąd zostanie odebrany, gdy klient wysyła sumę ustawioną w nowym polu do starego serwera lub gdy jest to stary klient odbierający połączenie z nowego serwera.

Zarządzanie wieloma wersjami

Nazwa domeny linkera w Androidzie może mieć tylko jedną wersję konkretnego interfejsu aidl, aby uniknąć sytuacji, w której wygenerowane typy aidl mają wiele definicji. W języku C++ obowiązuje reguła jednej definicji, która wymaga tylko jednej definicji każdego symbolu.

Kompilacja Androida wyświetla błąd, gdy moduł zależy od różnych wersji tej samej biblioteki aidl_interface. Moduł może być zależny od tych bibliotek bezpośrednio lub pośrednio przez zależności ich zależności. Te błędy pokazują wykres zależności od modułu, który nie działa, do wersji biblioteki aidl_interface, które się wykluczają. Wszystkie zależności muszą zostać zaktualizowane, aby uwzględniały tę samą (zwykle najnowszą) wersję tych bibliotek.

Jeśli biblioteka interfejsu jest używana przez wiele różnych modułów, warto utworzyć zmienne cc_defaults, java_defaultsrust_defaults dla każdej grupy bibliotek i procesów, które muszą używać tej samej wersji. Gdy wprowadzasz nową wersję interfejsu, te domyślne wartości mogą zostać zaktualizowane, a wszystkie moduły, które ich używają, zostaną zaktualizowane razem, aby nie korzystały z różnych wersji interfejsu.

cc_defaults {
  name: "my.aidl.my-process-group-ndk-shared",
  shared_libs: ["my.aidl-V3-ndk"],
  ...
}

cc_library {
  name: "foo",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

cc_binary {
  name: "bar",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

Gdy moduły aidl_interface importują inne moduły aidl_interface, powoduje to dodatkowe zależności, które wymagają używania określonych wersji. Ta sytuacja może być trudna do opanowania, gdy wspólne moduły aidl_interface są importowane w wielu modułach aidl_interface, które są używane razem w tych samych procesach.

aidl_interfaces_defaults można użyć do zachowania 1 definicji najnowszych wersji zależności aidl_interface, którą można aktualizować w jednym miejscu, i używać jej przez wszystkie moduły aidl_interface, które chcą zaimportować ten wspólny interfejs.

aidl_interface_defaults {
  name: "android.popular.common-latest-defaults",
  imports: ["android.popular.common-V3"],
  ...
}

aidl_interface {
  name: "android.foo",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

aidl_interface {
  name: "android.bar",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

Programowanie na podstawie flag

Interfejsów w trakcie tworzenia (niezamrożonych) nie można używać na urządzeniach w wersji produkcyjnej, ponieważ nie ma gwarancji, że są one zgodne ze starszymi wersjami.

AIDL obsługuje alternatywne wersje tych odmrożonych bibliotek interfejsu, aby kod napisany na podstawie najnowszej odmrożonej wersji mógł być nadal używany na urządzeniach z wersją opublikowaną. Wsteczna kompatybilność klientów jest podobna do dotychczasowego zachowania, a w przypadku implementacji zapasowych implementacje muszą również przestrzegać tych zachowań. Zobacz Używanie interfejsów z wersjonowaniem.

Flaga kompilacji AIDL

Flaga sterująca to zachowanie to RELEASE_AIDL_USE_UNFROZEN zdefiniowana w build/release/build_flags.bzl. true oznacza, że w czasie działania używana jest niezablokowana wersja interfejsu, a false oznacza, że biblioteki wszystkich niezablokowanych wersji zachowują się tak samo jak ostatnio zablokowane wersje. Na potrzeby programowania lokalnego możesz zmienić flagę na true, ale przed opublikowaniem wersji musisz przywrócić ją do wartości false. Zazwyczaj programowanie odbywa się w ramach konfiguracji z flagą ustawioną na true.

Tablica zgodności i pliki manifestu

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

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

Macierze

Interfejsy należące do partnera są dodawane do matryc zgodności dla poszczególnych urządzeń lub produktów, na które urządzenie jest kierowane podczas tworzenia. Dlatego, gdy do macierzy zgodności zostanie dodana nowa, odblokowana wersja interfejsu, poprzednie zamrożone wersje muszą pozostać w przypadku RELEASE_AIDL_USE_UNFROZEN=false. Możesz to zrobić, używając różnych plików tabeli zgodności dla różnych konfiguracji RELEASE_AIDL_USE_UNFROZENlub zezwalając na obie wersje w pojedynczym pliku tabeli zgodności, który jest używany we wszystkich konfiguracjach.

Na przykład podczas dodawania niezablokowanej wersji 4 użyj <version>3-4</version>.

Gdy wersja 4 jest zablokowana, możesz usunąć wersję 3 z matrycy zgodności, ponieważ gdy RELEASE_AIDL_USE_UNFROZEN jest false, używana jest zablokowana wersja 4.

Pliki manifestu

W Androidzie 15 wprowadzono zmianę w wartości libvintf, aby modyfikować pliki manifestu w czasie kompilacji na podstawie wartości RELEASE_AIDL_USE_UNFROZEN.

Pliki manifestu i ich fragmenty określają, która wersja interfejsu jest implementowana przez usługę. Jeśli używasz najnowszej wersji odblokowanego interfejsu, manifest musi zostać zaktualizowany, aby odzwierciedlał tę nową wersję. Gdy RELEASE_AIDL_USE_UNFROZEN=false, wpisy w pliku manifestu są dostosowywane przez libvintf, aby odzwierciedlić zmiany w wygenerowanej bibliotece AIDL. Wersja jest modyfikowana z wersji niezamrożonej (N) do ostatniej wersji zamrożonej (N - 1). Dzięki temu użytkownicy nie muszą zarządzać wieloma plikami manifestu ani fragmentami manifestu dla każdej usługi.

Zmiany w kliencie HAL

Kod klienta HAL musi być zgodny wstecz z każdą poprzednią obsługiwaną wersją zamrożoną. Gdy RELEASE_AIDL_USE_UNFROZEN to false, usługi zawsze wyglądają jak w ostatniej zamrożonej wersji lub wcześniejszej (na przykład wywołanie nowych niezamrożonych metod zwraca UNKNOWN_TRANSACTION, a nowe pola parcelable mają wartości domyślne). Klienci korzystający z ram Androida muszą być zgodni wstecznie z dodatkowymi poprzednimi wersjami, ale jest to nowy szczegół dla klientów dostawców i klientów interfejsów należących do partnerów.

Zmiany w implementacji HAL

Największą różnicą w programowaniu HAL w przypadku programowania opartego na flagach jest wymóg, aby implementacje HAL były zgodne wstecznie z ostatnią zamrożoną wersją, gdy RELEASE_AIDL_USE_UNFROZEN ma wartość false. Wsteczna zgodność w implementacjach i kodach urządzeń to nowe wyzwanie. Zobacz Używanie wersji interfejsów.

Zagadnienia związane z wsteczną zgodnością są w ogóle takie same w przypadku klientów i serwerów oraz kodu platformy i kodu dostawcy, ale istnieją subtelne różnice, o których musisz pamiętać, ponieważ obecnie wdrażasz 2 wersje, które korzystają z tego samego kodu źródłowego (obecna, niezamrożona wersja).

Przykład: interfejs ma 3 zablokowane wersje. Interfejs zostanie zaktualizowany o nową metodę. Klient i usługa są aktualizowane, aby używać nowej biblioteki w wersji 4. Biblioteka V4 jest oparta na niezamrożonej wersji interfejsu, dlatego zachowuje się jak ostatnia zamrożona wersja (V3), gdy RELEASE_AIDL_USE_UNFROZEN = false, i uniemożliwia korzystanie z nowej metody.

Gdy interfejs jest zamrożony, wszystkie wartości RELEASE_AIDL_USE_UNFROZEN używają tej zamrożonej wersji, a kod obsługujący zgodność wsteczną można usunąć.

Gdy wywołujesz metody z wywołaniami zwrotnymi, musisz płynnie obsługiwać przypadek, gdy zwracany jest parametr UNKNOWN_TRANSACTION. Klienty mogą implementować 2 różne wersje wywołania zwrotnego zależnie od konfiguracji wersji, więc nie możesz zakładać, że klient wysłał najnowszą wersję, a nowe metody mogą ją zwracać. Jest to podobne do sposobu, w jaki stabilne klienty AIDL zachowują zgodność wsteczną z serwerami. Opisano go w artykule Używanie interfejsów z wersjami.

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

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

Gdy parametr RELEASE_AIDL_USE_UNFROZEN ma wartość false, nowe pola w dotychczasowych typach (parcelable, enum, union) mogą nie istnieć lub zawierać wartości domyślne, a wartości nowych pól, które usługa próbuje wysłać, mogą zostać pominięte.

Nowych typów dodanych w tej nieodblokowanej wersji nie można wysyłać ani odbierać za pomocą interfejsu.

Gdy implementacja RELEASE_AIDL_USE_UNFROZEN ma wartość false, nie otrzymuje ona wywołania nowych metod od żadnego klienta.

Pamiętaj, aby używać nowych enumeratorów tylko w wersji, w której zostały wprowadzone, a nie w poprzedniej.

Aby sprawdzić, której wersji używa zdalny interfejs, użyj polecenia foo->getInterfaceVersion(). Jeśli jednak korzystasz z obsługi wersji na podstawie flagi, wdrażasz 2 różne wersje, więc warto pobrać wersję bieżącego interfejsu. Możesz to zrobić, uzyskując wersję interfejsu bieżącego obiektu, na przykład this->getInterfaceVersion() lub inną metodę my_ver. Więcej informacji znajdziesz w sekcji Wysyłanie zapytań do wersji obiektu zdalnego w interfejsie.

Nowe stabilne interfejsy VINTF

Po dodaniu nowego pakietu interfejsu AIDL nie ma ostatniej zablokowanej wersji, więc nie można wrócić do momentu, gdy RELEASE_AIDL_USE_UNFROZEN ma wartość false. Nie używaj tych interfejsów. Gdy RELEASE_AIDL_USE_UNFROZEN jest ustawiona na false, menedżer usługi nie zezwala usłudze na rejestrowanie interfejsu, a klienci nie mogą go znaleźć.

Usługi możesz dodawać warunkowo na podstawie wartości flagi RELEASE_AIDL_USE_UNFROZEN w makefile urządzenia:

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

Jeśli usługa jest częścią większego procesu, więc nie można jej bezwarunkowo dodać do urządzenia, możesz sprawdzić, czy jest ona zadeklarowana za pomocą IServiceManager::isDeclared(). Jeśli jest zadeklarowany, ale nie udało się go zarejestrować, przerwij proces. Jeśli nie jest zadeklarowana, prawdopodobnie nie uda się jej zarejestrować.

Cuttlefish jako narzędzie do tworzenia

Co roku po zamrożeniu specyfikacji VINTF dostosowujemy tablicę zgodności frameworka (FCM) target-levelPRODUCT_SHIPPING_API_LEVEL Cuttlefish, aby odzwierciedlały one urządzenia wprowadzane na rynek w ramach kolejnej wersji. Dostosowujemy target-levelPRODUCT_SHIPPING_API_LEVEL, aby mieć pewność, że w przyszłości będzie dostępne urządzenie z nowymi funkcjami, które zostało przetestowane i spełnia nowe wymagania.

Gdy RELEASE_AIDL_USE_UNFROZEN to true, Cuttlefish jest używany do tworzenia przyszłych wersji Androida. Jest ona kierowana na poziom FCM w przyszłej wersji Androida (PRODUCT_SHIPPING_API_LEVEL) i musi spełniać wymagania dotyczące oprogramowania dostawcy (VSR) w przyszłej wersji.

Gdy RELEASE_AIDL_USE_UNFROZEN to false, Cuttlefish ma poprzednią wersję target-levelPRODUCT_SHIPPING_API_LEVEL, aby odzwierciedlić urządzenie z wersją. W Androidzie 14 i starszych wersjach to rozróżnienie powstało przez różne gałęzie Git, które nie zauważyły zmiany w FCM target-level, na poziomie interfejsu API dostawy ani żadnego innego kodu kierowanego do następnej wersji.

Reguły nadawania nazw modułom

W Androidzie 11 dla każdej kombinacji włączonych wersji i włączonych backendów automatycznie tworzony jest moduł uniwersalnej biblioteki. Aby odwołać się do konkretnego modułu biblioteki podrzędnej na potrzeby łączenia, nie używaj nazwy modułu aidl_interface, ale nazwy modułu biblioteki podrzędnej, który jest ifacename-version-backend, gdzie

  • ifacename: nazwa modułu aidl_interface
  • version może być równe:
    • Vversion-number w przypadku wersji zamrożonych
    • Vlatest-frozen-version-number + 1 w przypadku wersji drzewa wierzchołkowego (jeszcze niezamrożonej)
  • backend może być równe:
    • java dla backendu Javy,
    • cpp dla backendu w C++,
    • ndk lub ndk_platform w przypadku backendu NDK. Pierwszy jest przeznaczony do aplikacji, a drugi do korzystania z platformy do Androida 13. W Androidzie 13 i nowszych używaj tylko ndk.
    • rust dla backendu Rust.

Załóżmy, że istnieje moduł o nazwie foo, którego najnowsza wersja to 2, a moduł obsługuje zarówno NDK, jak i C++. W tym przypadku AIDL generuje te moduły:

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

W porównaniu z Androidem 11:

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

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

  • Na podstawie wersji 1: foo-V1-(cpp|ndk|ndk_platform|rust).so
  • Na podstawie wersji 2: foo-V2-(cpp|ndk|ndk_platform|rust).so
  • Na podstawie wersji Warunków korzystania z usługi: foo-V3-(cpp|ndk|ndk_platform|rust).so

Pamiętaj, że kompilator AIDL nie tworzy ani modułu wersji unstable, ani modułu bez wersji dla stabilnego interfejsu AIDL. Od Androida 12 nazwa modułu wygenerowana na podstawie stabilnego interfejsu AIDL zawsze zawiera jego wersję.

Nowe metody interfejsu meta

Android 10 dodaje kilka metod metainterfejsu dla stabilnej wersji AIDL.

Przesyłanie zapytania do wersji interfejsu obiektu zdalnego

Klienci mogą wysyłać zapytania o wersję i sumę kontrolną interfejsu implementowanego przez obiekt zdalny oraz porównywać zwrócone wartości z wartościami interfejsu, którego używa klient.

Przykład z backendem cpp:

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

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

Przykład z backendem ndk (i ndk_platform):

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

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

Przykład z backendem java:

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

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

W przypadku języka Java strona zdalna MUSI implementować getInterfaceVersion()getInterfaceHash() w ten sposób (zamiast IFoo używana jest wartość super, aby uniknąć błędów podczas kopiowania i wklejania). W zależności od konfiguracji javac może być potrzebna adnotacja @SuppressWarnings("static"), aby wyłączyć ostrzeżenia:

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

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

Dzieje się tak, ponieważ wygenerowane klasy (IFoo, IFoo.Stub itp.) są współdzielone przez klienta i serwer (np. klasy mogą znajdować się w ścieżce klasy rozruchowej). Gdy zajęcia są udostępniane, serwer jest również połączony z najnowszą wersją zajęć, nawet jeśli została ona utworzona za pomocą starszej wersji interfejsu. Jeśli ten interfejs meta jest zaimplementowany w wspólnej klasie, zawsze zwraca najnowszą wersję. Jednak dzięki zaimplementowaniu metody w taki sposób numer wersji interfejsu jest zakodowany w kodzie serwera (bo IFoo.VERSION to static final int, który jest wstawiany w miejscu odwołania), a więc metoda może zwrócić dokładną wersję, z której został skompilowany serwer.

Praca ze starszymi interfejsami

Możliwe, że klient jest zaktualizowany do nowszej wersji interfejsu AIDL, ale serwer używa starszego interfejsu AIDL. W takich przypadkach wywołanie metody w starym interfejsie zwraca wartość UNKNOWN_TRANSACTION.

Stabilna wersja AIDL daje klientom większą kontrolę. Po stronie klienta możesz ustawić domyślną implementację interfejsu AIDL. Metoda w domyślnej implementacji jest wywoływana tylko wtedy, gdy nie jest ona zaimplementowana po stronie zdalnej (ponieważ została utworzona przy użyciu starszej wersji interfejsu). Domyślne wartości są ustawiane globalnie, dlatego nie należy ich używać w kontekstach, które mogą być udostępniane.

Przykład w C++ na Androidzie 13 i nowszych:

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

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

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

Przykład w Javie:

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

foo.anAddedMethod(...);

Nie musisz udostępniać domyślnej implementacji wszystkich metod w interfejsie AIDL. Metody, które są z pewnością zaimplementowane po stronie zdalnej (ponieważ masz pewność, że zdalne urządzenie zostało utworzone, gdy metody były w opisie interfejsu AIDL), nie muszą być zastąpione w domyślnej klasie impl.

Konwertowanie istniejących AIDL na uporządkowane lub stabilne AIDL

Jeśli masz już interfejs AIDL i kod, który go używa, wykonaj te czynności, aby przekonwertować interfejs na stabilny interfejs AIDL.

  1. Zidentyfikuj wszystkie zależności interfejsu. W przypadku każdego pakietu, od którego zależy interfejs, sprawdź, czy pakiet jest zdefiniowany w stabilnej wersji AIDL. Jeśli nie jest zdefiniowany, pakiet musi zostać przekonwertowany.

  2. Przekształć wszystkie obiekty Parcelable w interfejsie na stabilne obiekty Parcelable (same pliki interfejsu mogą pozostać bez zmian). Aby to zrobić, należy określić ich strukturę bezpośrednio w plikach AIDL. Aby z nich korzystać, trzeba zmodyfikować klasy zarządzania. Możesz to zrobić przed utworzeniem pakietu aidl_interface (poniżej).

  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.