Stabile AIDL

Android 10 bietet Unterstützung für die stabile Android Interface Definition Language (AIDL), eine neue Möglichkeit, die von AIDL-Schnittstellen bereitgestellte API (Application Programming Interface) und ABI (Application Binary Interface) im Blick zu behalten. Stabiles AIDL funktioniert genau wie AIDL, aber das Build-System verfolgt die Schnittstellenkompatibilität und es gibt Einschränkungen für das, was Sie tun können:

  • Schnittstellen werden im Build-System mit aidl_interfaces definiert.
  • Schnittstellen können nur strukturierte Daten enthalten. Parcelables, die die bevorzugten Typen darstellen, werden automatisch basierend auf ihrer AIDL-Definition erstellt und automatisch serialisiert und deserialisiert.
  • Schnittstellen können als stabil (abwärtskompatibel) deklariert werden. In diesem Fall wird die API in einer Datei neben der AIDL-Schnittstelle verfolgt und versioniert.

Strukturierte AIDL im Vergleich zu stabiler AIDL

Strukturiertes AIDL bezieht sich auf Typen, die ausschließlich in AIDL definiert sind. Beispielsweise ist eine Parcelable-Deklaration (ein benutzerdefiniertes Parcelable) kein strukturiertes AIDL. Parcelables, deren Felder in AIDL definiert sind, werden als strukturierte Parcelables bezeichnet.

Stabiles AIDL erfordert strukturiertes AIDL, damit das Build-System und der Compiler erkennen können, ob Änderungen an Parcelables abwärtskompatibel sind. Nicht alle strukturierten Schnittstellen sind jedoch stabil. Damit eine Schnittstelle stabil ist, darf sie nur strukturierte Typen verwenden und muss die folgenden Versionsverwaltungsfunktionen nutzen. Umgekehrt ist eine Schnittstelle nicht stabil, wenn das Core-Build-System zum Erstellen verwendet wird oder wenn unstable:true festgelegt ist.

AIDL-Schnittstelle definieren

