Stabile AIDL

Android 10 unterstützt die stabile Android Interface Definition Language (AIDL). Das ist eine neue Möglichkeit, die API (Application Program Interface) und die ABI (Application Binary Interface) zu verwalten, die von AIDL-Schnittstellen bereitgestellt werden. Stable AIDL funktioniert genau wie AIDL, aber das Build-System überwacht die Schnittstellenkompatibilität und es gibt Einschränkungen für Ihre Möglichkeiten:

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

Strukturierte und stabile AIDL

Strukturierte AIDL-Dateien beziehen sich auf Typen, die ausschließlich in AIDL definiert sind. Eine Deklaration für Parcelable (ein benutzerdefiniertes Parcelable) ist beispielsweise keine strukturierte AIDL. Parcelable-Objekte mit ihren in AIDL definierten Feldern werden als strukturierte Parcelable-Objekte bezeichnet.

Für stabile AIDL-Dateien ist strukturiertes AIDL erforderlich, damit das Buildsystem und der Compiler erkennen können, ob Änderungen an Parcelable-Objekten abwärtskompatibel sind. Nicht alle strukturierten Oberflächen sind jedoch stabil. Damit eine Schnittstelle stabil ist, müssen nur strukturierte Typen und die folgenden Versionierungsfunktionen verwendet werden. Umgekehrt ist eine Benutzeroberfläche nicht stabil, wenn das Kern-Buildsystem zum Erstellen verwendet wird oder 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 Benutzeroberfläche besteht. Der Pfad für einen AIDL-Typ Foo, der in einem Paket com.acme definiert ist, sollte sich unter <base_path>/com/acme/Foo.aidl befinden. Dabei kann <base_path> jedes Verzeichnis sein, das mit dem Verzeichnis, in dem sich Android.bp befindet, in Beziehung steht. Im vorherigen Beispiel ist <base_path> srcs/aidl.
  • local_include_dir: Der Pfad, ab dem der Paketname beginnt. Sie entspricht <base_path>, wie oben beschrieben.
  • imports: Eine Liste der verwendeten aidl_interface-Module. Wenn eine Ihrer AIDL-Schnittstellen eine Schnittstelle oder ein Parcelable aus einer anderen aidl_interface verwendet, geben Sie hier den Namen an. Dies kann der Name allein sein, um sich auf die neueste Version zu beziehen, oder der Name mit dem Versionssuffix (z. B. -V1), um sich auf eine bestimmte Version zu beziehen. Die Angabe einer Version wird seit Android 12 unterstützt.
  • versions: Die vorherigen Versionen der Benutzeroberfläche, die unter api_dir eingefroren sind. Ab Android 11 sind die versions unter aidl_api/name eingefroren. Wenn es keine eingefrorenen Versionen einer Benutzeroberfläche gibt, sollte dies nicht angegeben werden. Es werden dann keine Kompatibilitätstests 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 die Stabilitätsgarantie dieser Schnittstelle. Nur "vintf" wird unterstützt. Wenn stability nicht festgelegt ist, prüft das Buildsystem, ob die Benutzeroberfläche abwärtskompatibel ist, es sei denn, unstable ist angegeben. Wenn „unset“ festgelegt ist, entspricht dies einer stabilen Schnittstelle in diesem Kompilierungskontext (d. h. entweder alle Systemelemente, z. B. Elemente in system.img und zugehörige Partitionen, oder alle Anbieterelemente, z. B. Elemente in vendor.img und zugehörige Partitionen). Wenn stability auf "vintf" festgelegt ist, entspricht dies einem Stabilitätsversprechen: Die Benutzeroberfläche muss so lange stabil bleiben, wie sie verwendet wird.
  • gen_trace: Optionales Flag, mit dem die Aufzeichnung aktiviert oder deaktiviert wird. Ab Android 14 ist der Standardwert true für die cpp- und java-Backends.
  • host_supported: Optionales Flag, das die generierten Bibliotheken für die Hostumgebung verfügbar macht, wenn es auf true gesetzt ist.
  • unstable: Mit diesem optionalen Flag wird angegeben, 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 Benutzeroberfläche noch muss er aktualisiert werden.
  • frozen: Optionales Flag. Wenn es auf true gesetzt ist, hat sich die Benutzeroberfläche seit der vorherigen Version nicht geändert. Dadurch sind mehr Buildzeitprüfungen möglich. Wenn der Wert auf false festgelegt ist, befindet sich die Benutzeroberfläche in der Entwicklungsphase und es gibt neue Änderungen. Wenn Sie foo-freeze-api ausführen, wird eine neue Version generiert und der Wert wird automatisch in true geändert. In Android 14 eingeführt.
  • backend.<type>.enabled: Mit diesen Flags können Sie die einzelnen Back-Ends aktivieren, 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 Backends 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: Optionales Flag, das steuert, ob zusätzlicher Code zum Erfassen von Informationen zur Transaktion generiert wird.
  • backend.[cpp|java].vndk.enabled: Das optionale Flag, um diese Schnittstelle zu einem Teil von 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 für ndk_header und cpp_header.
  • backend.java.sdk_version: Optionales Flag zum Angeben der Version des SDK, gegen die die Java-Stub-Bibliothek erstellt wird. Der Standardwert ist "system_current". Dieser Wert sollte nicht festgelegt werden, wenn backend.java.platform_apis = true ist.
  • backend.java.platform_apis: Optionales Flag, das auf true gesetzt werden sollte, wenn die generierten Bibliotheken nicht auf dem SDK, sondern auf der Plattform-API basieren sollen.

