Stabile AIDL

Android 10 unterstützt die stabile Android Interface Definition Language (AIDL), eine neue Möglichkeit, die über AIDL-Schnittstellen bereitgestellte API/Binärschnittstelle (Application binary Interface, ABI) im Blick zu behalten. Die stabile AIDL unterscheidet sich in folgenden wichtigen Punkten von AIDL:

  • Schnittstellen werden im Build-System mit aidl_interfaces definiert.
  • Schnittstellen können nur strukturierte Daten enthalten. Parcelables, die die gewünschten Typen darstellen, werden automatisch anhand ihrer AIDL-Definition erstellt und automatisch marschiert und unmarshalliert.
  • Schnittstellen können als stabil (abwärtskompatibel) deklariert werden. Die API wird dann in einer Datei neben der AIDL-Schnittstelle verfolgt und versioniert.

Strukturierte vs. stabile AIDL

Strukturierte AIDL bezieht sich auf Typen, die ausschließlich in AIDL definiert sind. Beispielsweise ist eine Deklaration für Parcelable (ein benutzerdefiniertes Paket) keine strukturierte AIDL. Parcelables, deren Felder in AIDL definiert sind, werden als strukturierte Parcelables bezeichnet.

Stabile AIDL erfordert eine strukturierte AIDL, damit das Build-System und der Compiler verstehen können, ob Änderungen an Paketen abwärtskompatibel sind. Allerdings sind nicht alle strukturierten Oberflächen stabil. Damit eine Schnittstelle stabil ist, darf sie nur strukturierte Typen sowie die folgenden Versionsverwaltungsfunktionen verwenden. Umgekehrt ist eine Schnittstelle nicht stabil, wenn das Core-Build-System zu ihrer Erstellung 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 com.acme-Paket definiert ist, sollte sich unter <base_path>/com/acme/Foo.aidl befinden. Dabei kann <base_path> ein beliebiges Verzeichnis sein, das sich auf das Verzeichnis von Android.bp bezieht. Im obigen Beispiel ist <base_path> der Wert srcs/aidl.
  • local_include_dir: Pfad, von dem aus der Paketname beginnt. Sie entspricht dem oben erläuterten <base_path>-Objekt.
  • imports: Eine Liste von aidl_interface-Modulen, die verwendet werden. Wenn eine Ihrer AIDL-Schnittstellen eine Schnittstelle oder ein Paket aus einem anderen aidl_interface verwendet, geben Sie hier den Namen ein. Dies kann der Name an sich 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 früheren Versionen der Benutzeroberfläche, die unter api_dir fixiert sind, werden ab Android 11 die versions unter aidl_api/name fixiert. Wenn keine eingefrorenen Versionen einer Schnittstelle vorhanden sind, sollten Sie dies nicht angeben und es werden keine Kompatibilitätsprüfungen durchgeführt. Dieses Feld wurde bei 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 diese Version von aidl_interface importiert hat. Die Definition von Version V der AIDL-Schnittstelle IFACE finden Sie 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ätversprechen dieser Schnittstelle. Derzeit wird nur "vintf" unterstützt. Wenn stability nicht festgelegt ist, prüft das Build-System, ob die Schnittstelle abwärtskompatibel ist, sofern nicht unstable angegeben ist. Wenn die Richtlinie nicht konfiguriert ist, entspricht dies einer Schnittstelle mit Stabilität in diesem Kompilierungskontext (also entweder alle Systemelemente, z. B. in system.img und zugehörigen Partitionen oder alle Anbieterelemente, z. B. in vendor.img und zugehörigen Partitionen). Wenn stability auf "vintf" gesetzt ist, entspricht dies einem Stabilitätsversprechen: Die Schnittstelle muss stabil gehalten werden, solange sie verwendet wird.
  • gen_trace: Das optionale Flag zum Aktivieren oder Deaktivieren des Tracings. Ab Android 14 ist die Standardeinstellung true für die Back-Ends cpp und java.
  • host_supported: Das optionale Flag, das bei Einstellung auf true die generierten Bibliotheken für die Hostumgebung zur Verfügung stellt.
  • unstable: Das optionale Flag, das angibt, dass diese Schnittstelle nicht stabil sein muss. Wenn dieser Wert auf true gesetzt 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 bedeutet, dass sich seit der vorherigen Version der Benutzeroberfläche keine Änderungen an der Schnittstelle ergeben. Dies ermöglicht mehr Prüfungen während der Build-Erstellung. Wenn false festgelegt ist, befindet sich die Schnittstelle noch in der Entwicklung und es befinden sich neue Änderungen. Wenn foo-freeze-api ausgeführt wird, wird also eine neue Version generiert und der Wert automatisch in true geändert. Wurde mit Android 14 eingeführt.
  • backend.<type>.enabled: Diese Flags schalten die einzelnen Back-Ends ein, für die der AIDL-Compiler Code generiert. Derzeit 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 (AOSP experimentell).
  • 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, das steuert, ob zusätzlicher Code zum Erfassen von Informationen zur Transaktion generiert werden soll.
  • backend.[cpp|java].vndk.enabled ist das optionale Flag, mit dem diese Schnittstelle Teil des VNDK wird. 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 für ndk_header und cpp_header nützlich.
  • backend.java.sdk_version: Das optionale Flag zum Angeben der SDK-Version, für die die Java-Stub-Bibliothek erstellt wird. Der Standardwert ist "system_current". Sollte nicht festgelegt werden, wenn backend.java.platform_apis „true“ ist.
  • backend.java.platform_apis: Das optionale Flag, das auf true gesetzt werden sollte, wenn die generierten Bibliotheken nicht mit dem SDK, sondern mit der Plattform-API erstellt werden müssen.