Eine Definition von aidl_interface sieht so aus:

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: Der Name des AIDL-Schnittstellenmoduls, das eine AIDL-Schnittstelle eindeutig identifiziert.
  • srcs: Die Liste der AIDL-Quelldateien, aus denen die Schnittstelle besteht. Der Pfad für einen AIDL-Typ Foo, der in einem Paket com.acme definiert ist, sollte <base_path>/com/acme/Foo.aidl sein, wobei <base_path> ein beliebiges Verzeichnis sein kann, das sich auf das Verzeichnis bezieht, in dem sich Android.bp befindet. Im vorherigen Beispiel ist <base_path> srcs/aidl.
  • local_include_dir: Der Pfad, ab dem der Paketname beginnt. Sie entspricht dem oben beschriebenen <base_path>.
  • imports: Eine Liste der aidl_interface-Module, die verwendet werden. Wenn eine Ihrer AIDL-Schnittstellen eine Schnittstelle oder ein Parcelable aus einem anderen aidl_interface verwendet, geben Sie den Namen hier ein. Das kann der Name allein sein, um auf die neueste Version zu verweisen, oder der Name mit dem Versionssuffix (z. B. -V1), um auf eine bestimmte Version zu verweisen. Die Angabe einer Version wird seit Android 12 unterstützt.
  • versions: Die vorherigen Versionen der Schnittstelle, die unter api_dir eingefroren sind. Ab Android 11 werden die versions unter aidl_api/name eingefroren. Wenn es keine eingefrorenen Versionen einer Schnittstelle gibt, sollte dies nicht angegeben werden und es werden keine Kompatibilitätsprüfungen durchgeführt. Dieses Feld wurde für Android 13 und höher durch versions_with_info ersetzt.
  • versions_with_info: Liste von Tupeln, die jeweils den Namen einer eingefrorenen Version und eine Liste mit Versionsimporten anderer aidl_interface-Module enthalten, die von dieser Version der aidl_interface importiert wurden. Die Definition der Version V einer AIDL-Schnittstelle IFACE befindet sich unter aidl_api/IFACE/V. Dieses Feld wurde in Android 13 eingeführt und sollte nicht direkt in Android.bp geändert werden. Das Feld wird durch Aufrufen von *-update-api oder *-freeze-api hinzugefügt oder aktualisiert. Außerdem werden versions-Felder automatisch zu versions_with_info migriert, wenn ein Nutzer *-update-api oder *-freeze-api aufruft.
  • stability: Das optionale Flag für das Stabilitätsversprechen dieser Schnittstelle. Nur "vintf" wird unterstützt. Wenn stability nicht festgelegt ist, prüft das Build-System, ob die Schnittstelle abwärtskompatibel ist, sofern nicht unstable angegeben ist. Wenn der Wert nicht festgelegt ist, entspricht das einer Schnittstelle mit Stabilität in diesem Kompilierungskontext (entweder alle Systemelemente, z. B. Elemente in system.img und zugehörigen Partitionen, oder alle Anbieterelemente, z. B. Elemente in vendor.img und zugehörigen Partitionen). Wenn stability auf "vintf" festgelegt ist, entspricht dies einem Stabilitätsversprechen: Die Schnittstelle muss stabil bleiben, solange sie verwendet wird.
  • gen_trace: Das optionale Flag zum Aktivieren oder Deaktivieren der Ablaufverfolgung. Ab Android 14 ist der Standardwert für die Back-Ends cpp und java true.
  • host_supported: Das optionale Flag, das, wenn es auf true gesetzt ist, die generierten Bibliotheken für die Hostumgebung verfügbar macht.
  • unstable: Das optionale Flag, mit dem angegeben wird, dass diese Schnittstelle nicht stabil sein muss. Wenn dieser Wert auf true festgelegt ist, erstellt das Build-System weder den API-Dump für die Schnittstelle noch muss er aktualisiert werden.
  • frozen: Das optionale Flag, das bei Festlegung auf true angibt, dass die Schnittstelle seit der vorherigen Version der Schnittstelle keine Änderungen erfahren hat. Dadurch sind mehr Prüfungen zur Build-Zeit möglich. Wenn der Wert auf false festgelegt ist, bedeutet das, dass sich die Schnittstelle in der Entwicklung befindet und neue Änderungen vorgenommen wurden. Wenn Sie foo-freeze-api ausführen, wird eine neue Version generiert und der Wert automatisch in true geändert. Eingeführt in Android 14.
  • backend.<type>.enabled: Mit diesen Flags wird jedes der Back-Ends aktiviert oder deaktiviert, für die der AIDL-Compiler Code generiert. Es werden vier Back-Ends unterstützt: Java, C++, NDK und Rust. Java-, C++- und NDK-Back-Ends sind standardmäßig aktiviert. Wenn eines dieser drei Back-Ends nicht benötigt wird, muss es explizit deaktiviert werden. Rust ist bis Android 15 standardmäßig deaktiviert.
  • backend.<type>.apex_available: Die Liste der APEX-Namen, für die die generierte Stub-Bibliothek verfügbar ist.
  • backend.[cpp|java].gen_log: Das optionale Flag, mit dem gesteuert wird, ob zusätzlicher Code zum Erfassen von Informationen zur Transaktion generiert werden soll.
  • backend.[cpp|java].vndk.enabled: Das optionale Flag, um diese Schnittstelle zu einem Teil des VNDK zu machen. Der Standardwert ist false.
  • backend.[cpp|ndk].additional_shared_libraries: Dieses Flag wurde in Android 14 eingeführt und fügt den nativen Bibliotheken Abhängigkeiten hinzu. Dieses Flag ist nützlich in Verbindung mit ndk_header und cpp_header.
  • backend.java.sdk_version: Das optionale Flag zum Angeben der Version des SDK, für das die Java-Stub-Bibliothek erstellt wird. Der Standardwert ist "system_current". Dieser sollte nicht festgelegt werden, wenn backend.java.platform_apis gleich true ist.
  • backend.java.platform_apis: Das optionale Flag, das auf true gesetzt werden sollte, wenn die generierten Bibliotheken für die Plattform-API und nicht für das SDK erstellt werden müssen.

Für jede Kombination aus den Versionen und den aktivierten Backends wird eine Stub-Bibliothek erstellt. Informationen dazu, wie Sie auf die spezifische Version der Stub-Bibliothek für ein bestimmtes Backend verweisen, finden Sie unter Regeln für die Modulbenennung.

AIDL-Dateien schreiben

Schnittstellen in stabilem AIDL ähneln herkömmlichen Schnittstellen, mit der Ausnahme, dass sie keine unstrukturierten Parcelables verwenden dürfen, da diese nicht stabil sind (siehe Strukturiertes und stabiles AIDL). Der Hauptunterschied bei stabilem AIDL besteht darin, wie Parcelables definiert werden. Bisher wurden Parcelables vorab deklariert. In stabilem (und damit strukturiertem) AIDL werden Parcelable-Felder und -Variablen explizit definiert.

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

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

Für boolean, char, float, double, byte, int, long und String wird eine Standardeinstellung unterstützt (aber nicht erforderlich). In Android 12 werden auch Standardwerte für benutzerdefinierte Aufzählungen unterstützt. Wenn kein Standardwert angegeben ist, wird ein Wert verwendet, der 0 oder leer ist. Aufzählungen ohne Standardwert werden mit 0 initialisiert, auch wenn es keinen Enumerator mit dem Wert 0 gibt.

