Stabiles AIDL

Android 10 bietet Unterstützung für die stabile Android Interface Definition Language (AIDL), eine neue Möglichkeit, die von AIDL-Schnittstellen bereitgestellte Anwendungsprogrammschnittstelle (API)/Anwendungsbinärschnittstelle (ABI) im Auge zu behalten. Stabiles AIDL weist die folgenden wesentlichen Unterschiede zu AIDL auf:

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

Strukturiertes versus stabiles AIDL

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

Stabiles AIDL erfordert strukturiertes AIDL, damit das Build-System und der Compiler erkennen können, ob an Parcelables vorgenommene Änderungen abwärtskompatibel sind. Allerdings sind nicht alle strukturierten Schnittstellen stabil. Um stabil zu sein, darf eine Schnittstelle nur strukturierte Typen verwenden und außerdem die folgenden Versionierungsfunktionen verwenden. Umgekehrt ist eine Schnittstelle nicht stabil, wenn das Core-Build-System zum Erstellen verwendet wird oder wenn unstable:true festgelegt ist.

Definieren einer AIDL-Schnittstelle

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 unter <base_path>/com/acme/Foo.aidl liegen, wobei <base_path> ein beliebiges Verzeichnis sein kann, das mit dem Verzeichnis zusammenhängt, in dem sich Android.bp befindet. Im obigen Beispiel ist <base_path> srcs/aidl .
  • local_include_dir : Der Pfad, von dem aus der Paketname beginnt. Es entspricht dem oben erläuterten <base_path> .
  • imports : Eine Liste der aidl_interface Module, die verwendet werden. Wenn eine Ihrer AIDL-Schnittstellen eine Schnittstelle oder ein Parcelable von einer anderen aidl_interface verwendet, geben Sie hier ihren Namen ein. Dies 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 keine eingefrorenen Versionen einer Schnittstelle vorhanden sind, sollte dies nicht angegeben werden und es werden keine Kompatibilitätsprüfungen durchgeführt. Dieses Feld wurde ab Version 13 durch versions_with_info ersetzt.
  • versions_with_info : Liste von Tupeln, von denen jedes den Namen einer eingefrorenen Version und eine Liste mit Versionsimporten anderer Aidl_Interface-Module enthält, die diese Version des Aidl_Interface importiert hat. 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 automatisch nach versions_with_info migriert, wenn ein Benutzer *-update-api oder *-freeze-api aufruft.
  • stability : Das optionale Flag für das Stabilitätsversprechen dieser Schnittstelle. Unterstützt derzeit nur "vintf" . Wenn dies nicht gesetzt ist, entspricht dies einer Schnittstelle mit Stabilität innerhalb dieses Kompilierungskontexts (eine hier geladene Schnittstelle kann also nur mit zusammen kompilierten Dingen verwendet werden, beispielsweise auf system.img). Wird dieser auf "vintf" gesetzt, entspricht dies einem Stabilitätsversprechen: Die Schnittstelle muss stabil gehalten werden, solange sie genutzt wird.
  • gen_trace : Das optionale Flag zum Aktivieren oder Deaktivieren der Ablaufverfolgung. Ab Android 14 true die Standardeinstellung für die cpp und java Backends.
  • 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, das verwendet wird, um zu markieren, dass diese Schnittstelle nicht stabil sein muss. Wenn dies auf true gesetzt ist, erstellt das Build-System weder den API-Dump für die Schnittstelle, noch erfordert er dessen Aktualisierung.
  • frozen : Das optionale Flag, das, wenn es auf true gesetzt ist, bedeutet, dass die Schnittstelle keine Änderungen seit der vorherigen Version der Schnittstelle aufweist. Dies ermöglicht mehr Überprüfungen während der Erstellung. Wenn der Wert auf false gesetzt ist, bedeutet dies, dass sich die Schnittstelle in der Entwicklung befindet und neue Änderungen aufweist. Wenn Sie also 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 : Diese Flags schalten die einzelnen Backends um, für die der AIDL-Compiler Code generiert. Derzeit werden vier Backends unterstützt: Java, C++, NDK und Rust. Java-, C++- und NDK-Backends sind standardmäßig aktiviert. Wenn eines dieser drei Backends nicht benötigt wird, muss es explizit deaktiviert werden. Rust ist 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, das steuert, ob zusätzlicher Code zum Sammeln von Informationen über die Transaktion generiert werden soll.
  • 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 Abhängigkeiten zu den nativen Bibliotheken hinzu. Dieses Flag ist nützlich bei ndk_header und cpp_header .
  • backend.java.sdk_version : Das optionale Flag zum Angeben der Version des SDK, auf der die Java-Stub-Bibliothek basiert. Der Standardwert ist "system_current" . Dies sollte nicht festgelegt werden, wenn backend.java.platform_apis wahr ist.
  • backend.java.platform_apis : Das optionale Flag, das auf true gesetzt werden sollte, wenn die generierten Bibliotheken auf der Plattform-API und nicht auf dem SDK basieren müssen.