Für jede Kombination aus Versionen und aktivierten Back-Ends wird eine Stub-Bibliothek erstellt. Wie Sie auf die spezifische Version der Stub-Bibliothek für ein bestimmtes Back-End verweisen, können Sie unter Benennungsregeln für Module nachlesen.

AIDL-Dateien schreiben

Schnittstellen in stabiler AIDL ähneln herkömmlichen Schnittstellen, mit der Ausnahme, dass sie keine unstrukturierten Pakete verwenden dürfen (da diese instabil sind! Siehe Strukturierter und stabiler AIDL). Der Hauptunterschied in der stabilen AIDL besteht darin, wie Pakete definiert werden. Zuvor wurden Parcelables Forward-Deklarationen gemacht. In stabilen (und daher strukturierten) 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;
}

Derzeit wird eine Standardeinstellung für boolean, char, float, double, byte, int, long und String unterstützt (aber nicht erforderlich). In Android 12 werden auch Standardeinstellungen für benutzerdefinierte Aufzählungen unterstützt. Wenn kein Standardwert angegeben ist, wird ein 0-ähnlicher oder leerer Wert verwendet. Aufzählungen ohne Standardwert werden mit 0 initialisiert, auch wenn kein Null-Enumerator vorhanden ist.

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. Im Folgenden finden Sie Beispiele für Stub-Bibliotheken im Build-System (Android.mk kann auch für Legacy-Moduldefinitionen verwendet werden):

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

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 die Deklaration eines Moduls mit dem Namen foo wird im Build-System auch ein Ziel erstellt, mit dem Sie die API des Moduls verwalten können. Nach der Erstellung fügt foo-Free-api je nach Android-Version eine neue API-Definition unter api_dir oder aidl_api/name hinzu. Außerdem wird eine .hash-Datei hinzugefügt, die beide die neu eingefrorene Version der Benutzeroberfläche darstellt. Außerdem wird das Attribut versions_with_info aktualisiert, um die zusätzliche Version und imports für die Version anzugeben. Im Grunde wird imports in versions_with_info aus dem Feld imports kopiert. Die neueste stabile Version wird jedoch für den Import, die keine explizite Version hat, in imports in versions_with_info angegeben. Sobald das Attribut versions_with_info angegeben ist, führt das Build-System Kompatibilitätsprüfungen zwischen den eingefrorenen Versionen sowie zwischen Top of Tree (ToT) und der neuesten eingefrorenen Version aus.

