Stabilna wersja AIDL

Android 10 obsługuje stabilną wersję języka definicji interfejsu Androida (AIDL), który jest nowym sposobem ś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ą definiowane 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.

Porównywanie uporządkowanych i stabilnych plików 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. Obiekty Parcelable z polami zdefiniowanymi w pliku AIDL są nazywane strukturyzowanymi obiektami Parcelable.

Stabilna wersja AIDL wymaga uporządkowanego AIDL, aby system kompilacji i kompilator mogli zrozumieć, czy zmiany wprowadzone w pakietach 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, z których składa się 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 początku nazwy 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ć nazwa sama w sobie, aby wskazać najnowszą wersję, lub nazwa z sufiksem (np. -V1), aby wskazać konkretną wersję. 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 zamrożonych wersji interfejsu, nie należy tego określać, a sprawdzanie zgodności nie będzie przeprowadzane. To pole zostało zastąpione przez versions_with_info w Androidzie 13 i nowszych.
  • 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 AIDL IFACE znajduje się na stronie aidl_api/IFACE/V. To pole zostało wprowadzone w Androidzie 13 i nie powinno być modyfikowane bezpośrednio w Android.bp. Pole jest dodawane lub aktualizowane przez wywołanie *-update-api lub *-freeze-api. Ponadto 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 parametr stability nie jest ustawiony, system kompilacji sprawdza, czy interfejs jest zgodny wstecznie, chyba że określono parametr unstable. Stan nieustawiony odpowiada interfejsowi o 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 po ustawieniu na wartość true udostępnia wygenerowane biblioteki środowisku hosta.
  • unstable: opcjonalna flaga używana do oznaczania, że ten interfejs nie musi być 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 ta jest wyłączona do Androida 15.
  • backend.<type>.apex_available: lista nazw APEX, dla których wygenerowana biblioteka podkładów jest dostępna.
  • backend.[cpp|java].gen_log: opcjonalna flaga, która określa, czy należy wygenerować 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 znaczników 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 Java. 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 Reguły 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óżnice między uporządkowanymi a stabilnymi interfejsami AIDL). Główna różnica w wersji stabilnej AIDL dotyczy sposobu definiowania obiektów parcelable. Wcześniej obiekty parcelable były deklarowane w przód. W stabilnych (a zatem ustrukturyzowanych) plikach 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 parametrów 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 nie podasz wartości domyślnej, zostanie użyta wartość 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 zastępczych

Po dodaniu bibliotek zastępczych jako zależności do modułu możesz je uwzględnić w plikach. Oto przykłady bibliotek stubów w systemie kompilacji (element Android.mk można też używać w przypadku starszych definicji modułów). Pamiętaj, że w tych przykładach nie ma wersji, więc jest to przykład użycia niestabilnego interfejsu, ale nazwy interfejsów z wersjami zawierają dodatkowe informacje. Więcej informacji znajdziesz w artykule Interfejsy z wersjami.

cc_... {
    name: ...,
    // use `shared_libs:` to load your library and its transitive dependencies
    // dynamically
    shared_libs: ["my-module-name-cpp"],
    // use `static_libs:` to include the library in this binary and drop
    // transitive dependencies
    static_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // use `static_libs:` to add all jars and classes to this jar
    static_libs: ["my-module-name-java"],
    // use `libs:` to make these classes available during build time, but
    // not add them to the jar, in case the classes are already present on the
    // boot classpath (such as if it's in framework.jar) or another jar.
    libs: ["my-module-name-java"],
    // use `srcs:` with `-java-sources` if you want to add classes in this
    // library jar directly, but you get transitive dependencies from
    // somewhere else, such as the boot classpath or another jar.
    srcs: ["my-module-name-java-source", ...],
    ...
}
# 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

Obsługa wersji interfejsów

Oprócz zadeklarowania modułu o nazwie foo system kompilacji tworzy też element docelowy, za pomocą którego można zarządzać interfejsem API modułu. 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 głównym (ToT) a najnowszą zamrożoną wersją.

Dodatkowo musisz 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 liczniki
  • W Androidzie 12 pola na końcu unii

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ść importów

Aktualizowanie wersji importów w zamrożonych wersjach interfejsu jest zgodne wstecz 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. W przypadku pakietów tylko z typami lub typowymi pakietami jest to bezpieczne, ponieważ kod musi już obsługiwać nieznane typy z transakcji IPC.

W kodzie platformy Androida android.hardware.graphics.common jest najlepszym przykładem tego typu uaktualnienia wersji.

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.
  • java backend dostaje android.os.RemoteException z wiadomością, że interfejs API nie jest zaimplementowany.

Strategie radzenia sobie z tym problemem znajdziesz w artykule wysyłanie zapytań do wersjiużywanie wartości domyślnych.

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 wartości domyślne muszą być określone dla wszystkich nowych pól w klasie Parcelable.

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