Für jede Kombination der Versionen und der aktivierten Backends wird eine Stub-Bibliothek erstellt. Informationen zum Verweisen auf die spezifische Version der Stub-Bibliothek für ein bestimmtes Backend finden Sie unter Modulbenennungsregeln .

Schreiben von AIDL-Dateien

Schnittstellen in stabiler AIDL ähneln herkömmlichen Schnittstellen, mit der Ausnahme, dass sie keine unstrukturierten Parcelables verwenden dürfen (da diese nicht stabil sind! siehe Strukturierte versus stabile AIDL ). Der Hauptunterschied bei stabiler AIDL besteht darin, wie Parzellen definiert werden. Zuvor wurden Parzellengüter vorwärts deklariert ; In der stabilen (und daher strukturierten) AIDL werden Parcelables-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 ein Standardwert 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 auf 0 initialisiert, auch wenn kein Null-Enumerator vorhanden ist.

Verwendung von Stub-Bibliotheken

Nachdem Sie Stub-Bibliotheken als Abhängigkeit zu Ihrem Modul hinzugefügt haben, können Sie diese in Ihre Dateien einbinden. Hier sind 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

Versionierungsschnittstellen

Durch die Deklaration eines Moduls mit dem Namen foo wird auch ein Ziel im Build-System erstellt, das Sie zum Verwalten der API des Moduls verwenden können. Beim Erstellen fügt foo-freeze-api je nach Android-Version eine neue API-Definition unter api_dir oder aidl_api/ name hinzu und fügt eine .hash Datei hinzu, die beide die neu eingefrorene Version der Schnittstelle darstellen. foo-freeze-api aktualisiert außerdem die Eigenschaft versions_with_info , um die zusätzliche Version und imports für die Version widerzuspiegeln. Grundsätzlich werden imports in versions_with_info aus dem imports kopiert. Für den Import, der keine explizite Version hat, wird jedoch in imports in versions_with_info die neueste stabile Version angegeben. Sobald die Eigenschaft versions_with_info angegeben ist, führt das Build-System Kompatibilitätsprüfungen zwischen eingefrorenen Versionen und auch zwischen Top of Tree (ToT) und der neuesten eingefrorenen Version durch.

Darüber hinaus 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, das die API-Definition der ToT-Version enthält.

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

  • Methoden bis zum Ende einer Schnittstelle (oder Methoden mit explizit definierten neuen Seriennummern)
  • Elemente bis zum Ende einer Parzelle (erfordert das Hinzufügen eines Standardwerts für jedes Element)
  • Konstante Werte
  • In Android 11 Enumeratoren
  • In Android 12 Felder bis zum Ende einer Union

Andere Aktionen sind nicht zulässig und niemand sonst kann eine Schnittstelle ändern (andernfalls besteht die Gefahr einer Kollision mit Änderungen, die ein Eigentümer vornimmt).

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

  • AIDL_FROZEN_REL=true m ... – Build erfordert das Einfrieren aller stabilen AIDL-Schnittstellen, für die kein owner: -Feld angegeben ist.
  • AIDL_FROZEN_OWNERS="aosp test" – Build erfordert, dass alle stabilen AIDL-Schnittstellen eingefroren werden, wobei das Feld „ owner: “ als „aosp“ oder „test“ angegeben ist.

Stabilität der Importe