Außerdem musst du die API-Definition der ToT-Version verwalten. Bei jeder API-Aktualisierung führen Sie foo-update-api aus, um aidl_api/name/current zu aktualisieren, das die API-Definition der ToT-Version enthält.

Um die Stabilität einer Oberfläche zu wahren, können Inhaber neue Elemente hinzufügen:

  • Methoden am Ende einer Schnittstelle (oder Methoden mit explizit definierten neuen Seriennummern)
  • Elemente am Ende eines Pakets (erfordert das Hinzufügen eines Standardwerts für jedes Element)
  • Konstantenwerte
  • In Android 11 können Zähler
  • In Android 12 sind Felder am Ende einer Union (Union)

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

Wenn Sie testen möchten, ob alle Schnittstellen für den Release eingefroren sind, können Sie einen Build mit den folgenden Umgebungsvariablen erstellen:

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

Stabilität von Importen

Das Aktualisieren der Versionen von Importen für eingefrorene Versionen einer Schnittstelle ist auf der stabilen AIDL-Ebene abwärtskompatibel. Zum Aktualisieren müssen jedoch alle Server und Clients aktualisiert werden, die die alte Version der Schnittstelle verwenden. Außerdem kann es bei der Kombination verschiedener Typen von Anwendungen zu Missverständnissen kommen. Im Allgemeinen ist dies bei nur Typen oder gängigen Paketen sicher, da Code bereits geschrieben werden muss, um unbekannte Typen aus IPC-Transaktionen zu verarbeiten.

Im Android-Plattformcode ist android.hardware.graphics.common das beste Beispiel für diese Art von Versionsupgrade.

Versionierte Schnittstellen verwenden

Schnittstellenmethoden

Wenn Sie während der Laufzeit versuchen, neue Methoden auf einem alten Server aufzurufen, erhalten neue Clients je nach Back-End entweder einen Fehler oder eine Ausnahme.

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

Entsprechende Strategien finden Sie unter Versionen abfragen und Standardeinstellungen verwenden.

Parcelables

Wenn zu Paketen neue Felder hinzugefügt werden, werden sie von alten Clients und Servern verworfen. Wenn neue Clients und Server alte Pakete erhalten, werden die Standardwerte für die neuen Felder automatisch ausgefüllt. Dies bedeutet, dass für alle neuen Felder in einem Paket Standardeinstellungen angegeben werden müssen.

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

Enums und Konstanten

Ebenso sollten Clients und Server nicht erkannte konstante Werte und Zähler entweder ablehnen oder ignorieren, da in Zukunft möglicherweise weitere hinzugefügt werden können. Ein Server sollte beispielsweise nicht abgebrochen werden, wenn er einen Zähler empfängt, von dem er nichts weiß. Sie sollte sie entweder ignorieren oder etwas zurückgeben, damit der Client weiß, dass dies 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. Bei der Implementierung wird die Union mit dem neuen Feld nie angezeigt. Der Fehler wird ignoriert, wenn es sich um eine One-Way-Transaktion handelt. Andernfalls lautet der Fehler BAD_VALUE(für das C++- oder NDK-Back-End) oder IllegalArgumentException(für das Java-Back-End). Der Fehler tritt auf, wenn der Client einen Union-Set-Wert auf das neue Feld an einen alten Server sendet oder wenn es ein alter Client ist, der die Union von einem neuen Server empfängt.

Flag-basierte Entwicklung

In der Entwicklung befindliche (nicht eingefrorene) Schnittstellen können auf Release-Geräten nicht verwendet werden, da ihre Abwärtskompatibilität nicht garantiert ist.

AIDL unterstützt das Laufzeit-Fallback für diese nicht eingefrorenen Schnittstellenbibliotheken, damit Code für die neueste nicht eingefrorene Version geschrieben und weiterhin auf Release-Geräten verwendet werden kann. Das abwärtskompatible Verhalten von Clients ähnelt dem vorhandenen Verhalten und mit dem Fallback müssen die Implementierungen diesem Verhalten ebenfalls folgen. Weitere Informationen finden Sie unter Versionierte Oberflächen verwenden.