Für jede Kombination aus Versionen und aktivierten Backends wird eine Stub-Bibliothek erstellt. Weitere 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 stabiler AIDL ähneln herkömmlichen Schnittstellen, mit der Ausnahme, dass keine unstrukturierten Parcelables verwendet werden dürfen, da diese nicht stabil sind (siehe Strukturiertes und stabiles AIDL). Der Hauptunterschied bei stabiler AIDL besteht in der Definition von Parcelables. Bisher wurden Parcelable-Objekte vordeklariert. In stabiler (und daher strukturierter) 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 (ist 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 einer 0 ähnelt oder leer ist. Aufzählungen ohne Standardwert werden mit 0 initialisiert, auch wenn es keinen Nullenumerator gibt.

Stub-Bibliotheken verwenden

Nachdem Sie Ihrem Modul Stub-Bibliotheken als Abhängigkeit hinzugefügt haben, können Sie sie in Ihre Dateien einbinden. Hier sind Beispiele für Stub-Bibliotheken im Build-System. Android.mk kann auch für ältere Moduldefinitionen verwendet werden. In diesen Beispielen ist die Version nicht vorhanden, sodass eine instabile Benutzeroberfläche verwendet wird. Namen für Benutzeroberflächen mit Versionen enthalten jedoch zusätzliche Informationen. Weitere Informationen finden Sie unter Benutzeroberflächen versionieren.

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

Versionierungsschnittstellen

Wenn Sie ein Modul mit dem Namen foo deklarieren, wird im Build-System auch ein Ziel erstellt, mit dem Sie die API des Moduls verwalten können. Beim Erstellen fügt foo-freeze-api je nach Android-Version eine neue API-Definition unter api_dir oder aidl_api/name und eine .hash-Datei hinzu. Beide stellen die neu eingefrorene Version der Benutzeroberfläche dar. 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 ist jedoch in imports in versions_with_info für den Import angegeben, der keine explizite Version hat. Nachdem die versions_with_info-Eigenschaft angegeben wurde, führt das Buildsystem Kompatibilitätsprüfungen zwischen eingefrorenen Versionen und auch zwischen Top of Tree (ToT) und der neuesten eingefrorenen Version durch.

Außerdem müssen Sie die API-Definition der ToT-Version verwalten. Jedes Mal, 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 Benutzeroberfläche zu erhalten, können Inhaber Folgendes hinzufügen:

  • Methoden am Ende einer Schnittstelle (oder Methoden mit explizit definierten neuen Sequenzen)
  • Elemente am Ende eines Parcelable (für jedes Element muss ein Standard hinzugefügt werden)
  • Konstante Werte
  • Unter Android 11 haben Zähler
  • In Android 12: Felder am Ende einer Union

Andere Aktionen sind nicht zulässig und niemand sonst kann eine Benutzeroberfläche ändern. Andernfalls besteht die Gefahr von Konflikten mit Änderungen, die ein Inhaber vornimmt.

Wenn Sie testen möchten, ob alle Oberflächen 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 die Build-Umgebung müssen alle stabilen AIDL-Schnittstellen eingefroren sein. Das Feld owner: muss als „aosp“ oder „test“ angegeben sein.

Stabilität von Importen

Das Aktualisieren der Versionen von Importen für eingefrorene Versionen einer Schnittstelle ist auf der Stable-AIDL-Ebene abwärtskompatibel. Das Aktualisieren dieser erfordert jedoch das Aktualisieren aller Server und Clients, die eine ältere Version der Benutzeroberfläche verwenden. Außerdem kann es bei einigen Apps zu Problemen kommen, wenn verschiedene Versionen von Typen verwendet werden. Bei reinen Typen- oder gängigen Paketen ist das in der Regel kein Problem, da Code bereits zum Umgang mit unbekannten Typen aus IPC-Transaktionen geschrieben werden muss.

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

Versionierte Oberflächen verwenden

Schnittstellenmethoden

Wenn bei der Laufzeit neue Methoden auf einem alten Server aufgerufen werden, erhalten neue Clients je nach Backend entweder eine Fehlermeldung 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 der Meldung, dass die API nicht implementiert ist.

Strategien zur Bewältigung dieses Problems finden Sie unter Versionen abfragen und Standardwerte verwenden.

Parcelables

Wenn Parcelable-Objekten neue Felder hinzugefügt werden, werden sie von alten Clients und Servern verworfen. Wenn neue Clients und Server alte Parcelable-Objekte empfangen, werden die Standardwerte für neue Felder automatisch ausgefüllt. 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 nach Bedarf ablehnen oder ignorieren, da in Zukunft möglicherweise weitere hinzugefügt werden. Ein Server sollte beispielsweise nicht abbrechen, 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 sieht nie die Vereinigung mit dem neuen Feld. Bei einer Einwegtransaktion wird der Fehler ignoriert. Andernfalls ist der Fehler BAD_VALUE(für das C++- oder NDK-Backend) oder IllegalArgumentException(für das Java-Backend). Der Fehler wird ausgegeben, wenn der Client eine Union, die auf das neue Feld festgelegt ist, an einen alten Server sendet oder wenn ein alter Client die Union 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. C++ hat die One Definition Rule, die nur eine Definition jedes Symbols erfordert.

Der Android-Build gibt einen Fehler zurück, wenn ein Modul von verschiedenen Versionen derselben aidl_interface-Bibliothek abhängt. Das Modul kann direkt oder indirekt über Abhängigkeiten der Abhängigkeiten von diesen Bibliotheken davon abhängig sein. 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, um dieselbe (in der Regel die neueste) Version dieser Bibliotheken zu enthalten.

Wenn die Benutzeroberflächenbibliothek 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 Benutzeroberfläche eingeführt wird, können diese Standardeinstellungen aktualisiert und alle Module, die sie verwenden, gemeinsam aktualisiert werden. So wird sichergestellt, dass nicht unterschiedliche Versionen der Benutzeroberfläche verwendet werden.

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, die erfordern, dass bestimmte Versionen zusammen verwendet werden. Diese Situation kann schwierig zu verwalten sein, wenn es gemeinsame aidl_interface-Module gibt, die in mehrere aidl_interface-Module importiert werden, die in denselben Prozessen zusammen verwendet werden.

Mit aidl_interfaces_defaults können Sie eine Definition der neuesten Versionen von Abhängigkeiten für ein aidl_interface speichern, die an einem 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"],
  ...
}