Stub-Bibliotheken verwenden

Nachdem Sie Stub-Bibliotheken als Abhängigkeit zu Ihrem Modul hinzugefügt haben, können Sie sie in Ihre Dateien einfügen. Hier sind Beispiele für Stub-Bibliotheken im Build-System (Android.mk kann auch für Legacy-Moduldefinitionen verwendet werden). In diesen Beispielen ist die Version nicht vorhanden. Das bedeutet, dass eine instabile Schnittstelle verwendet wird. Namen für Schnittstellen mit Versionen enthalten zusätzliche Informationen. Weitere Informationen finden Sie unter Schnittstellenversionierung.

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"],
    ...
}

Beispiel in C++:

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

Beispiel in Java:

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

Beispiel in Rust:

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

Versionsverwaltung von Schnittstellen

Durch das Deklarieren eines Moduls mit dem Namen foo wird auch ein Ziel im Build-System erstellt, mit dem Sie die API des Moduls verwalten können. Beim Erstellen von foo-freeze-api wird je nach Android-Version eine neue API-Definition unter api_dir oder aidl_api/name hinzugefügt. Außerdem wird eine .hash-Datei hinzugefügt, die beide die neu eingefrorene Version der Schnittstelle darstellen. foo-freeze-api aktualisiert auch die Eigenschaft versions_with_info, um die zusätzliche Version und imports für die Version widerzuspiegeln. Im Grunde wird imports in versions_with_info aus dem Feld imports kopiert. Die neueste stabile Version wird jedoch in imports in versions_with_info für den Import angegeben, der keine explizite Version hat. Nachdem das Attribut versions_with_info angegeben wurde, führt das Build-System Kompatibilitätsprüfungen zwischen eingefrorenen Versionen sowie zwischen „Top of Tree“ (ToT) und der neuesten eingefrorenen Version durch.

Außerdem müssen Sie die API-Definition der ToT-Version verwalten. Wenn eine API aktualisiert wird, führen Sie foo-update-api aus, um aidl_api/name/current zu aktualisieren, die die API-Definition der ToT-Version enthält.

Um die Stabilität einer Schnittstelle zu gewährleisten, können Inhaber Folgendes hinzufügen:

  • Methoden am Ende einer Schnittstelle (oder Methoden mit explizit definierten neuen Seriennummern)
  • Elemente am Ende eines Parcelable-Objekts (für jedes Element muss ein Standardwert hinzugefügt werden)
  • Konstante Werte
  • In Android 11
  • In Android 12: Felder bis zum Ende einer Union

Andere Aktionen sind nicht zulässig und niemand sonst kann eine Schnittstelle ändern, da sonst das Risiko besteht, dass es zu Konflikten mit Änderungen kommt, die ein Eigentümer vornimmt.

Um zu testen, ob alle Schnittstellen für die Veröffentlichung eingefroren sind, können Sie den Build mit den folgenden Umgebungsvariablen erstellen:

  • AIDL_FROZEN_REL=true m ...: Für den Build müssen alle stabilen AIDL-Schnittstellen, für die kein owner:-Feld angegeben ist, eingefroren werden.
  • AIDL_FROZEN_OWNERS="aosp test": Für den Build müssen alle stabilen AIDL-Schnittstellen eingefroren werden. Das Feld owner: muss als „aosp“ oder „test“ angegeben werden.

Stabilität von Importen

Das Aktualisieren der Versionen von Importen für eingefrorene Versionen einer Schnittstelle ist auf der Stable-AIDL-Ebene abwärtskompatibel. Dazu müssen jedoch alle Server und Clients aktualisiert werden, die eine frühere Version der Schnittstelle verwenden. Außerdem kann es bei einigen Apps zu Problemen kommen, wenn verschiedene Versionen von Typen verwendet werden. Im Allgemeinen ist das bei reinen Typ- oder allgemeinen Paketen sicher, da bereits Code geschrieben werden muss, um unbekannte Typen aus IPC-Transaktionen zu verarbeiten.

Im Android-Plattformcode ist android.hardware.graphics.common das größte Beispiel für diese Art von Versionsupgrade.

Versionierte Schnittstellen verwenden

Schnittstellenmethoden

Wenn zur Laufzeit versucht wird, neue Methoden auf einem alten Server aufzurufen, erhalten neue Clients je nach Backend entweder einen Fehler oder eine Ausnahme.

  • Das cpp-Backend erhält ::android::UNKNOWN_TRANSACTION.
  • Das ndk-Backend erhält STATUS_UNKNOWN_TRANSACTION.
  • Das java-Backend erhält android.os.RemoteException mit einer Meldung, dass die API nicht implementiert ist.

Strategien zur Behandlung dieses Problems finden Sie unter Versionen abfragen und Standardwerte verwenden.

