Stabiles AIDL

Android 10 fügt Unterstützung für die stabile Android Interface Definition Language (AIDL) hinzu, eine neue Möglichkeit, die von AIDL-Schnittstellen bereitgestellte Application Program Interface (API)/Application Binary Interface (ABI) zu verfolgen. Stable AIDL hat die folgenden Hauptunterschiede zu 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 basierend auf ihrer AIDL-Definition automatisch erstellt und automatisch zusammengestellt und entpackt.
  • Schnittstellen können als stabil (abwärtskompatibel) deklariert werden. In diesem Fall wird ihre API in einer Datei neben der AIDL-Schnittstelle nachverfolgt und versioniert.

Definieren einer AIDL-Schnittstelle

Eine Definition von aidl_interface sieht folgendermaßen aus:

aidl_interface {
    name: "my-aidl",
    srcs: ["srcs/aidl/**/*.aidl"],
    local_include_dir: "srcs/aidl",
    imports: ["other-aidl"],
    versions: ["1", "2"],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
    },

}
  • name : Der Name des AIDL-Schnittstellenmoduls, das eine AIDL-Schnittstelle eindeutig identifiziert.
  • srcs : Die Liste der AIDL-Quelldateien, die die Schnittstelle bilden. 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 , wobei <base_path> ein beliebiges Verzeichnis sein kann, das sich auf das Verzeichnis bezieht, in dem sich Android.bp befindet. Im obigen Beispiel ist <base_path> srcs/aidl .
  • local_include_dir : Der Pfad, ab dem der Paketname beginnt. Er entspricht dem oben erläuterten <base_path> .
  • imports : Eine Liste von aidl_interface Modulen, die dies verwendet. Wenn eine Ihrer AIDL-Schnittstellen eine Schnittstelle oder ein Paket von einer anderen aidl_interface , geben Sie hier ihren Namen ein. Dies kann der Name selbst 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 sind die versions unter aidl_api/ name eingefroren. Wenn es keine eingefrorenen Versionen einer Schnittstelle gibt, sollte dies nicht angegeben werden, und es finden keine Kompatibilitätsprüfungen statt.
  • stability : Das optionale Flag für das Stabilitätsversprechen dieser Schnittstelle. Derzeit wird nur "vintf" unterstützt. 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, zum Beispiel auf system.img). Ist diese auf "vintf" , entspricht dies einem Stabilitätsversprechen: Die Schnittstelle muss so lange stabil gehalten werden, wie sie genutzt wird.
  • gen_trace : Das optionale Flag zum Ein- oder Ausschalten der Ablaufverfolgung. Standard ist false .
  • 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 es, dass sie aktualisiert wird.
  • backend.<type>.enabled : Diese Flags schalten die einzelnen Backends um, für die der AIDL-Compiler Code generiert. Derzeit werden drei Backends unterstützt: java , cpp und ndk . Die Backends sind alle standardmäßig aktiviert. Wenn ein bestimmtes Backend nicht benötigt wird, muss es explizit deaktiviert werden.
  • 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. Standard ist false .
  • backend.java.platform_apis : Das optionale Flag, das steuert, ob die Java-Stub-Bibliothek für die privaten APIs der Plattform erstellt wird. Dies sollte auf "true" gesetzt werden, wenn die stability auf "vintf" gesetzt ist.
  • backend.java.sdk_version : Das optionale Flag zum Angeben der Version des SDK, für das die Java-Stub-Bibliothek erstellt wird. Der Standardwert ist "system_current" . 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 für die Plattform-API und nicht für das SDK erstellt werden müssen.

Für jede Kombination der versions und der aktivierten Backends wird eine Stub-Bibliothek erstellt. Siehe Benennungsregeln für Module, um zu erfahren, wie auf die spezifische Version der Stub-Bibliothek für ein bestimmtes Backend verwiesen wird.

Schreiben von AIDL-Dateien

Schnittstellen in stabilem AIDL ähneln traditionellen Schnittstellen, mit der Ausnahme, dass sie keine unstrukturierten Paketdateien verwenden dürfen (weil diese nicht stabil sind!). Der Hauptunterschied in Stable AIDL besteht darin, wie Parcelables definiert werden. Zuvor wurden Paketsendungen vorwärts deklariert ; In Stable AIDL werden die Felder und Variablen von Paketen explizit definiert.

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

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

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

Verwenden von Stub-Bibliotheken

Nachdem Sie Stub-Bibliotheken als Abhängigkeit zu Ihrem Modul hinzugefügt haben, können Sie sie in Ihre Dateien aufnehmen. 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"],
    ...
}

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

Versionierungsschnittstellen