AIDL-Build-Flag

Das Flag, das dieses Verhalten steuert, ist RELEASE_AIDL_USE_UNFROZEN 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 fixierten Versionen wie ihre letzte eingefrorene Version verhalten. Sie können das Flag für die lokale Entwicklung mit true überschreiben, 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 erwartet und welche Versionen auf beiden Seiten der Anbieterschnittstelle zur Verfügung gestellt werden.

Die meisten Geräte, die nicht Cuttlefish sind, zielen erst auf die neueste Kompatibilitätsmatrix ab, nachdem Schnittstellen eingefroren sind. Daher gibt es keinen Unterschied in den auf RELEASE_AIDL_USE_UNFROZEN basierenden AIDL-Bibliotheken.

Matrizen

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

Wenn Sie beispielsweise eine nicht fixierte 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 fixierte Version 4 verwendet wird, wenn RELEASE_AIDL_USE_UNFROZEN den Wert false hat.

Manifeste

In Android 15 (AOSP experimentell) wird eine Änderung in libvintf eingeführt, um die Manifestdateien zum Zeitpunkt der Erstellung basierend auf dem Wert von RELEASE_AIDL_USE_UNFROZEN zu ändern.

Die Manifeste und Manifestfragmente geben an, welche Version einer Schnittstelle ein Dienst implementiert. Wenn Sie die neueste nicht eingefrorene Version einer Oberfläche 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 widerzuspiegeln. Die Version wird von der nicht eingefrorenen Version N auf die letzte eingefrorene Version N - 1 geändert. Daher müssen Nutzer nicht mehrere Manifeste oder Manifestfragmente für jeden ihrer Dienste verwalten.

Änderungen am HAL-Client

Der HAL-Clientcode muss mit jeder zuvor unterstützten eingefrorenen Version abwärtskompatibel sein. Wenn RELEASE_AIDL_USE_UNFROZEN auf false gesetzt ist, sehen die Dienste immer wie die letzte oder eine frühere Version aus. So wird beispielsweise beim Aufrufen neuer nicht fixierter Methoden UNKNOWN_TRANSACTION zurückgegeben oder neue parcelable-Felder haben ihre Standardwerte. Android-Framework-Clients müssen mit weiteren vorherigen Versionen abwärtskompatibel sein. Dies ist jedoch ein neues Detail für Anbieterclients und Kunden von partnereigenen Benutzeroberflächen.

Änderungen an der HAL-Implementierung

Der größte Unterschied bei der HAL-Entwicklung mit Flag-basierter Entwicklung besteht darin, dass HAL-Implementierungen abwärtskompatibel mit der letzten eingefrorenen Version sind, damit sie funktionieren, wenn RELEASE_AIDL_USE_UNFROZEN den Wert false hat. Die Berücksichtigung der Abwärtskompatibilität bei Implementierungen und Gerätecode ist eine neue Übung. Weitere Informationen finden Sie unter Versionierte Oberflächen verwenden.

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

Beispiel: Eine Schnittstelle hat drei eingefrorene Versionen. Die Benutzeroberfläche wird mit einer neuen Methode aktualisiert. Der Client und der Dienst wurden auf die Verwendung der neuen Bibliothek der Version 4 aktualisiert. Da die V4-Bibliothek auf einer nicht eingefrorenen Version der Schnittstelle basiert, verhält sie sich wie die letzte eingefrorene Version, Version 3, wenn RELEASE_AIDL_USE_UNFROZEN den Wert false hat, und verhindert die Verwendung der neuen Methode.

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

Beim Aufrufen von Methoden für Callbacks muss der Fall korrekt verarbeitet werden, wenn UNKNOWN_TRANSACTION zurückgegeben wird. Clients implementieren möglicherweise zwei verschiedene Versionen eines Callbacks, die auf der Releasekonfiguration basieren. Daher können Sie nicht davon ausgehen, dass der Client die neueste Version sendet. Neue Methoden können dies zurückgeben. Dies ähnelt der Aufrechterhaltung der Abwärtskompatibilität mit Servern, 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 gleich false ist und die Werte neuer Felder, die ein Dienst zu senden versucht, bei Ende des Prozesses gelöscht werden.

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