Parcelables

Wenn Parcelables neue Felder hinzugefügt werden, werden sie von alten Clients und Servern verworfen. Wenn neue Clients und Server alte Parcelables empfangen, werden die Standardwerte für neue Felder automatisch eingefügt. Das bedeutet, dass für alle neuen Felder in einem Parcelable Standardwerte angegeben werden müssen.

Clients sollten nicht davon ausgehen, dass Server die neuen Felder verwenden, es sei denn, sie wissen, dass der Server die Version implementiert, in der das Feld definiert ist (siehe Versionen abfragen).

Enums und Konstanten

Ebenso sollten Clients und Server nicht erkannte Konstantenwerte und Enumeratoren entsprechend ablehnen oder ignorieren, da in Zukunft weitere hinzugefügt werden können. Ein Server sollte beispielsweise nicht abgebrochen werden, wenn er einen Enumerator empfängt, den er nicht kennt. Der Server sollte den Enumerator entweder ignorieren oder etwas zurückgeben, damit der Client weiß, dass er in dieser Implementierung nicht unterstützt wird.

Gewerkschaften

Der Versuch, eine Union mit einem neuen Feld zu senden, schlägt fehl, wenn der Empfänger alt ist und das Feld nicht kennt. Die Implementierung wird die Union mit dem neuen Feld nie sehen. Der Fehler wird ignoriert, wenn es sich um eine Einwegtransaktion handelt. Andernfalls ist der Fehler BAD_VALUE(für das C++- oder NDK-Backend) oder IllegalArgumentException(für das Java-Backend). Der Fehler wird empfangen, wenn der Client ein Union-Set für das neue Feld an einen alten Server sendet oder wenn ein alter Client das Union-Set von einem neuen Server empfängt.

Mehrere Versionen verwalten

Ein Linker-Namespace in Android kann nur eine Version einer bestimmten aidl-Schnittstelle haben, um Situationen zu vermeiden, in denen die generierten aidl-Typen mehrere Definitionen haben. In C++ gilt die One Definition Rule (Regel für eine Definition), die nur eine Definition für jedes Symbol erfordert.

Beim Android-Build wird ein Fehler ausgegeben, wenn ein Modul von verschiedenen Versionen derselben aidl_interface-Bibliothek abhängt. Das Modul ist möglicherweise direkt oder indirekt über Abhängigkeiten seiner Abhängigkeiten von diesen Bibliotheken abhängig. Diese Fehler zeigen den Abhängigkeitsgraphen vom fehlerhaften Modul zu den in Konflikt stehenden Versionen der aidl_interface-Bibliothek. Alle Abhängigkeiten müssen aktualisiert werden, damit sie dieselbe (in der Regel die neueste) Version dieser Bibliotheken enthalten.

Wenn die Schnittstellenbibliothek von vielen verschiedenen Modulen verwendet wird, kann es hilfreich sein, cc_defaults, java_defaults und rust_defaults für jede Gruppe von Bibliotheken und Prozessen zu erstellen, die dieselbe Version verwenden müssen. Wenn eine neue Version der Schnittstelle eingeführt wird, können diese Standardwerte aktualisiert werden. Alle Module, die sie verwenden, werden dann gemeinsam aktualisiert, sodass sie nicht unterschiedliche Versionen der Schnittstelle verwenden.

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"],
  ...
}

Wenn aidl_interface-Module andere aidl_interface-Module importieren, entstehen zusätzliche Abhängigkeiten, für die bestimmte Versionen zusammen verwendet werden müssen. Diese Situation kann schwierig zu handhaben sein, wenn es gemeinsame aidl_interface-Module gibt, die in mehrere aidl_interface-Module importiert werden, die zusammen in denselben Prozessen verwendet werden.

Mit aidl_interfaces_defaults kann eine Definition der neuesten Versionen von Abhängigkeiten für ein aidl_interface beibehalten werden, die an einem einzigen Ort aktualisiert und von allen aidl_interface-Modulen verwendet werden kann, die diese gemeinsame Schnittstelle importieren möchten.

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"],
  ...
}

Flag-basierte Entwicklung

Schnittstellen, die sich in der Entwicklung befinden (nicht eingefroren), können nicht auf Release-Geräten verwendet werden, da keine Abwärtskompatibilität garantiert wird.

AIDL unterstützt Laufzeit-Fallback für diese nicht eingefrorenen Schnittstellenbibliotheken, damit Code für die neueste nicht eingefrorene Version geschrieben und trotzdem auf Release-Geräten verwendet werden kann. Das abwärtskompatible Verhalten von Clients ähnelt dem vorhandenen Verhalten. Durch den Fallback müssen die Implementierungen auch diesem Verhalten entsprechen. Weitere Informationen finden Sie unter Versionierte Schnittstellen verwenden.