Das Aktualisieren der Importversionen für eingefrorene Versionen einer Schnittstelle ist auf der stabilen AIDL-Ebene abwärtskompatibel. Um diese zu aktualisieren, müssen jedoch alle Server und Clients aktualisiert werden, die die alte Version der Schnittstelle verwenden, und einige Anwendungen können verwirrt werden, wenn verschiedene Versionen von Typen gemischt werden. Im Allgemeinen ist dies für Nur-Typ-Pakete oder allgemeine Pakete 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 Versionsaktualisierung.

Verwendung versionierter Schnittstellen

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.

  • cpp Backend erhält ::android::UNKNOWN_TRANSACTION .
  • ndk Backend erhält STATUS_UNKNOWN_TRANSACTION .
  • 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 Abfragen von Versionen und Verwenden von Standardwerten .

Pakete

Wenn neue Felder zu Parcelables hinzugefügt werden, werden diese von alten Clients und Servern gelöscht. Wenn neue Clients und Server alte Parcelables 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 erwarten, dass Server die neuen Felder verwenden, es sei denn, sie wissen, dass der Server die Version implementiert, in der das Feld definiert ist (siehe Abfragen von Versionen ).

Aufzählungen und Konstanten

Ebenso sollten Clients und Server gegebenenfalls nicht erkannte konstante Werte und Enumeratoren entweder ablehnen oder ignorieren, da in Zukunft möglicherweise weitere hinzugefügt werden. Beispielsweise sollte ein Server nicht abbrechen, wenn er einen Enumerator empfängt, den er nicht kennt. Es sollte es entweder ignorieren oder etwas zurückgeben, damit der Client weiß, dass es 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 es nie zu einer Vereinigung mit dem neuen Feld kommen. Der Fehler wird ignoriert, wenn es sich um eine einseitige Transaktion 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 einen Union-Satz für das neue Feld an einen alten Server sendet oder wenn es sich um einen alten Client handelt, der die Union von einem neuen Server empfängt.

Flaggenbasierte Entwicklung

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

AIDL unterstützt Laufzeit-Fallback für diese nicht eingefrorenen Schnittstellenbibliotheken, damit Code mit der neuesten nicht eingefrorenen Version geschrieben und weiterhin auf Release-Geräten verwendet werden kann. Das abwärtskompatible Verhalten von Clients ähnelt dem bestehenden Verhalten und mit dem Fallback müssen auch die Implementierungen diesen Verhaltensweisen folgen. Siehe Verwenden versionierter Schnittstellen .

AIDL-Build-Flag

Das Flag, das dieses Verhalten steuert, ist RELEASE_AIDL_USE_UNFROZEN , definiert in build/release/build_flags.bzl . 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 überschreiben, müssen es jedoch vor der Veröffentlichung auf false zurücksetzen. Normalerweise erfolgt die Entwicklung mit einer Konfiguration, deren Flag auf true gesetzt ist.

Kompatibilitätsmatrix und Manifeste

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

Die meisten Nicht-Cuttlefish-Geräte zielen erst auf die neueste Kompatibilitätsmatrix ab, nachdem die Schnittstellen eingefroren wurden, sodass es keinen Unterschied in den AIDL-Bibliotheken gibt, die auf RELEASE_AIDL_USE_UNFROZEN basieren.

Matrizen

Partnereigene Schnittstellen werden zu gerätespezifischen oder produktspezifischen Kompatibilitätsmatrizen hinzugefügt, auf die das Gerät während der Entwicklung abzielt. 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 erhalten bleiben. Sie können dies bewältigen, indem Sie unterschiedliche Kompatibilitätsmatrixdateien für unterschiedliche 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 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.

Manifestiert

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

Die Manifeste und die Manifestfragmente deklarieren, welche Version einer Schnittstelle ein Dienst implementiert. Wenn Sie die neueste nicht eingefrorene Version einer Schnittstelle verwenden, muss das Manifest aktualisiert werden, um diese neue Version widerzuspiegeln. Wenn RELEASE_AIDL_USE_UNFROZEN=false ist, 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 zur letzten eingefrorenen Version N - 1 geändert. Daher müssen Benutzer nicht mehrere Manifeste oder Manifestfragmente für jeden ihrer Dienste verwalten.