Die Implementierung erhält niemals einen Aufruf für neue Methoden von Clients, wenn RELEASE_AIDL_USE_UNFROZEN den Wert false hat.

Achten Sie darauf, neue Zähler nur mit der Version zu verwenden, 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. Mit der Unterstützung für die Flag-basierte Versionsverwaltung implementieren Sie jedoch zwei verschiedene Versionen. Möglicherweise möchten Sie die Version der aktuellen Benutzeroberfläche verwenden. Rufen Sie dazu die Schnittstellenversion des aktuellen Objekts ab, z. B. this->getInterfaceVersion(), oder die anderen Methoden für my_ver. Weitere Informationen finden Sie unter Schnittstellenversion des Remote-Objekts abfragen.

Neue stabile VINTF-Oberflächen

Wenn ein neues AIDL-Schnittstellenpaket hinzugefügt wird, gibt es keine letzte eingefrorene Version. Es gibt also kein Verhalten, auf das zurückgegriffen werden kann, wenn RELEASE_AIDL_USE_UNFROZEN den Wert false hat. Verwenden Sie diese Oberflächen nicht. Wenn RELEASE_AIDL_USE_UNFROZEN auf false gesetzt ist, lässt Service Manager nicht zu, dass der Dienst die Schnittstelle registriert und die Clients 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, sodass er dem Gerät nicht bedingt hinzugefügt werden kann, können Sie prüfen, ob der Dienst mit IServiceManager::isDeclared() deklariert wurde. Wenn er deklariert ist und nicht registriert werden konnte, brechen Sie den Prozess ab. Wenn es nicht deklariert ist, schlägt die Registrierung wahrscheinlich fehl.

Sepien als Entwicklungstool

Jedes Jahr, nachdem die VINTF eingefroren ist, passen wir die Framework-Kompatibilitätsmatrix (FCM) target-level und den PRODUCT_SHIPPING_API_LEVEL von Cuttlefish an, sodass sie Geräte widerspiegeln, die mit dem Release im nächsten Jahr auf den Markt kommen. Wir passen target-level und PRODUCT_SHIPPING_API_LEVEL an, um sicherzustellen, dass ein Markteinführungsgerät getestet wurde und die neuen Anforderungen für den Release im nächsten Jahr erfüllt.

Wenn RELEASE_AIDL_USE_UNFROZEN den Wert true hat, wird Cuttlefish für die Entwicklung zukünftiger Android-Releases verwendet. Sie ist auf das FCM-Level des Android-Releases und PRODUCT_SHIPPING_API_LEVEL im nächsten Jahr ausgerichtet, sodass die Anforderungen an die anbieterseitige Software des nächsten Release erfüllt werden müssen.

Wenn RELEASE_AIDL_USE_UNFROZEN den Wert false hat, hat Cuttlefish die vorherigen target-level und PRODUCT_SHIPPING_API_LEVEL, um ein Releasegerät widerzuspiegeln. In Android 14 und niedriger wird diese Differenzierung mit verschiedenen Git-Zweigen erreicht, die die Änderung in FCM-target-level, Versand-API-Level oder anderen Code, der auf den nächsten Release ausgerichtet ist, nicht übernehmen.

Regeln für die Benennung von Modulen

In Android 11 wird für jede Kombination der aktivierten Versionen und Back-Ends 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 (ifacename-version-backend), wobei

  • ifacename: Name des Moduls aidl_interface
  • version ist entweder
    • Vversion-number für die eingefrorenen Versionen
    • Vlatest-frozen-version-number + 1 für die Tip-of-Tree-Version (noch nicht gefroren)
  • backend ist entweder
    • java für das Java-Back-End
    • 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 gedacht,
    • rust für Rust-Back-End.