AIDL-Build-Flag

Das Flag, mit dem dieses Verhalten gesteuert wird, ist RELEASE_AIDL_USE_UNFROZEN und wird in build/release/build_flags.bzl definiert. true bedeutet, dass die nicht eingefrorene Version der Schnittstelle zur Laufzeit verwendet wird, und false bedeutet, dass sich die Bibliotheken der nicht eingefrorenen Versionen alle wie ihre letzte eingefrorene Version verhalten. Sie können das Flag für die lokale Entwicklung auf true setzen, müssen es aber vor der Veröffentlichung auf false zurücksetzen. In der Regel erfolgt die Entwicklung mit einer Konfiguration, bei der das Flag auf true gesetzt ist.

Kompatibilitätsmatrix und Manifeste

Anbieterschnittstellenobjekte (VINTF-Objekte) definieren, welche Versionen auf beiden Seiten der Anbieterschnittstelle erwartet und bereitgestellt werden.

Die meisten Geräte, die nicht auf Cuttlefish basieren, sind erst nach dem Einfrieren der Schnittstellen auf die neueste Kompatibilitätsmatrix ausgerichtet. Daher gibt es keinen Unterschied bei den AIDL-Bibliotheken basierend auf RELEASE_AIDL_USE_UNFROZEN.

Matrizen

Partnereigene Schnittstellen werden geräte- oder produktspezifischen Kompatibilitätsmatrizen hinzugefügt, auf die das Gerät während der Entwicklung ausgerichtet ist. Wenn also eine neue, nicht eingefrorene Version einer Schnittstelle einer Kompatibilitätsmatrix hinzugefügt wird, müssen die vorherigen eingefrorenen Versionen für RELEASE_AIDL_USE_UNFROZEN=false beibehalten werden. Sie können dieses Problem beheben, indem Sie für verschiedene RELEASE_AIDL_USE_UNFROZEN-Konfigurationen unterschiedliche Kompatibilitätsmatrixdateien verwenden oder beide Versionen in einer einzigen Kompatibilitätsmatrixdatei zulassen, die in allen Konfigurationen verwendet wird.

Wenn Sie beispielsweise eine nicht eingefrorene Version 4 hinzufügen, verwenden Sie <version>3-4</version>.

Wenn Version 4 eingefroren ist, können Sie Version 3 aus der Kompatibilitätsmatrix entfernen, da die eingefrorene Version 4 verwendet wird, wenn RELEASE_AIDL_USE_UNFROZEN gleich false ist.

Manifeste

In Android 15 wird eine Änderung an libvintf eingeführt, um die Manifestdateien zur Build-Zeit basierend auf dem Wert von RELEASE_AIDL_USE_UNFROZEN zu ändern.

In den Manifesten und Manifestfragmenten wird deklariert, welche Version einer Schnittstelle ein Dienst implementiert. Wenn Sie die aktuelle nicht eingefrorene Version einer Schnittstelle verwenden, muss das Manifest entsprechend aktualisiert werden. Wenn RELEASE_AIDL_USE_UNFROZEN=false werden die Manifesteinträge von libvintf angepasst, um die Änderung in der generierten AIDL-Bibliothek zu berücksichtigen. Die Version wird von der nicht mehr unterstützten Version N zur letzten nicht mehr unterstützten Version N - 1 geändert. Nutzer müssen daher nicht mehrere Manifeste oder Manifestfragmente für jeden ihrer Dienste verwalten.

HAL-Client-Änderungen

Der HAL-Clientcode muss abwärtskompatibel mit jeder vorherigen unterstützten eingefrorenen Version sein. Wenn RELEASE_AIDL_USE_UNFROZEN gleich false ist, sehen die Dienste immer wie die letzte eingefrorene Version oder eine frühere Version aus. Wenn Sie beispielsweise neue, nicht eingefrorene Methoden aufrufen, wird UNKNOWN_TRANSACTION zurückgegeben. Neue parcelable-Felder haben ihre Standardwerte. Android-Framework-Clients müssen abwärtskompatibel mit zusätzlichen früheren Versionen sein. Dies ist jedoch eine neue Anforderung für Anbieterclients und Clients von Schnittstellen, die Partnern gehören.

Änderungen bei der HAL-Implementierung

Der größte Unterschied bei der HAL-Entwicklung mit flagbasierten Entwicklung ist die Anforderung, dass HAL-Implementierungen mit der letzten eingefrorenen Version abwärtskompatibel sein müssen, damit sie funktionieren, wenn RELEASE_AIDL_USE_UNFROZEN = false. Die Berücksichtigung der Abwärtskompatibilität in Implementierungen und Gerätecode ist eine neue Aufgabe. Weitere Informationen finden Sie unter Versionierte Schnittstellen verwenden.