HAL-Client-Änderungen

Der HAL-Clientcode muss mit jeder zuvor unterstützten eingefrorenen Version abwärtskompatibel sein. Wenn RELEASE_AIDL_USE_UNFROZEN false ist, sehen die Dienste immer wie die letzte eingefrorene Version oder früher aus (der Aufruf neuer nicht eingefrorener Methoden gibt beispielsweise UNKNOWN_TRANSACTION zurück oder neue parcelable Felder haben ihre Standardwerte). Android-Framework-Clients müssen mit weiteren Vorgängerversionen abwärtskompatibel sein. Dies ist jedoch ein neues Detail für Anbieter-Clients und Clients von Schnittstellen im Besitz von Partnern.

Änderungen der HAL-Implementierung

Der größte Unterschied zwischen der HAL-Entwicklung und der Flag-basierten 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 Berücksichtigung der Abwärtskompatibilität in Implementierungen und Gerätecode ist eine neue Aufgabe. Siehe Verwenden versionierter Schnittstellen .

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 nun 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. Sowohl der Client als auch der Dienst werden aktualisiert, um die neue Bibliothek der Version 4 zu verwenden. 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 false ist, und verhindert die Verwendung der neuen Methode.

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

Wenn Sie Methoden für Rückrufe aufrufen, müssen Sie den Fall ordnungsgemäß behandeln, wenn UNKNOWN_TRANSACTION zurückgegeben wird. Clients implementieren möglicherweise zwei verschiedene Versionen eines Rückrufs basierend auf der Release-Konfiguration. Sie können also nicht davon ausgehen, dass der Client die neueste Version sendet, und neue Methoden geben dies möglicherweise zurück. Dies ähnelt der Art und Weise, wie stabile AIDL-Clients die Abwärtskompatibilität mit Servern aufrechterhalten, die unter Verwenden versionierter Schnittstellen 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 auf false gesetzt ist und die Werte neuer Felder, die ein Dienst zu senden versucht, beim Verlassen des Prozesses verworfen werden.

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

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

Achten Sie darauf, neue Enumeratoren 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. Bei der Flag-basierten Versionierungsunterstützung implementieren Sie jedoch zwei verschiedene Versionen, sodass Sie möglicherweise die Version der aktuellen Schnittstelle abrufen möchten. Sie können dies tun, indem Sie die Schnittstellenversion des aktuellen Objekts abrufen, zum Beispiel this->getInterfaceVersion() oder die anderen Methoden für my_ver . Weitere Informationen finden Sie unter Abfragen der Schnittstellenversion des Remote-Objekts .

Neue stabile VINTF-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 false ist. Benutzen Sie diese Schnittstellen nicht. Wenn RELEASE_AIDL_USE_UNFROZEN den Wert false hat, lässt Service Manager nicht zu, dass der Dienst die Schnittstelle registriert, und die Clients finden sie nicht.

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

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 zum Gerät hinzufügen können, können Sie überprüfen, ob der Dienst mit IServiceManager::isDeclared() deklariert ist. Wenn es deklariert wurde und die Registrierung fehlgeschlagen ist, brechen Sie den Vorgang ab. Wenn es nicht deklariert ist, ist zu erwarten, dass die Registrierung fehlschlägt.

Tintenfisch als Entwicklungswerkzeug

Jedes Jahr, nachdem die VINTF eingefroren wurde, passen wir die target-level der Framework-Kompatibilitätsmatrix (FCM) und die PRODUCT_SHIPPING_API_LEVEL von Cuttlefish an, damit sie 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 an, um sicherzustellen, dass 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 zielt auf die FCM-Stufe und PRODUCT_SHIPPING_API_LEVEL der Android-Version des nächsten Jahres ab und erfordert, dass die Vendor Software Requirements (VSR) der nächsten Version erfüllt werden.

Wenn RELEASE_AIDL_USE_UNFROZEN false ist, verfügt Cuttlefish über die vorherige target-level und PRODUCT_SHIPPING_API_LEVEL , um ein Freigabegerät widerzuspiegeln. In Android 14 und niedriger würde diese Differenzierung durch verschiedene Git-Zweige erreicht werden, die die Änderung an der FCM- target-level , der Versand-API-Ebene oder einem anderen Code, der auf die nächste Version abzielt, nicht übernehmen.