Angenommen, es gibt ein Modul namens foo mit der neuesten Version 2, das sowohl NDK als auch C++ unterstützt. In diesem Fall generiert AIDL diese 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 Version der ToT-Funktion
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

Im Vergleich zu Android 11

  • foo-backend, das auf die neueste stabile Version verwiesen hat, wird zu foo-V2-backend
  • foo-unstable-backend, das auf die Version der Nutzungsbedingungen verweist, 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 Version der Nutzungsbedingungen: 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 von einer stabilen AIDL-Schnittstelle generierten Modulname immer die Version.

Neue Metaschnittstellenmethoden

Android 10 fügt mehrere Metaschnittstellenmethoden für die stabile AIDL hinzu.

Schnittstellenversion des Remote-Objekts abfragen

Clients können die Version und den Hash der vom Remoteobjekt implementierten Schnittstelle abfragen und die zurückgegebenen Werte mit den Werten der vom Client verwendeten Schnittstelle vergleichen.

Beispiel mit dem Back-End 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();

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

Für die Sprache Java MÜSSEN auf der Remoteseite getInterfaceVersion() und getInterfaceHash() wie unten dargestellt implementieren (super wird anstelle von IFoo verwendet, um Fehler beim Kopieren und Einfügen zu vermeiden. Die Annotation @SuppressWarnings("static") kann je nach javac-Konfiguration erforderlich sein, 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.) vom Client und dem Server gemeinsam genutzt werden. Die Klassen können sich beispielsweise im Bootklassenpfad befinden. Wenn Klassen freigegeben werden, ist der Server auch mit der neuesten Version der Klassen verknüpft, auch wenn dieser möglicherweise mit einer älteren Version der Benutzeroberfläche erstellt wurde. Wenn diese Metaschnittstelle in der gemeinsam genutzten Klasse implementiert ist, wird immer die neueste Version zurückgegeben. Bei der Implementierung der Methode wie oben wird jedoch die Versionsnummer der Schnittstelle in den Code des Servers eingebettet, da IFoo.VERSION eine static final int ist, die beim Verweis eingefügt wird. Dadurch kann die Methode genau die Version zurückgeben, mit der der Server erstellt wurde.

Umgang mit älteren Benutzeroberflächen

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

Mit der stabilen AIDL haben Clients mehr Kontrolle. Clientseitig können Sie eine Standardimplementierung für eine AIDL-Schnittstelle festlegen. Eine Methode in der Standardimplementierung wird nur aufgerufen, wenn die Methode nicht auf der Remoteseite implementiert wurde, weil sie mit einer älteren Version der Schnittstelle erstellt wurde. Da die Standardeinstellungen global festgelegt sind, sollten sie nicht aus potenziell freigegebenen 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 bereitstellen. Methoden, die garantiert auf der Remoteseite implementiert werden (da Sie sicher sind, dass das Remote erstellt wurde, wenn die Methoden in der Beschreibung der AIDL-Schnittstelle aufgeführt sind) müssen nicht in der Standardklasse impl überschrieben werden.

Vorhandene AIDL in strukturierte/stabile AIDL konvertieren

Wenn Sie bereits eine AIDL-Schnittstelle und Code haben, der sie verwendet, konvertieren Sie die folgenden Schritte, um die Schnittstelle in eine stabile AIDL-Schnittstelle zu konvertieren.

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

  2. Konvertieren Sie alle Parcelables in der Schnittstelle in stabile Parcelables (die Schnittstellendateien selbst können unverändert bleiben). Dazu geben Sie die Struktur direkt in AIDL-Dateien aus. Verwaltungsklassen müssen umgeschrieben werden, um diese neuen Typen zu verwenden. Dies kann vor dem Erstellen eines aidl_interface-Pakets erfolgen (siehe unten).

  3. Erstellen Sie wie oben beschrieben ein aidl_interface-Paket, das den Namen des Moduls, seine Abhängigkeiten und alle weiteren benötigten Informationen enthält. Damit sie stabil und nicht nur strukturiert ist, muss sie auch versioniert werden. Weitere Informationen finden Sie unter Schnittstellen versionieren.