Die Überlegungen zur Abwärtskompatibilität sind im Allgemeinen für Clients und Server sowie für Framework-Code und Anbietercode gleich. Es gibt jedoch subtile Unterschiede, die Sie beachten müssen, da Sie jetzt effektiv zwei Versionen implementieren, die denselben Quellcode verwenden (die aktuelle, nicht eingefrorene Version).

Beispiel: Eine Schnittstelle hat drei eingefrorene Versionen. Die Schnittstelle wird mit einer neuen Methode aktualisiert. Der Client und der Dienst werden beide aktualisiert, um die neue Version 4 der Bibliothek zu verwenden. Da die V4-Bibliothek auf einer nicht eingefrorenen Version der Schnittstelle basiert, verhält sie sich wie die letzte eingefrorene Version 3, wenn RELEASE_AIDL_USE_UNFROZEN gleich false ist. Dadurch wird die Verwendung der neuen Methode verhindert.

Wenn die Schnittstelle eingefroren ist, verwenden alle Werte von RELEASE_AIDL_USE_UNFROZEN diese eingefrorene Version und der Code für die Abwärtskompatibilität kann entfernt werden.

Wenn Sie Methoden für Callbacks aufrufen, müssen Sie den Fall, in dem UNKNOWN_TRANSACTION zurückgegeben wird, ordnungsgemäß verarbeiten. Clients implementieren möglicherweise zwei verschiedene Versionen eines Callbacks basierend auf der Releasekonfiguration. Sie können also nicht davon ausgehen, dass der Client die neueste Version sendet, und neue Methoden geben möglicherweise diesen Wert zurück. Dies ähnelt der Art und Weise, wie stabile AIDL-Clients die Abwärtskompatibilität mit Servern aufrechterhalten, wie unter Versionierte Schnittstellen verwenden beschrieben.

// 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();
  }
}

Neue Felder in vorhandenen Typen (parcelable, enum, union) sind möglicherweise nicht vorhanden oder enthalten ihre Standardwerte, wenn RELEASE_AIDL_USE_UNFROZEN false ist und die Werte neuer Felder, die ein Dienst zu senden versucht, auf dem Weg aus dem Prozess verworfen werden.

Neue Typen, die in dieser nicht eingefrorenen Version hinzugefügt wurden, können über die Schnittstelle nicht gesendet oder empfangen werden.

Die Implementierung erhält nie einen Aufruf für neue Methoden von Clients, wenn RELEASE_AIDL_USE_UNFROZEN gleich false ist.

Verwenden Sie neue Enumeratoren nur mit der Version, in der sie eingeführt wurden, und nicht mit der vorherigen Version.

Normalerweise verwenden Sie foo->getInterfaceVersion(), um zu sehen, welche Version die Remote-Schnittstelle verwendet. Da Sie mit der flagbasierten Versionsverwaltung zwei verschiedene Versionen implementieren, sollten Sie die Version der aktuellen Schnittstelle abrufen. Dazu können Sie die Schnittstellenversion des aktuellen Objekts abrufen, z. B. this->getInterfaceVersion() oder die anderen Methoden für my_ver. Weitere Informationen finden Sie unter Schnittstellenversion des Remote-Objekts abfragen.

Neue VINTF-stabile Schnittstellen

Wenn ein neues AIDL-Schnittstellenpaket hinzugefügt wird, gibt es keine letzte eingefrorene Version. Daher gibt es kein Verhalten, auf das zurückgegriffen werden kann, wenn RELEASE_AIDL_USE_UNFROZEN gleich false ist. Verwenden Sie diese Schnittstellen nicht. Wenn RELEASE_AIDL_USE_UNFROZEN false ist, kann der Dienst die Schnittstelle nicht registrieren und die Clients können sie nicht finden.

Sie können die Dienste basierend auf dem Wert des Flags RELEASE_AIDL_USE_UNFROZEN im Geräte-Makefile bedingt hinzufügen:

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

Wenn der Dienst Teil eines größeren Prozesses ist und Sie ihn nicht bedingt dem Gerät hinzufügen können, prüfen Sie, ob der Dienst mit IServiceManager::isDeclared() deklariert ist. Wenn sie deklariert wurde und die Registrierung fehlgeschlagen ist, brechen Sie den Vorgang ab. Wenn sie nicht deklariert ist, kann sie nicht registriert werden.

Neue VINTF-stabile Erweiterungsschnittstellen

Für neue Erweiterungsschnittstellen gibt es keine vorherige Version, auf die zurückgegriffen werden kann. Da sie nicht bei ServiceManager registriert oder in VINTF-Manifesten deklariert sind, kann IServiceManager::isDeclared() nicht verwendet werden, um zu bestimmen, wann die Erweiterungsschnittstelle an eine andere Schnittstelle angehängt werden soll.