Flaggenbasierte Entwicklung

In der Entwicklung befindliche (nicht eingefrorene) Oberflächen können nicht auf Release-Geräten verwendet werden, da sie nicht garantiert abwärtskompatibel sind.

AIDL unterstützt einen Laufzeit-Fallback für diese nicht eingefrorenen Schnittstellenbibliotheken, damit Code anhand der neuesten nicht eingefrorenen Version geschrieben und trotzdem auf Release-Geräten verwendet werden kann. Das rückwärtskompatible Verhalten von Clients ähnelt dem vorhandenen Verhalten. Bei der Umstellung müssen die Implementierungen auch diesen Verhaltensweisen folgen. Weitere Informationen finden Sie unter Versionierte Oberflächen verwenden.

AIDL-Build-Flag

Das Flag, das dieses Verhalten steuert, ist RELEASE_AIDL_USE_UNFROZEN und in build/release/build_flags.bzl definiert. true bedeutet, dass die nicht eingefrorene Version der Benutzeroberfläche zur Laufzeit verwendet wird. 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 überschreiben, müssen es aber vor der Veröffentlichung auf false zurücksetzen. Normalerweise wird die Entwicklung mit einer Konfiguration durchgeführt, bei der das Flag auf true gesetzt ist.

Kompatibilitätsmatrix und Manifeste

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

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