Durch das Deklarieren eines Moduls mit dem Namen foo wird auch ein Ziel im Build-System erstellt, mit dem Sie die API des Moduls verwalten können. Beim Erstellen 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. Wenn Sie dies erstellen, wird auch die versions aktualisiert, um die zusätzliche Version widerzuspiegeln. Sobald die Eigenschaft versions angegeben ist, führt das Buildsystem 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 aufrechtzuerhalten, können Eigentümer Folgendes hinzufügen:

  • Methoden am Ende einer Schnittstelle (oder Methoden mit explizit definierten neuen Seriennummern)
  • Elemente bis zum Ende eines parzellierbaren Elements (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 riskieren sie eine Kollision mit Änderungen, die ein Eigentümer vornimmt).

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

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

Verwenden von versionierten Schnittstellen

Schnittstellenmethoden

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

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

Für Strategien, um damit umzugehen, siehe Abfragen von Versionen und Verwenden von Standardwerten .

Paketsendungen

Wenn neue Felder zu Parcelables hinzugefügt werden, werden sie von alten Clients und Servern gelöscht. Wenn neue Clients und Server alte Parcelables erhalten, werden die Standardwerte für neue Felder automatisch ausgefüllt. Das bedeutet, dass Standardwerte für alle neuen Felder in einem Parcelable 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 Versionen abfragen ).

Aufzählungen und Konstanten

Ebenso sollten Clients und Server nicht erkannte konstante Werte und Enumeratoren entweder zurückweisen oder ignorieren, da in Zukunft möglicherweise weitere hinzugefügt werden. Beispielsweise sollte ein Server nicht abbrechen, wenn er einen Enumerator empfängt, von dem er nichts weiß. 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 nichts über das Feld weiß. Die Implementierung wird niemals die Vereinigung mit dem neuen Feld sehen. Der Fehler wird ignoriert, wenn es sich um eine Einwegtransaktion handelt; andernfalls ist der Fehler BAD_VALUE (für das C++- oder NDK-Backend) oder IllegalArgumentException (für das Java-Backend). Der Fehler wird empfangen, wenn der Client eine Vereinigungsmenge an das neue Feld an einen alten Server sendet oder wenn es sich um einen alten Client handelt, der die Vereinigung von einem neuen Server empfängt.

Benennungsregeln für Module

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

  • ifacename : Name des aidl_interface -Moduls
  • version ist entweder
    • V version-number für die eingefrorenen Versionen
    • V latest-frozen-version-number + 1 für die Tip-of-Tree (noch nicht eingefrorene) Version
  • backend ist entweder
    • java für das Java-Backend,
    • cpp für das C++-Backend,
    • ndk oder ndk_platform für das NDK-Backend. Ersteres ist für Apps und letzteres für die Plattformnutzung.

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

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

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 die gleichen wie die Modulnamen.

  • Basierend auf Version 1: foo-V1-(cpp|ndk|ndk_platform).so
  • Basierend auf Version 2: foo-V2-(cpp|ndk|ndk_platform).so
  • Basierend auf der ToT-Version: foo-V3-(cpp|ndk|ndk_platform).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 dessen Version.

Neue Meta-Interface-Methoden

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

Abfragen 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 (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:

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

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

Dies liegt daran, dass die generierten Klassen ( IFoo , IFoo.Stub usw.) von Client und Server gemeinsam genutzt werden (die Klassen können sich beispielsweise im Boot-Klassenpfad befinden). Wenn Klassen gemeinsam genutzt werden, wird der Server auch mit der neuesten Version der Klassen verknüpft, obwohl er möglicherweise mit einer älteren Version der Schnittstelle erstellt wurde. Wenn 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 die Versionsnummer der Schnittstelle jedoch in den Code des Servers eingebettet (weil IFoo.VERSION ein static final int ist, der beim Verweis inline ist), und daher 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 auf 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 aus möglicherweise gemeinsam genutzten Kontexten verwendet werden.

Beispiel in C++ in Android T (AOSP experimentell) 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 (weil Sie sicher sind, dass die Remote erstellt wurde, als die Methoden in der AIDL-Schnittstellenbeschreibung enthalten waren), müssen nicht in der Standardklasse impl überschrieben werden.

Konvertieren bestehender AIDL in strukturierte/stabile AIDL

Wenn Sie über eine vorhandene 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 stabiler AIDL definiert ist. Wenn nicht definiert, muss das Paket konvertiert werden.

  2. Konvertieren Sie alle Parcelables in Ihrer Schnittstelle in stabile Parcelables (die Schnittstellendateien selbst können unverändert bleiben). Tun Sie dies, indem Sie ihre Struktur direkt in AIDL-Dateien ausdrücken. Verwaltungsklassen müssen umgeschrieben werden, um diese neuen Typen zu verwenden. Dies kann getan werden, bevor Sie ein aidl_interface -Paket erstellen (unten).

  3. Erstellen Sie ein aidl_interface -Paket (wie oben beschrieben), das den Namen Ihres Moduls, seine Abhängigkeiten und alle anderen Informationen enthält, die Sie benötigen. Um es zu stabilisieren (nicht nur zu strukturieren), muss es auch versioniert werden. Weitere Informationen finden Sie unter Versionierungsschnittstellen .