Wartości w polu enum i stałe

Podobnie klienci i serwery powinni odrzucać lub ignorować nierozpoznane wartości stałych i enumeratory, ponieważ w przyszłości może zostać dodanych więcej takich wartości. 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

Wysyłanie złączenia z nowym polem kończy się niepowodzeniem, jeśli odbiorca jest stary i nie zna tego pola. 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 jest zwracany, jeśli klient wysyła do nowego pola zbiór zjednoczony na stary serwer lub jeśli stary klient odbiera zbiór zjednoczony 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żywać do przechowywania jednej definicji najnowszych wersji zależności dla aidl_interface, które można aktualizować w jednym miejscu i które mogą być używane 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 bibliotek interfejsu w czasie wykonywania, aby kod napisany dla najnowszej wersji biblioteki niezamrożonej nadal mógł być 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 wersjami.

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 wykonywania jest używana odmrożona wersja interfejsu, a false oznacza, że biblioteki odmrożonych wersji zachowują się jak ich ostatnia zamrożona wersja. Możesz zastąpić flagę wartością true na potrzeby lokalnego tworzenia, ale przed wydaniem musisz ją przywrócić 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ą wymagane 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.

Podczas dodawania odblokowanej wersji 4 użyj na przykład <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 fragmenty manifestu określają, która wersja interfejsu jest implementowana przez usługę. Jeśli używasz najnowszej wersji interfejsu, musisz zaktualizować plik manifestu, aby uwzględniał tę nową wersję. GdyRELEASE_AIDL_USE_UNFROZEN=false, wpisy w pliku manifestu są dostosowywane przezlibvintf, 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 pliku manifestu dla każdej ze swoich usług.

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ększa różnica w rozwijaniu HAL w porównaniu z rozwijaniem opartym na flagach to wymóg, aby implementacje HAL były zgodne wstecz z ostatnią zamrożoną wersją, aby działały, gdy RELEASE_AIDL_USE_UNFROZEN jest 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 frameworku 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 zamrożone wersje. Interfejs został 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ąć.

Podczas wywoływania metod w wywołaniach zwrotnych musisz odpowiednio obsłużyć przypadek, gdy zwracana jest wartość UNKNOWN_TRANSACTION. Klienci mogą stosować 2 różne wersje wywołania zwrotnego na podstawie konfiguracji wersji, więc nie możesz zakładać, że klient wysyła najnowszą wersję, a nowe metody mogą zwracać tę wersję. 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();
  }
}

Nowe pola w dotychczasowych typach (parcelable, enum, union) mogą nie istnieć lub zawierać domyślne wartości, gdy RELEASE_AIDL_USE_UNFROZEN to false, a wartości nowych pól, które usługa próbuje wysłać, są pomijane na etapie przetwarzania.

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

Gdy RELEASE_AIDL_USE_UNFROZEN to false, implementacja nigdy nie wywołuje nowych metod przez żadnych klientów.

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

Gdy dodawany jest nowy pakiet interfejsu AIDL, nie ma ostatniej zamrożonej wersji, więc nie ma żadnego zachowania, do którego można przejść, gdy RELEASE_AIDL_USE_UNFROZEN jest 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, przez co nie można jej dodać do urządzenia bezwarunkowo, 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 zadeklarowany, nie powinien zostać zarejestrowany.

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 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, 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 publikujące. W Androidzie 14 i starszych ta różnica byłaby osiągana za pomocą różnych gałęzi Git, które nie uwzględniają zmiany w FCMtarget-level, poziomu API w wersji produkcyjnej ani żadnego innego kodu przeznaczonego do następnej wersji.

Reguły nadawania nazw modułom

W Androidzie 11 dla każdej kombinacji wersji i włączonych backendów automatycznie tworzony jest moduł biblioteki zastępczej. 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 Java,
    • 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 ToT: 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 itd.) są współdzielone przez klienta i serwer (np. klasy mogą znajdować się w ścieżce klas uruchamiania). Gdy zajęcia są udostępniane, serwer jest również powiązany 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 dlatego 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.

Dzięki stabilnemu interfejsowi 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 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 z pewnością są implementowane po stronie zdalnej (ponieważ masz pewność, że zdalne urządzenie jest skompilowane, gdy metody były w opisie interfejsu AIDL), nie muszą być zastępowane 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. Klasy zarządzania muszą zostać przekształcone, aby używać tych nowych typów. Możesz 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 wszystkie inne potrzebne informacje. Aby dane były stabilne (a nie tylko ustrukturyzowane), muszą być też powiązane z wersjami. Więcej informacji znajdziesz w artykule Zarządzanie wersjami interfejsów.