Matrizen

Von Partnern stammende Oberflächen werden geräte- oder produktspezifischen Kompatibilitätsmatrizen hinzugefügt, auf die das Gerät während der Entwicklung ausgerichtet ist. Wenn also einer Kompatibilitätsmatrix eine neue, nicht eingefrorene Version einer Benutzeroberfläche hinzugefügt wird, müssen die vorherigen eingefrorenen Versionen für RELEASE_AIDL_USE_UNFROZEN=false erhalten bleiben. Sie können dies 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 false ist.

Manifeste

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

In den Manifesten und Manifestfragmenten wird angegeben, welche Version einer Benutzeroberfläche ein Dienst implementiert. Wenn Sie die neueste nicht eingefrorene Version einer Benutzeroberfläche verwenden, muss das Manifest entsprechend aktualisiert werden. Wenn RELEASE_AIDL_USE_UNFROZEN=false, werden die Manifesteinträge um libvintf angepasst, um die Änderung in der generierten AIDL-Bibliothek widerzuspiegeln. Die Version wird von der nicht eingefrorenen Version N in die letzte eingefrorene Version N - 1 geändert. Daher müssen Nutzer 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 false ist, sehen die Dienste immer so aus wie die letzte oder eine frühere eingefrorene Version. Wenn Sie beispielsweise neue nicht eingefrorene Methoden aufrufen, wird UNKNOWN_TRANSACTION zurückgegeben oder neue parcelable-Felder haben ihre Standardwerte. Android-Framework-Clients müssen mit zusätzlichen früheren Versionen abwärtskompatibel sein. Dies ist jedoch eine neue Anforderung für Anbieter-Clients und Clients von Partneroberflächen.

Änderungen bei der HAL-Implementierung

Der größte Unterschied bei der HAL-Entwicklung im Vergleich zur flagbasierten Entwicklung besteht darin, dass HAL-Implementierungen abwärtskompatibel mit der letzten eingefrorenen Version sein müssen, damit sie funktionieren, wenn RELEASE_AIDL_USE_UNFROZEN = false ist. Die Abwärtskompatibilität bei Implementierungen und Gerätecode ist eine neue Herausforderung. 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 Benutzeroberfläche hat drei eingefrorene Versionen. Die Benutzeroberfläche wird mit einer neuen Methode aktualisiert. Sowohl der Client als auch der Dienst werden auf die neue Version 4 der Bibliothek aktualisiert. Da die V4-Bibliothek auf einer nicht eingefrorenen Version der Benutzeroberfläche basiert, verhält sie sich wie die letzte eingefrorene Version, Version 3, wenn RELEASE_AIDL_USE_UNFROZEN = false ist, und verhindert die Verwendung der neuen Methode.

