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 vs. stabile AIDL

Strukturierte AIDL bezieht sich auf Typen, die ausschließlich in AIDL definiert sind. Eine Parcelable-Deklaration (ein benutzerdefinierter 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 sie mit dem Kern-Build-System erstellt 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 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 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 früheren Versionen der Benutzeroberfläche, die unter api_dir fixiert sind, werden ab Android 11 die versions unter aidl_api/name fixiert. 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 soll 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, sofern unstable nicht angegeben ist. Wenn die Richtlinie nicht konfiguriert ist, entspricht dies einer Schnittstelle mit Stabilität in diesem Kompilierungskontext (also entweder alle Systemelemente, z. B. Elemente in system.img und zugehörigen Partitionen oder alle Anbieterelemente, z. B. 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 diese Option auf true festgelegt ist, erstellt das Build-System weder den API-Dump für die Benutzeroberfläche noch muss dieser aktualisiert werden.
  • frozen: Optionales Flag. Wenn es auf true gesetzt ist, hat sich die Benutzeroberfläche seit der vorherigen Version nicht geändert. Dies ermöglicht mehr Prüfungen während der Build-Erstellung. 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 für ndk_header und cpp_header nützlich.
  • 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;
}

Ein Standardwert wird 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 Null- oder leerer Wert verwendet. Aufzählungen ohne Standardwert werden mit 0 initialisiert, auch wenn kein Null-Enumerator vorhanden ist.

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:

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if your preference 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

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ätsüberprüfungen zwischen eingefrorenen Versionen und zwischen Top of Tree (ToT) und der neuesten eingefrorenen Version durch.

Außerdem musst du 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 Oberfläche zu wahren, können Inhaber neue Elemente 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 die Schnittstelle ändern. Andernfalls besteht die Gefahr von Konflikten mit Änderungen, die ein Inhaber vornimmt.

Wenn Sie prüfen möchten, ob alle Oberflächen für die Veröffentlichung eingefroren sind, können Sie mit den folgenden Umgebungsvariablen einen Build erstellen:

  • AIDL_FROZEN_REL=true m ... – Für den Build müssen alle stabilen AIDL-Schnittstellen, für die kein owner:-Feld angegeben ist, eingefroren werden.
  • AIDL_FROZEN_OWNERS="aosp test" – Für den Build müssen alle stabilen AIDL-Schnittstellen eingefroren sein und 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. Zum Aktualisieren müssen jedoch alle Server und Clients aktualisiert werden, die eine frühere Version der Benutzeroberfläche verwenden. Außerdem können einige Anwendungen verwirrt werden, wenn verschiedene Versionen von Typen gemischt werden. Bei reinen Typen- oder gängigen Paketen ist das in der Regel kein Problem, da Code bereits für die Verarbeitung unbekannter Typen aus IPC-Transaktionen geschrieben werden muss.

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

Versionierte Oberflächen verwenden

Schnittstellenmethoden

Wenn bei der Laufzeit neue Methoden auf einem alten Server aufgerufen werden, erhalten neue Clients je nach Backend entweder einen Fehler oder eine Ausnahme.

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

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

Parcelables

Wenn zu Paketen 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. Bei der Implementierung wird die Union mit dem neuen Feld niemals angezeigt. 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 aus, wenn ein Modul von verschiedenen Versionen derselben aidl_interface-Bibliothek abhängt. Das Modul kann direkt oder indirekt über Abhängigkeiten von diesen Bibliotheken von diesen Bibliotheken abhängen. Diese Fehler zeigen die Abhängigkeitsgrafik vom fehlgeschlagenen Modul zu den in Konflikt stehenden Versionen der aidl_interface-Bibliothek. Alle Abhängigkeiten müssen aktualisiert werden, damit sie dieselbe (in der Regel die neueste) Version dieser Bibliotheken enthalten.

Wenn die Interface-Bibliothek von vielen verschiedenen Modulen verwendet wird, kann es hilfreich sein, cc_defaults, java_defaults und rust_defaults für eine beliebige 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 verschiedene 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, werden dadurch zusätzliche Abhängigkeiten erstellt, für die bestimmte Versionen zusammen verwendet werden müssen. 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 eine aidl_interface beibehalten, die an einem einzigen Ort aktualisiert und von allen aidl_interface-Modulen verwendet werden kann, die diese gemeinsame Schnittstelle importieren möchten.

aidl_interface_defaults {
  name: "android.popular.common-latest-defaults",
  imports: ["android.popular.common-V3"],
  ...
}

aidl_interface {
  name: "android.foo",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

aidl_interface {
  name: "android.bar",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

Flag-basierte Entwicklung

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 für die neueste nicht eingefrorene 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. In der Regel erfolgt die Entwicklung mit einer Konfiguration, 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 Nicht-Cuttlefish-Geräte zielen erst auf die neueste Kompatibilitätsmatrix ab, nachdem Schnittstellen eingefroren wurden. Daher gibt es keinen Unterschied in den AIDL-Bibliotheken basierend auf RELEASE_AIDL_USE_UNFROZEN.

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 eine neue, nicht eingefrorene Version einer Schnittstelle zu einer Kompatibilitätsmatrix hinzugefügt wird, müssen die vorherigen fixierten Versionen für RELEASE_AIDL_USE_UNFROZEN=false beibehalten werden. 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 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 an 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 auf false gesetzt ist, sehen die Dienste immer wie die zuletzt oder eine frühere Version aus. Wenn beispielsweise neue nicht eingefrorene Methoden aufgerufen werden, 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.

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 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 den Wert false hat. 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.

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-Benutzeroberfläche verwendet. Wenn Sie jedoch die flagbasierte Versionsverwaltung 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 Schnittstellenversion des Remote-Objekts 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, kann nicht auf ein anderes Verhalten zurückgegriffen werden. 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 basierend auf dem Wert des Flags RELEASE_AIDL_USE_UNFROZEN im Geräte-Makefile bedingt hinzufügen:

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

Wenn der Dienst Teil eines größeren Prozesses ist und Sie ihn dem Gerät nicht bedingt hinzufügen können, können Sie prüfen, ob der Dienst mit IServiceManager::isDeclared() deklariert ist. Wenn er deklariert ist und nicht registriert werden konnte, brechen Sie den Prozess ab. Wenn sie nicht deklariert ist, wird sie wahrscheinlich nicht registriert.

Cuttlefish 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 eingeführt werden. Wir passen target-level und PRODUCT_SHIPPING_API_LEVEL so an, dass es ein 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 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 für verschiedene Git-Zweige erreicht, die die Änderung auf FCM-target-level, Versand-API-Level oder anderen Code, der auf das nächste Release ausgerichtet ist, nicht übernehmen.

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. 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 Version der Nutzungsbedingungen
    • 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 auf die Version mit den Nutzungsbedingungen verweist, 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-Oberflä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 vom Client verwendeten Schnittstelle vergleichen.

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 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 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 Klassen freigegeben werden, wird 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 Meta-Benutzeroberfläche in der freigegebenen 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. So 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 über eine alte Schnittstelle UNKNOWN_TRANSACTION zurückgegeben.

Mit der stabilen AIDL haben Clients mehr Kontrolle. Auf der Clientseite können Sie für eine AIDL-Schnittstelle eine Standardimplementierung 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 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. Dazu müssen Sie die Struktur direkt in AIDL-Dateien angeben. Verwaltungsklassen müssen neu geschrieben werden, um diese neuen Typen zu verwenden. Das ist auch möglich, bevor du ein aidl_interface-Paket erstellst (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 Schnittstellen versionieren.