Mit der Variablen RELEASE_AIDL_USE_UNFROZEN kann bestimmt werden, ob die neue, nicht eingefrorene Erweiterungsschnittstelle an die vorhandene Schnittstelle angehängt werden soll, um sie nicht auf veröffentlichten Geräten zu verwenden. Die Schnittstelle muss eingefroren werden, damit sie auf veröffentlichten Geräten verwendet werden kann.

Die VTS-Tests vts_treble_vintf_vendor_test und vts_treble_vintf_framework_test erkennen, wenn eine nicht eingefrorene Erweiterungsschnittstelle auf einem veröffentlichten Gerät verwendet wird, und geben einen Fehler aus.

Wenn die Erweiterungsoberfläche nicht neu ist und eine zuvor eingefrorene Version vorhanden ist, wird auf diese zuvor eingefrorene Version zurückgegriffen und es sind keine zusätzlichen Schritte erforderlich.

Cuttlefish als Entwicklungstool

Jedes Jahr nach dem Einfrieren der VINTF passen wir die Framework-Kompatibilitätsmatrix (Framework Compatibility Matrix, FCM) target-level und die PRODUCT_SHIPPING_API_LEVEL von Cuttlefish an, damit sie Geräte widerspiegeln, die mit der Version des nächsten Jahres auf den Markt kommen. Wir passen target-level und PRODUCT_SHIPPING_API_LEVEL an, damit es ein Startgerät gibt, das getestet wurde und die neuen Anforderungen für die Veröffentlichung im nächsten Jahr erfüllt.

Wenn RELEASE_AIDL_USE_UNFROZEN true ist, wird Cuttlefish für die Entwicklung zukünftiger Android-Versionen verwendet. Es ist auf das FCM-Level der Android-Version im nächsten Jahr und PRODUCT_SHIPPING_API_LEVEL ausgerichtet und muss die VSR (Vendor Software Requirements) der nächsten Version erfüllen.

Wenn RELEASE_AIDL_USE_UNFROZEN = false ist, hat Cuttlefish die vorherigen target-level und PRODUCT_SHIPPING_API_LEVEL, um ein Release-Gerät darzustellen. In Android 14 und niedriger wurde diese Unterscheidung durch verschiedene Git-Branches erreicht, in denen die Änderung an FCM target-level, der Versand-API-Ebene oder anderem Code für die nächste Version nicht berücksichtigt wurde.

Regeln für Modulnamen

Unter Android 11 wird für jede Kombination der aktivierten Versionen und Back-Ends automatisch ein Stub-Bibliotheksmodul erstellt. Wenn Sie zum Verknüpfen auf ein bestimmtes Stub-Bibliotheksmodul verweisen möchten, verwenden Sie nicht den Namen des aidl_interface-Moduls, sondern den Namen des Stub-Bibliotheksmoduls, der ifacename-version-backend lautet. Dabei gilt:

  • ifacename: Name des aidl_interface-Moduls
  • version ist eine der folgenden Optionen:
    • Vversion-number für die nicht mehr unterstützten Versionen
    • Vlatest-frozen-version-number + 1 für die Version des aktuellen Entwicklungszweigs (die noch nicht eingefroren wurde)
  • backend ist eine der folgenden Optionen:
    • java für das Java-Back-End,
    • cpp für das C++-Backend,
    • ndk oder ndk_platform für das NDK-Backend. Ersteres gilt für Apps und letzteres für die Plattformnutzung bis Android 13. Verwenden Sie in Android 13 und höher nur ndk.
    • rust für das Rust-Back-End.

Angenommen, es gibt ein Modul mit dem Namen foo und die neueste Version ist 2. Es unterstützt sowohl NDK als auch C++. In diesem Fall generiert AIDL die folgenden Module:

  • Basierend auf Version 1
    • foo-V1-(java|cpp|ndk|ndk_platform|rust)
  • Basierend auf Version 2 (der neuesten stabilen Version)
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • Basierend auf der ToT-Version
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

Im Vergleich zu Android 11:

  • foo-backend, die sich auf die letzte stabile Version bezog, wird zu foo-V2-backend.
  • foo-unstable-backend, die sich auf die ToT-Version bezog, wird zu foo-V3-backend.

Die Namen der Ausgabedateien sind immer mit den Modulnamen identisch.

  • Basierend auf Version 1: foo-V1-(cpp|ndk|ndk_platform|rust).so
  • Basierend auf Version 2: foo-V2-(cpp|ndk|ndk_platform|rust).so
  • Basierend auf der ToT-Version: foo-V3-(cpp|ndk|ndk_platform|rust).so

Der AIDL-Compiler erstellt weder ein unstable-Versionsmodul noch ein Modul ohne Versionsangabe für eine stabile AIDL-Schnittstelle. Ab Android 12 enthält der aus einer stabilen AIDL-Schnittstelle generierte Modulname immer die Version.