Wenn die Benutzeroberfläche eingefroren ist, wird für alle Werte von RELEASE_AIDL_USE_UNFROZEN diese eingefrorene Version verwendet und der Code zur Abwärtskompatibilität kann entfernt werden.

Wenn Sie Methoden in Callbacks aufrufen, müssen Sie den Fall, in dem UNKNOWN_TRANSACTION zurückgegeben wird, ordnungsgemäß behandeln. Clients implementieren möglicherweise zwei verschiedene Versionen eines Callbacks basierend auf der Release-Konfiguration. Du kannst also nicht davon ausgehen, dass der Client die neueste Version sendet. Neue Methoden könnten dies jedoch zurückgeben. Das ähnelt der Abwärtskompatibilität stabiler AIDL-Clients mit Servern, die im Abschnitt Versionierte Schnittstellen verwenden beschrieben wird.

// 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. Die Werte neuer Felder, die ein Dienst senden möchte, werden am Ende des Prozesses gelöscht.

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

Die Implementierung erhält nie einen Aufruf für neue Methoden von Clients, wenn RELEASE_AIDL_USE_UNFROZEN 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-Benutzeroberfläche verwendet. Wenn Sie jedoch die Unterstützung für die flagbasierte Versionierung verwenden, implementieren Sie zwei unterschiedliche Versionen. Daher sollten Sie die Version der aktuellen Benutzeroberfläche abrufen. Dazu können Sie die Benutzeroberflächenversion des aktuellen Objekts abrufen, z. B. this->getInterfaceVersion() oder die anderen Methoden für my_ver. Weitere Informationen finden Sie unter Oberflächenversion des Remoteobjekts abfragen.

Neue stabile VINTF-Schnittstellen

Wenn ein neues AIDL-Interface-Paket hinzugefügt wird, gibt es keine letzte eingefrorene Version. Wenn RELEASE_AIDL_USE_UNFROZEN also false ist, gibt es kein Verhalten, auf das zurückgegriffen werden kann. Verwenden Sie diese Schnittstellen nicht. Wenn RELEASE_AIDL_USE_UNFROZEN false ist, erlaubt der Dienstmanager dem Dienst nicht, die Benutzeroberfläche zu registrieren. Die Clients können sie dann nicht finden.

Sie können die Dienste bedingt basierend auf dem Wert des RELEASE_AIDL_USE_UNFROZEN-Flags im Geräte-Makefile 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 dem Gerät nicht bedingt hinzufügen können, können Sie prüfen, ob der Dienst mit IServiceManager::isDeclared() deklariert ist. Wenn die Deklaration vorhanden ist, aber die Registrierung fehlgeschlagen ist, beenden Sie den Vorgang. Wenn sie nicht deklariert ist, wird sie wahrscheinlich nicht registriert.

Cuttlefish als Entwicklungstool

Jedes Jahr, nachdem die VINTF eingefroren wurde, passen wir die Framework Compatibility Matrix (FCM) target-level und die PRODUCT_SHIPPING_API_LEVEL von Cuttlefish an, damit sie die Geräte widerspiegeln, die mit der Veröffentlichung des nächsten Jahres auf den Markt kommen. Wir passen target-level und PRODUCT_SHIPPING_API_LEVEL so an, dass es ein eingeführtes Gerä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-Releases verwendet. Sie ist auf die FCM-Ebene der Android-Version des nächsten Jahres und auf PRODUCT_SHIPPING_API_LEVEL ausgerichtet. Daher muss sie die Softwareanforderungen des Anbieters (Vendor Software Requirements, VSR) 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 widerzuspiegeln. Unter Android 14 und niedriger wird diese Unterscheidung mit verschiedenen Git-Branches erreicht, die die Änderung an FCM target-level, die API-Schichten für die Bereitstellung oder anderen Code, der auf die nächste Version ausgerichtet ist, nicht berücksichtigen.

Regeln für die Benennung von Modulen