Benennungsregeln für Module

In Android 11 wird für jede Kombination der Versionen und aktivierten Backends automatisch ein Stub-Bibliotheksmodul erstellt. Um zum Verknüpfen auf ein bestimmtes Stub-Bibliotheksmodul zu verweisen, verwenden Sie nicht den Namen des Moduls aidl_interface , sondern den Namen des Stub-Bibliotheksmoduls, nämlich ifacename - version - backend , wobei

  • ifacename : Name des Moduls aidl_interface .
  • version ist eine von
    • V version-number für die eingefrorenen Versionen
    • V latest-frozen-version-number + 1 für die (noch einzufrierende) Spitzenversion
  • backend ist eines von beiden
    • java für das Java-Backend,
    • 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.
    • rust für das Rust-Backend.

Angenommen, es gibt ein Modul mit dem Namen foo , dessen neueste Version 2 ist und 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 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 zu foo- V2 - backend
  • foo-unstable- backend , das sich auf die ToT-Version bezog, wird zu foo- V3 - backend

Die Namen der Ausgabedateien sind immer dieselben wie die Modulnamen.

  • 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

Beachten Sie, dass der AIDL-Compiler weder ein unstable Versionsmodul noch ein nicht versioniertes Modul für eine stabile AIDL-Schnittstelle erstellt. Ab Android 12 enthält der aus einer stabilen AIDL-Schnittstelle generierte Modulname immer seine Version.

Neue Metaschnittstellenmethoden

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

Abfrage der Schnittstellenversion des Remote-Objekts

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

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/Einfügen zu vermeiden. Die Annotation @SuppressWarnings("static") kann je nach Bedarf zum Deaktivieren von Warnungen erforderlich sein die javac Konfiguration):

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

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

Dies liegt daran, dass die generierten Klassen ( IFoo , IFoo.Stub usw.) zwischen Client und Server gemeinsam genutzt werden (die Klassen können sich beispielsweise im Boot-Klassenpfad befinden). Wenn Klassen gemeinsam genutzt werden, ist der Server auch mit der neuesten Version der Klassen verknüpft, auch wenn er möglicherweise mit einer älteren Version der Schnittstelle erstellt wurde. Wenn diese Metaschnittstelle in der gemeinsam genutzten Klasse implementiert ist, gibt sie immer die neueste Version zurück. Durch die Implementierung der Methode wie oben wird jedoch die Versionsnummer der Schnittstelle in den Code des Servers eingebettet (da IFoo.VERSION ein static final int ist, das bei Referenzierung inline wird) und somit kann die Methode die genaue Version zurückgeben, die der Server erstellt hat mit.

Umgang mit älteren Schnittstellen

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 gibt der Aufruf einer Methode auf einer alten Schnittstelle UNKNOWN_TRANSACTION zurück.

Mit stabilem AIDL haben Kunden 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 nicht auf der Remote-Seite 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++ 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 bereitstellen. Methoden, die garantiert auf der Remote-Seite implementiert werden (da Sie sicher sind, dass die Remote-Seite erstellt wurde, als die Methoden in der AIDL-Schnittstellenbeschreibung enthalten waren), müssen in der Standard- impl Klasse nicht überschrieben werden.

Konvertieren bestehender AIDL in strukturiertes/stabiles AIDL

Wenn Sie bereits über eine AIDL-Schnittstelle und Code verfügen, der diese verwendet, führen Sie die folgenden Schritte aus, um die Schnittstelle in eine stabile AIDL-Schnittstelle zu konvertieren.

  1. Identifizieren Sie alle Abhängigkeiten Ihrer Schnittstelle. Bestimmen 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. Wandeln Sie alle Parcelables in Ihrer Schnittstelle in stabile Parcelables um (die Schnittstellendateien selbst können unverändert bleiben). Dies erreichen Sie, indem Sie ihre Struktur direkt in AIDL-Dateien ausdrücken. Verwaltungsklassen müssen neu geschrieben werden, um diese neuen Typen verwenden zu können. Dies kann erfolgen, bevor Sie ein aidl_interface -Paket erstellen (unten).

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