Neue Meta-Schnittstellenmethoden

In Android 10 wurden mehrere Meta-Schnittstellenmethoden für das stabile AIDL hinzugefügt.

Schnittstellenversion des Remote-Objekts abfragen

Clients können die Version und den Hash der Schnittstelle abfragen, die das Remote-Objekt implementiert, und die zurückgegebenen Werte mit den Werten der Schnittstelle vergleichen, die der Client verwendet.

Beispiel mit dem cpp-Backend:

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();

Beispiel mit dem Backend ndk (und 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);

Beispiel mit dem java-Backend:

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();

Für die Java-Sprache MUSS die Remote-Seite getInterfaceVersion() und getInterfaceHash() wie folgt implementieren (super wird anstelle von IFoo verwendet, um Fehler beim Kopieren und Einfügen zu vermeiden). Je nach javac-Konfiguration ist möglicherweise die Annotation @SuppressWarnings("static") erforderlich, um Warnungen zu deaktivieren:

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

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

Das liegt daran, dass die generierten Klassen (IFoo, IFoo.Stub usw.) für Client und Server freigegeben sind (z. B. können sich die Klassen im Boot-Classpath befinden). Wenn Klassen freigegeben werden, wird der Server auch mit der neuesten Version der Klassen verknüpft, obwohl er möglicherweise mit einer älteren Version der Schnittstelle erstellt wurde. Wenn dieses Meta-Interface in der freigegebenen Klasse implementiert ist, wird immer die neueste Version zurückgegeben. Wenn Sie die Methode jedoch wie oben implementieren, wird die Versionsnummer der Schnittstelle in den Code des Servers eingebettet (da IFoo.VERSION ein static final int ist, das bei der Referenzierung inline eingefügt wird). Die Methode kann also die genaue Version zurückgeben, mit der der Server erstellt wurde.

Mit älteren Schnittstellen arbeiten

Es ist möglich, dass ein Client mit der neueren Version einer AIDL-Schnittstelle aktualisiert wird, der Server jedoch die alte AIDL-Schnittstelle verwendet. In solchen Fällen wird beim Aufrufen einer Methode auf einer alten Schnittstelle UNKNOWN_TRANSACTION zurückgegeben.

Mit stabilem AIDL haben Clients mehr Kontrolle. Auf der Clientseite können Sie eine Standardimplementierung für eine AIDL-Schnittstelle festlegen. Eine Methode in der Standardimplementierung wird nur aufgerufen, wenn die Methode auf der Remote-Seite nicht implementiert ist (weil sie mit einer älteren Version der Schnittstelle erstellt wurde). Da Standardwerte global festgelegt werden, sollten sie nicht in potenziell gemeinsam genutzten Kontexten verwendet werden.

Beispiel in C++ unter Android 13 und höher:

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

Beispiel in Java:

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

foo.anAddedMethod(...);

Sie müssen nicht die Standardimplementierung aller Methoden in einer AIDL-Schnittstelle angeben. Methoden, die garantiert auf der Remote-Seite implementiert werden (weil Sie sicher sind, dass die Remote-Seite erstellt wird, wenn die Methoden in der AIDL-Schnittstellenbeschreibung enthalten sind), müssen in der Standardklasse impl nicht überschrieben werden.

Vorhandenes AIDL in strukturiertes oder stabiles AIDL konvertieren

Wenn Sie eine vorhandene AIDL-Schnittstelle und Code haben, der sie verwendet, gehen Sie so vor, um die Schnittstelle in eine stabile AIDL-Schnittstelle zu konvertieren.

  1. Ermitteln Sie alle Abhängigkeiten Ihrer Schnittstelle. Prüfen Sie für jedes Paket, von dem die Schnittstelle abhängt, ob es in stabilem AIDL definiert ist. Wenn nicht definiert, muss das Paket konvertiert werden.

  2. Konvertieren Sie alle Parcelables in Ihrer Schnittstelle in stabile Parcelables. Die Schnittstellendateien selbst können unverändert bleiben. Dazu wird die Struktur direkt in AIDL-Dateien ausgedrückt. Verwaltungsklassen müssen neu geschrieben werden, damit sie diese neuen Typen verwenden. Das kann erfolgen, bevor Sie ein aidl_interface-Paket erstellen (siehe unten).

  3. Erstellen Sie ein aidl_interface-Paket (wie oben beschrieben), das den Namen Ihres Moduls, seine Abhängigkeiten und alle anderen erforderlichen Informationen enthält. Damit es stabilisiert (nicht nur strukturiert) wird, muss es auch versioniert werden. Weitere Informationen finden Sie unter Schnittstellenversionen.