Unter Android 11 wird für jede Kombination der aktivierten Versionen und Backends automatisch ein Stub-Bibliotheksmodul erstellt. Wenn Sie für die Verknüpfung auf ein bestimmtes Stub-Bibliotheksmodul verweisen möchten, verwenden Sie nicht den Namen des aidl_interface-Moduls, sondern den Namen des Stub-Bibliotheksmoduls, also ifacename-version-backend, wobei

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

Angenommen, es gibt ein Modul mit dem Namen foo, dessen aktuelle Version 2 ist und das sowohl NDK als auch C++ unterstützt. 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, das sich auf die neueste stabile Version bezog, wird in foo-V2-backend geändert.
  • foo-unstable-backend, das sich auf die ToT-Version bezog, wird zu foo-V3-backend.

Die Namen der Ausgabedateien stimmen immer mit den Namen der Module überein.

  • 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 nicht versioniertes Modul für eine stabile AIDL-Schnittstelle. Ab Android 12 enthält der Modulname, der aus einer stabilen AIDL-Schnittstelle generiert wird, immer die Version.

Neue Methoden für die Meta-Benutzeroberfläche

Android 10 fügt mehrere Meta-Interface-Methoden für die stabile AIDL hinzu.

Schnittstellenversion des Remote-Objekts abfragen

Clients können die Version und den Hash der Schnittstelle abfragen, die vom Remoteobjekt implementiert wird, 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 ndk- (und dem ndk_platform-) Backend:

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

Bei der Java-Sprache MÜSSEN getInterfaceVersion() und getInterfaceHash() auf der Remoteseite so implementiert werden. super wird anstelle von IFoo verwendet, um Fehler beim Kopieren und Einfügen zu vermeiden. Je nach javac-Konfiguration ist die Anmerkung @SuppressWarnings("static") möglicherweise 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.) zwischen Client und Server freigegeben werden. Die Klassen können sich beispielsweise im Boot-Classpath befinden. Wenn Kurse freigegeben werden, wird der Server auch mit der neuesten Version der Kurse verknüpft, auch wenn er mit einer älteren Version der Benutzeroberfläche erstellt wurde. Wenn diese Meta-Benutzeroberfläche 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 eine static final int ist, die bei der Referenzierung inline eingefügt wird). Die Methode kann also die genaue Version zurückgeben, mit der der Server erstellt wurde.

Ältere Benutzeroberflächen

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

Mit stabiler AIDL haben Kunden mehr Kontrolle. Auf der Clientseite können Sie für eine AIDL-Schnittstelle eine Standardimplementierung festlegen. Eine Methode in der Standardimplementierung wird nur dann aufgerufen, wenn die Methode nicht auf der Remoteseite implementiert ist, weil sie mit einer älteren Version der Benutzeroberfläche erstellt wurde. Da Standardeinstellungen global festgelegt werden, sollten sie nicht in potenziell freigegebenen Kontexten verwendet werden.

Beispiel in C++ in 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-Steuerung erstellt wurde, als die Methoden in der AIDL-Beschreibung der Schnittstelle enthalten waren), müssen in der Standardklasse impl nicht überschrieben werden.

Vorhandene AIDL in strukturierte oder stabile AIDL konvertieren

Wenn Sie eine vorhandene AIDL-Schnittstelle und Code haben, der sie verwendet, können Sie die Schnittstelle mit den folgenden Schritten in eine stabile AIDL-Schnittstelle umwandeln.

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

  2. Konvertieren Sie alle Parcelable-Objekte in Ihrer Benutzeroberfläche in stabile Parcelable-Objekte. Die Benutzeroberflächedateien selbst können unverändert bleiben. Geben Sie dazu die Struktur direkt in AIDL-Dateien an. Verwaltungsklassen müssen neu geschrieben werden, um diese neuen Typen zu verwenden. Das kann vor dem Erstellen eines aidl_interface-Pakets erfolgen (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 er stabil (nicht nur strukturiert) ist, muss er auch versioniert werden. Weitere Informationen finden Sie unter Versionierung von Benutzeroberflächen.