Android 10 unterstützt die stabile Android Interface Definition Language (AIDL). Das ist eine neue Möglichkeit, die API (Application Program Interface) und die ABI (Application Binary Interface) zu verwalten, die von AIDL-Schnittstellen bereitgestellt werden. Stable AIDL funktioniert genau wie AIDL, aber das Build-System überwacht die Schnittstellenkompatibilität und es gibt Einschränkungen für Ihre Möglichkeiten:
- Schnittstellen werden im Build-System mit
aidl_interfaces
definiert. - Oberflächen dürfen nur strukturierte Daten enthalten. Parcelables, die die bevorzugten Typen darstellen, werden automatisch basierend auf ihrer AIDL-Definition erstellt und automatisch gemarshallt und unmarshallt.
- Schnittstellen können als stabil (abwärtskompatibel) deklariert werden. In diesem Fall wird die API in einer Datei neben der AIDL-Schnittstelle erfasst und versioniert.
Strukturierte und stabile AIDL
Strukturierte AIDL-Dateien beziehen sich auf Typen, die ausschließlich in AIDL definiert sind. Eine Deklaration für Parcelable (ein benutzerdefiniertes Parcelable) ist beispielsweise keine strukturierte AIDL. Parcelable-Objekte mit ihren in AIDL definierten Feldern werden als strukturierte Parcelable-Objekte bezeichnet.
Für stabile AIDL-Dateien ist strukturiertes AIDL erforderlich, damit das Buildsystem und der Compiler erkennen können, ob Änderungen an Parcelable-Objekten abwärtskompatibel sind.
Nicht alle strukturierten Oberflächen sind jedoch stabil. Damit eine Schnittstelle stabil ist, müssen nur strukturierte Typen und die folgenden Versionierungsfunktionen verwendet werden. Umgekehrt ist eine Benutzeroberfläche nicht stabil, wenn das Kern-Buildsystem zum Erstellen verwendet wird oder unstable:true
festgelegt ist.
AIDL-Schnittstelle definieren
Eine Definition von aidl_interface
sieht so aus:
aidl_interface {
name: "my-aidl",
srcs: ["srcs/aidl/**/*.aidl"],
local_include_dir: "srcs/aidl",
imports: ["other-aidl"],
versions_with_info: [
{
version: "1",
imports: ["other-aidl-V1"],
},
{
version: "2",
imports: ["other-aidl-V3"],
}
],
stability: "vintf",
backend: {
java: {
enabled: true,
platform_apis: true,
},
cpp: {
enabled: true,
},
ndk: {
enabled: true,
},
rust: {
enabled: true,
},
},
}
name
: Der Name des AIDL-Schnittstellenmoduls, das eine AIDL-Schnittstelle eindeutig identifiziert.srcs
: Die Liste der AIDL-Quelldateien, aus denen die Benutzeroberfläche besteht. Der Pfad für einen AIDL-TypFoo
, der in einem Paketcom.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 sichAndroid.bp
befindet, in Beziehung steht. Im vorherigen Beispiel ist<base_path>
srcs/aidl
.local_include_dir
: Der Pfad, ab dem der Paketname beginnt. Sie entspricht<base_path>
, wie oben beschrieben.imports
: Eine Liste der verwendetenaidl_interface
-Module. Wenn eine Ihrer AIDL-Schnittstellen eine Schnittstelle oder ein Parcelable aus einer anderenaidl_interface
verwendet, geben Sie hier den Namen an. Dies kann der Name allein sein, um sich auf die neueste Version zu beziehen, oder der Name mit dem Versionssuffix (z. B.-V1
), um sich auf eine bestimmte Version zu beziehen. Die Angabe einer Version wird seit Android 12 unterstützt.versions
: Die vorherigen Versionen der Benutzeroberfläche, die unterapi_dir
eingefroren sind. Ab Android 11 sind dieversions
unteraidl_api/name
eingefroren. Wenn es keine eingefrorenen Versionen einer Benutzeroberfläche gibt, sollte dies nicht angegeben werden. Es werden dann keine Kompatibilitätstests durchgeführt. Dieses Feld wurde für Android 13 und höher durchversions_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 unteraidl_api/IFACE/V
. Dieses Feld wurde in Android 13 eingeführt und sollte nicht direkt inAndroid.bp
geändert werden. Das Feld wird durch Aufrufen von*-update-api
oder*-freeze-api
hinzugefügt oder aktualisiert. Außerdem werdenversions
-Felder automatisch zuversions_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. Wennstability
nicht festgelegt ist, prüft das Buildsystem, ob die Benutzeroberfläche abwärtskompatibel ist, es sei denn,unstable
ist angegeben. Wenn „unset“ festgelegt ist, entspricht dies einer stabilen Schnittstelle in diesem Kompilierungskontext (d. h. entweder alle Systemelemente, z. B. Elemente insystem.img
und zugehörige Partitionen, oder alle Anbieterelemente, z. B. Elemente invendor.img
und zugehörige Partitionen). Wennstability
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 Standardwerttrue
für diecpp
- undjava
-Backends.host_supported
: Optionales Flag, das die generierten Bibliotheken für die Hostumgebung verfügbar macht, wenn es auftrue
gesetzt ist.unstable
: Mit diesem optionalen Flag wird angegeben, dass diese Schnittstelle nicht stabil sein muss. Wenn dieser Wert auftrue
festgelegt ist, erstellt das Build-System weder den API-Dump für die Benutzeroberfläche noch muss er aktualisiert werden.frozen
: Optionales Flag. Wenn es auftrue
gesetzt ist, hat sich die Benutzeroberfläche seit der vorherigen Version nicht geändert. Dadurch sind mehr Buildzeitprüfungen möglich. Wenn der Wert auffalse
festgelegt ist, befindet sich die Benutzeroberfläche in der Entwicklungsphase und es gibt neue Änderungen. Wenn Siefoo-freeze-api
ausführen, wird eine neue Version generiert und der Wert wird automatisch intrue
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 istfalse
.backend.[cpp|ndk].additional_shared_libraries
: Dieses Flag wurde in Android 14 eingeführt und fügt den nativen Bibliotheken Abhängigkeiten hinzu. Dieses Flag ist nützlich fürndk_header
undcpp_header
.backend.java.sdk_version
: Optionales Flag zum Angeben der Version des SDK, gegen die die Java-Stub-Bibliothek erstellt wird. Der Standardwert ist"system_current"
. Dieser Wert sollte nicht festgelegt werden, wennbackend.java.platform_apis
=true
ist.backend.java.platform_apis
: Optionales Flag, das auftrue
gesetzt werden sollte, wenn die generierten Bibliotheken nicht auf dem SDK, sondern auf der Plattform-API basieren sollen.
Für jede Kombination aus Versionen und aktivierten Backends wird eine Stub-Bibliothek erstellt. Weitere Informationen dazu, wie Sie auf die spezifische Version der Stub-Bibliothek für ein bestimmtes Backend verweisen, finden Sie unter Regeln für die Modulbenennung.
AIDL-Dateien schreiben
Schnittstellen in stabiler AIDL ähneln herkömmlichen Schnittstellen, mit der Ausnahme, dass keine unstrukturierten Parcelables verwendet werden dürfen, da diese nicht stabil sind (siehe Strukturiertes und stabiles AIDL). Der Hauptunterschied bei stabiler AIDL besteht in der Definition von Parcelables. Bisher wurden Parcelable-Objekte vordeklariert. In stabiler (und daher strukturierter) AIDL werden Parcelable-Felder und ‑Variablen explizit definiert.
// in a file like 'some/package/Thing.aidl'
package some.package;
parcelable SubThing {
String a = "foo";
int b;
}
Für boolean
, char
, float
, double
, byte
, int
, long
und String
wird eine Standardeinstellung unterstützt (ist aber nicht erforderlich). In Android 12 werden auch Standardwerte für benutzerdefinierte Aufzählungen unterstützt. Wenn kein Standardwert angegeben ist, wird ein Wert verwendet, der einer 0 ähnelt oder leer ist.
Aufzählungen ohne Standardwert werden mit 0 initialisiert, auch wenn es keinen Nullenumerator gibt.
Stub-Bibliotheken verwenden
Nachdem Sie Ihrem Modul Stub-Bibliotheken als Abhängigkeit hinzugefügt haben, können Sie sie in Ihre Dateien einbinden. Hier sind Beispiele für Stub-Bibliotheken im Build-System. Android.mk
kann auch für ältere Moduldefinitionen verwendet werden.
In diesen Beispielen ist die Version nicht vorhanden, sodass eine instabile Benutzeroberfläche verwendet wird. Namen für Benutzeroberflächen mit Versionen enthalten jedoch zusätzliche Informationen. Weitere Informationen finden Sie unter Benutzeroberflächen versionieren.
cc_... {
name: ...,
// use `shared_libs:` to load your library and its transitive dependencies
// dynamically
shared_libs: ["my-module-name-cpp"],
// use `static_libs:` to include the library in this binary and drop
// transitive dependencies
static_libs: ["my-module-name-cpp"],
...
}
# or
java_... {
name: ...,
// use `static_libs:` to add all jars and classes to this jar
static_libs: ["my-module-name-java"],
// use `libs:` to make these classes available during build time, but
// not add them to the jar, in case the classes are already present on the
// boot classpath (such as if it's in framework.jar) or another jar.
libs: ["my-module-name-java"],
// use `srcs:` with `-java-sources` if you want to add classes in this
// library jar directly, but you get transitive dependencies from
// somewhere else, such as the boot classpath or another jar.
srcs: ["my-module-name-java-source", ...],
...
}
# or
rust_... {
name: ...,
rustlibs: ["my-module-name-rust"],
...
}
Beispiel in C++:
#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
// use just like traditional AIDL
Beispiel in Java:
import some.package.IFoo;
import some.package.Thing;
...
// use just like traditional AIDL
Beispiel in Rust:
use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
// use just like traditional AIDL
Versionierungsschnittstellen
Wenn Sie ein Modul mit dem Namen foo deklarieren, wird im Build-System auch ein Ziel erstellt, mit dem Sie die API des Moduls verwalten können. Beim Erstellen fügt foo-freeze-api je nach Android-Version eine neue API-Definition unter api_dir
oder aidl_api/name
und eine .hash
-Datei hinzu. Beide stellen die neu eingefrorene Version der Benutzeroberfläche dar. foo-freeze-api aktualisiert auch die Eigenschaft versions_with_info
, um die zusätzliche Version und imports
für die Version widerzuspiegeln. Im Grunde wird imports
in versions_with_info
aus dem Feld imports
kopiert. Die neueste stabile Version ist jedoch in imports
in versions_with_info
für den Import angegeben, der keine explizite Version hat.
Nachdem die versions_with_info
-Eigenschaft angegeben wurde, führt das Buildsystem Kompatibilitätsprüfungen zwischen eingefrorenen Versionen und auch zwischen Top of Tree (ToT) und der neuesten eingefrorenen Version durch.
Außerdem müssen Sie die API-Definition der ToT-Version verwalten. Jedes Mal, wenn eine API aktualisiert wird, führen Sie foo-update-api aus, um aidl_api/name/current
zu aktualisieren, die die API-Definition der ToT-Version enthält.
Um die Stabilität einer Benutzeroberfläche zu erhalten, können Inhaber Folgendes hinzufügen:
- Methoden am Ende einer Schnittstelle (oder Methoden mit explizit definierten neuen Sequenzen)
- Elemente am Ende eines Parcelable (für jedes Element muss ein Standard hinzugefügt werden)
- Konstante Werte
- Unter Android 11 haben Zähler
- In Android 12: Felder am Ende einer Union
Andere Aktionen sind nicht zulässig und niemand sonst kann eine Benutzeroberfläche ändern. Andernfalls besteht die Gefahr von Konflikten mit Änderungen, die ein Inhaber vornimmt.
Wenn Sie testen möchten, ob alle Oberflächen für die Veröffentlichung eingefroren sind, können Sie den Build mit den folgenden Umgebungsvariablen erstellen:
AIDL_FROZEN_REL=true m ...
– Für den Build müssen alle stabilen AIDL-Schnittstellen, für die keinowner:
-Feld angegeben ist, eingefroren werden.AIDL_FROZEN_OWNERS="aosp test"
– Für die Build-Umgebung müssen alle stabilen AIDL-Schnittstellen eingefroren sein. Das Feldowner:
muss als „aosp“ oder „test“ angegeben sein.
Stabilität von Importen
Das Aktualisieren der Versionen von Importen für eingefrorene Versionen einer Schnittstelle ist auf der Stable-AIDL-Ebene abwärtskompatibel. Das Aktualisieren dieser erfordert jedoch das Aktualisieren aller Server und Clients, die eine ältere Version der Benutzeroberfläche verwenden. Außerdem kann es bei einigen Apps zu Problemen kommen, wenn verschiedene Versionen von Typen verwendet werden. Bei reinen Typen- oder gängigen Paketen ist das in der Regel kein Problem, da Code bereits zum Umgang mit unbekannten Typen aus IPC-Transaktionen geschrieben werden muss.
Im Android-Plattformcode ist android.hardware.graphics.common
das größte Beispiel für diese Art von Versionsaktualisierung.
Versionierte Oberflächen verwenden
Schnittstellenmethoden
Wenn bei der Laufzeit neue Methoden auf einem alten Server aufgerufen werden, erhalten neue Clients je nach Backend entweder eine Fehlermeldung oder eine Ausnahme.
- Das
cpp
-Backend erhält::android::UNKNOWN_TRANSACTION
. - Das
ndk
-Backend erhältSTATUS_UNKNOWN_TRANSACTION
. - Das
java
-Backend erhältandroid.os.RemoteException
mit der Meldung, dass die API nicht implementiert ist.
Strategien zur Bewältigung dieses Problems finden Sie unter Versionen abfragen und Standardwerte verwenden.
Parcelables
Wenn Parcelable-Objekten neue Felder hinzugefügt werden, werden sie von alten Clients und Servern verworfen. Wenn neue Clients und Server alte Parcelable-Objekte empfangen, werden die Standardwerte für neue Felder automatisch ausgefüllt. Das bedeutet, dass für alle neuen Felder in einem Parcelable Standardwerte angegeben werden müssen.
Clients sollten nicht davon ausgehen, dass Server die neuen Felder verwenden, es sei denn, sie wissen, dass der Server die Version implementiert, in der das Feld definiert ist (siehe Versionen abfragen).
Enums und Konstanten
Ebenso sollten Clients und Server nicht erkannte Konstantenwerte und Enumeratoren nach Bedarf ablehnen oder ignorieren, da in Zukunft möglicherweise weitere hinzugefügt werden. Ein Server sollte beispielsweise nicht abbrechen, wenn er einen Enumerator empfängt, den er nicht kennt. Der Server sollte den Enumerator entweder ignorieren oder etwas zurückgeben, damit der Client weiß, dass er in dieser Implementierung nicht unterstützt wird.
Gewerkschaften
Der Versuch, eine Union mit einem neuen Feld zu senden, schlägt fehl, wenn der Empfänger alt ist und das Feld nicht kennt. Die Implementierung sieht nie die Vereinigung mit dem neuen Feld. Bei einer Einwegtransaktion wird der Fehler ignoriert. Andernfalls ist der Fehler BAD_VALUE
(für das C++- oder NDK-Backend) oder IllegalArgumentException
(für das Java-Backend). Der Fehler wird ausgegeben, wenn der Client eine Union, die auf das neue Feld festgelegt ist, an einen alten Server sendet oder wenn ein alter Client die Union von einem neuen Server empfängt.
Mehrere Versionen verwalten
Ein Linker-Namespace in Android kann nur eine Version einer bestimmten aidl
-Schnittstelle haben, um Situationen zu vermeiden, in denen die generierten aidl
-Typen mehrere Definitionen haben. C++ hat die One Definition Rule, die nur eine Definition jedes Symbols erfordert.
Der Android-Build gibt einen Fehler zurück, wenn ein Modul von verschiedenen Versionen derselben aidl_interface
-Bibliothek abhängt. Das Modul kann direkt oder indirekt über Abhängigkeiten der Abhängigkeiten von diesen Bibliotheken davon abhängig sein. Diese Fehler zeigen den Abhängigkeitsgraphen vom fehlerhaften Modul zu den in Konflikt stehenden Versionen der aidl_interface
-Bibliothek. Alle Abhängigkeiten müssen aktualisiert werden, um dieselbe (in der Regel die neueste) Version dieser Bibliotheken zu enthalten.
Wenn die Benutzeroberflächenbibliothek von vielen verschiedenen Modulen verwendet wird, kann es hilfreich sein, cc_defaults
, java_defaults
und rust_defaults
für jede Gruppe von Bibliotheken und Prozessen zu erstellen, die dieselbe Version verwenden müssen. Wenn eine neue Version der Benutzeroberfläche eingeführt wird, können diese Standardeinstellungen aktualisiert und alle Module, die sie verwenden, gemeinsam aktualisiert werden. So wird sichergestellt, dass nicht unterschiedliche Versionen der Benutzeroberfläche verwendet werden.
cc_defaults {
name: "my.aidl.my-process-group-ndk-shared",
shared_libs: ["my.aidl-V3-ndk"],
...
}
cc_library {
name: "foo",
defaults: ["my.aidl.my-process-group-ndk-shared"],
...
}
cc_binary {
name: "bar",
defaults: ["my.aidl.my-process-group-ndk-shared"],
...
}
Wenn aidl_interface
-Module andere aidl_interface
-Module importieren, entstehen zusätzliche Abhängigkeiten, die erfordern, dass bestimmte Versionen zusammen verwendet werden. Diese Situation kann schwierig zu verwalten sein, wenn es gemeinsame aidl_interface
-Module gibt, die in mehrere aidl_interface
-Module importiert werden, die in denselben Prozessen zusammen verwendet werden.
Mit aidl_interfaces_defaults
können Sie eine Definition der neuesten Versionen von Abhängigkeiten für ein aidl_interface
speichern, die an einem Ort aktualisiert und von allen aidl_interface
-Modulen verwendet werden kann, die diese gemeinsame Schnittstelle importieren möchten.
aidl_interface_defaults {
name: "android.popular.common-latest-defaults",
imports: ["android.popular.common-V3"],
...
}
aidl_interface {
name: "android.foo",
defaults: ["my.aidl.latest-ndk-shared"],
...
}
aidl_interface {
name: "android.bar",
defaults: ["my.aidl.latest-ndk-shared"],
...
}
Flaggenbasierte Entwicklung
In der Entwicklung befindliche (nicht eingefrorene) Oberflächen können nicht auf Release-Geräten verwendet werden, da sie nicht garantiert abwärtskompatibel sind.
AIDL unterstützt einen Laufzeit-Fallback für diese nicht eingefrorenen Schnittstellenbibliotheken, damit Code anhand der neuesten nicht eingefrorenen Version geschrieben und trotzdem auf Release-Geräten verwendet werden kann. Das rückwärtskompatible Verhalten von Clients ähnelt dem vorhandenen Verhalten. Bei der Umstellung müssen die Implementierungen auch diesen Verhaltensweisen folgen. Weitere Informationen finden Sie unter Versionierte Oberflächen verwenden.
AIDL-Build-Flag
Das Flag, das dieses Verhalten steuert, ist RELEASE_AIDL_USE_UNFROZEN
und in build/release/build_flags.bzl
definiert. true
bedeutet, dass die nicht eingefrorene Version der Benutzeroberfläche zur Laufzeit verwendet wird. false
bedeutet, dass sich die Bibliotheken der nicht eingefrorenen Versionen alle wie ihre letzte eingefrorene Version verhalten.
Sie können das Flag für die lokale Entwicklung auf true
überschreiben, müssen es aber vor der Veröffentlichung auf false
zurücksetzen. Normalerweise wird die Entwicklung mit einer Konfiguration durchgeführt, bei der das Flag auf true
gesetzt ist.
Kompatibilitätsmatrix und Manifeste
Objekte der Anbieterschnittstelle (VINTF-Objekte) definieren, welche Versionen auf beiden Seiten der Anbieterschnittstelle erwartet und bereitgestellt werden.
Die meisten Geräte, die nicht Cuttlefish sind, sind erst nach dem Einfrieren der Schnittstellen auf die neueste Kompatibilitätsmatrix ausgerichtet. Daher gibt es keine Unterschiede bei den AIDL-Bibliotheken, die auf RELEASE_AIDL_USE_UNFROZEN
basieren.
Matrizen
Von Partnern stammende Oberflächen werden geräte- oder produktspezifischen Kompatibilitätsmatrizen hinzugefügt, auf die das Gerät während der Entwicklung ausgerichtet ist. Wenn also einer Kompatibilitätsmatrix eine neue, nicht eingefrorene Version einer Benutzeroberfläche hinzugefügt wird, müssen die vorherigen eingefrorenen Versionen für RELEASE_AIDL_USE_UNFROZEN=false
erhalten bleiben. Sie können dies beheben, indem Sie für verschiedene RELEASE_AIDL_USE_UNFROZEN
-Konfigurationen unterschiedliche Kompatibilitätsmatrixdateien verwenden oder beide Versionen in einer einzigen Kompatibilitätsmatrixdatei zulassen, die in allen Konfigurationen verwendet wird.
Wenn Sie beispielsweise eine nicht eingefrorene Version 4 hinzufügen, verwenden Sie <version>3-4</version>
.
Wenn Version 4 eingefroren ist, können Sie Version 3 aus der Kompatibilitätsmatrix entfernen, da die eingefrorene Version 4 verwendet wird, wenn RELEASE_AIDL_USE_UNFROZEN
false
ist.
Manifeste
In Android 15 wurde eine Änderung an libvintf
eingeführt, um die Manifestdateien beim Build-Vorgang basierend auf dem Wert von RELEASE_AIDL_USE_UNFROZEN
zu ändern.
In den Manifesten und Manifestfragmenten wird angegeben, welche Version einer Benutzeroberfläche ein Dienst implementiert. Wenn Sie die neueste nicht eingefrorene Version einer Benutzeroberfläche verwenden, muss das Manifest entsprechend aktualisiert werden. Wenn RELEASE_AIDL_USE_UNFROZEN=false
, werden die Manifesteinträge um libvintf
angepasst, um die Änderung in der generierten AIDL-Bibliothek widerzuspiegeln. Die Version wird von der nicht eingefrorenen Version N
in die letzte eingefrorene Version N - 1
geändert. Daher müssen Nutzer nicht mehrere Manifeste oder Manifestfragmente für jeden ihrer Dienste verwalten.
HAL-Clientänderungen
Der HAL-Clientcode muss abwärtskompatibel mit jeder vorherigen unterstützten eingefrorenen Version sein. Wenn RELEASE_AIDL_USE_UNFROZEN
false
ist, sehen die Dienste immer so aus wie die letzte oder eine frühere eingefrorene Version. Wenn Sie beispielsweise neue nicht eingefrorene Methoden aufrufen, wird UNKNOWN_TRANSACTION
zurückgegeben oder neue parcelable
-Felder haben ihre Standardwerte. Android-Framework-Clients müssen mit zusätzlichen früheren Versionen abwärtskompatibel sein. Dies ist jedoch eine neue Anforderung für Anbieter-Clients und Clients von Partneroberflächen.
Änderungen bei der HAL-Implementierung
Der größte Unterschied bei der HAL-Entwicklung im Vergleich zur flagbasierten Entwicklung besteht darin, dass HAL-Implementierungen abwärtskompatibel mit der letzten eingefrorenen Version sein müssen, damit sie funktionieren, wenn RELEASE_AIDL_USE_UNFROZEN
= false
ist.
Die Abwärtskompatibilität bei Implementierungen und Gerätecode ist eine neue Herausforderung. Weitere Informationen finden Sie unter Versionierte Schnittstellen verwenden.
Die Überlegungen zur Abwärtskompatibilität sind im Allgemeinen für Clients und Server sowie für Framework-Code und Anbietercode gleich. Es gibt jedoch subtile Unterschiede, die Sie beachten müssen, da Sie jetzt effektiv zwei Versionen implementieren, die denselben Quellcode verwenden (die aktuelle, nicht eingefrorene Version).
Beispiel: Eine Benutzeroberfläche hat drei eingefrorene Versionen. Die Benutzeroberfläche wird mit einer neuen Methode aktualisiert. Sowohl der Client als auch der Dienst werden auf die neue Version 4 der Bibliothek aktualisiert. Da die V4-Bibliothek auf einer nicht eingefrorenen Version der Benutzeroberfläche basiert, verhält sie sich wie die letzte eingefrorene Version, Version 3, wenn RELEASE_AIDL_USE_UNFROZEN
= false
ist, und verhindert die Verwendung der neuen Methode.
Wenn die Benutzeroberfläche eingefroren ist, wird für alle Werte von RELEASE_AIDL_USE_UNFROZEN
diese eingefrorene Version verwendet und der Code zur Abwärtskompatibilität kann entfernt werden.
Wenn Sie Methoden in Callbacks aufrufen, müssen Sie den Fall, in dem UNKNOWN_TRANSACTION
zurückgegeben wird, ordnungsgemäß behandeln. Clients implementieren möglicherweise zwei verschiedene Versionen eines Callbacks basierend auf der Release-Konfiguration. Du kannst also nicht davon ausgehen, dass der Client die neueste Version sendet. Neue Methoden könnten dies jedoch zurückgeben. Das ähnelt der Abwärtskompatibilität stabiler AIDL-Clients mit Servern, die im Abschnitt Versionierte Schnittstellen verwenden beschrieben wird.
// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
mMyCallback = cb;
// Get the version of the callback for later when we call methods on it
auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
return status;
}
// Example of using the callback later
void NotifyCallbackLater() {
// From the latest frozen version (V2)
mMyCallback->foo();
// Call this method from the unfrozen V3 only if the callback is at least V3
if (mMyCallbackVersion >= 3) {
mMyCallback->bar();
}
}
Neue Felder in vorhandenen Typen (parcelable
, enum
, union
) sind möglicherweise nicht vorhanden oder enthalten ihre Standardwerte, wenn RELEASE_AIDL_USE_UNFROZEN
false
ist. Die Werte neuer Felder, die ein Dienst senden möchte, werden am Ende des Prozesses gelöscht.
Neue Typen, die in dieser nicht eingefrorenen Version hinzugefügt wurden, können nicht über die Benutzeroberfläche gesendet oder empfangen werden.
Die Implementierung erhält nie einen Aufruf für neue Methoden von Clients, wenn RELEASE_AIDL_USE_UNFROZEN
false
ist.
Verwenden Sie neue Enumeratoren nur mit der Version, in der sie eingeführt wurden, und nicht mit der vorherigen Version.
Normalerweise verwenden Sie foo->getInterfaceVersion()
, um zu sehen, welche Version die Remote-Benutzeroberfläche verwendet. Wenn Sie jedoch die Unterstützung für die flagbasierte Versionierung verwenden, implementieren Sie zwei unterschiedliche Versionen. Daher sollten Sie die Version der aktuellen Benutzeroberfläche abrufen. Dazu können Sie die Benutzeroberflächenversion des aktuellen Objekts abrufen, z. B. this->getInterfaceVersion()
oder die anderen Methoden für my_ver
. Weitere Informationen finden Sie unter Oberflächenversion des Remoteobjekts abfragen.
Neue stabile VINTF-Schnittstellen
Wenn ein neues AIDL-Interface-Paket hinzugefügt wird, gibt es keine letzte eingefrorene Version. Wenn RELEASE_AIDL_USE_UNFROZEN
also false
ist, gibt es kein Verhalten, auf das zurückgegriffen werden kann. Verwenden Sie diese Schnittstellen nicht. Wenn RELEASE_AIDL_USE_UNFROZEN
false
ist, erlaubt der Dienstmanager dem Dienst nicht, die Benutzeroberfläche zu registrieren. Die Clients können sie dann nicht finden.
Sie können die Dienste bedingt basierend auf dem Wert des RELEASE_AIDL_USE_UNFROZEN
-Flags im Geräte-Makefile hinzufügen:
ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
android.hardware.health.storage-service
endif
Wenn der Dienst Teil eines größeren Prozesses ist und Sie ihn dem Gerät nicht bedingt hinzufügen können, können Sie prüfen, ob der Dienst mit IServiceManager::isDeclared()
deklariert ist. Wenn die Deklaration vorhanden ist, aber die Registrierung fehlgeschlagen ist, beenden Sie den Vorgang. Wenn sie nicht deklariert ist, wird sie wahrscheinlich nicht registriert.
Cuttlefish als Entwicklungstool
Jedes Jahr, nachdem die VINTF eingefroren wurde, passen wir die Framework Compatibility Matrix (FCM) target-level
und die PRODUCT_SHIPPING_API_LEVEL
von Cuttlefish an, damit sie die Geräte widerspiegeln, die mit der Veröffentlichung des nächsten Jahres auf den Markt kommen. Wir passen target-level
und PRODUCT_SHIPPING_API_LEVEL
so an, dass es ein eingeführtes Gerät gibt, das getestet wurde und die neuen Anforderungen für die Veröffentlichung im nächsten Jahr erfüllt.
Wenn RELEASE_AIDL_USE_UNFROZEN
= true
ist, wird Cuttlefish für die Entwicklung zukünftiger Android-Releases verwendet. Sie ist auf die FCM-Ebene der Android-Version des nächsten Jahres und auf PRODUCT_SHIPPING_API_LEVEL
ausgerichtet. Daher muss sie die Softwareanforderungen des Anbieters (Vendor Software Requirements, VSR) der nächsten Version erfüllen.
Wenn RELEASE_AIDL_USE_UNFROZEN
false
ist, hat Cuttlefish die vorherigen target-level
und PRODUCT_SHIPPING_API_LEVEL
, um ein Release-Gerät widerzuspiegeln.
Unter Android 14 und niedriger wird diese Unterscheidung mit verschiedenen Git-Branches erreicht, die die Änderung an FCM target-level
, die API-Schichten für die Bereitstellung oder anderen Code, der auf die nächste Version ausgerichtet ist, nicht berücksichtigen.
Regeln für die Benennung von Modulen
Unter Android 11 wird für jede Kombination der aktivierten Versionen und Backends automatisch ein Stub-Bibliotheksmodul erstellt. Wenn Sie für die Verknüpfung auf ein bestimmtes Stub-Bibliotheksmodul verweisen möchten, verwenden Sie nicht den Namen des aidl_interface
-Moduls, sondern den Namen des Stub-Bibliotheksmoduls, also ifacename-version-backend, wobei
ifacename
: Name desaidl_interface
-Modulsversion
ist entwederVversion-number
für die nicht mehr unterstützten VersionenVlatest-frozen-version-number + 1
für die Version am Ende des Stammbaums (noch nicht eingefroren)
backend
ist entwederjava
für das Java-Backend,cpp
für das C++-Back-End,ndk
oderndk_platform
für das NDK-Back-End. Ersteres ist für Apps und letzteres für die Plattformnutzung bis Android 13 vorgesehen. Verwenden Sie unter Android 13 und höher nurndk
.rust
für das Rust-Backend.
Angenommen, es gibt ein Modul mit dem Namen foo, dessen aktuelle Version 2 ist und das sowohl NDK als auch C++ unterstützt. In diesem Fall generiert AIDL die folgenden Module:
- Basierend auf Version 1
foo-V1-(java|cpp|ndk|ndk_platform|rust)
- Basierend auf Version 2 (der neuesten stabilen Version)
foo-V2-(java|cpp|ndk|ndk_platform|rust)
- Basierend auf der ToT-Version
foo-V3-(java|cpp|ndk|ndk_platform|rust)
Im Vergleich zu Android 11:
foo-backend
, das sich auf die neueste stabile Version bezog, wird infoo-V2-backend
geändert.foo-unstable-backend
, das sich auf die ToT-Version bezog, wird zufoo-V3-backend
.
Die Namen der Ausgabedateien stimmen immer mit den Namen der Module überein.
- Basierend auf Version 1:
foo-V1-(cpp|ndk|ndk_platform|rust).so
- Basierend auf Version 2:
foo-V2-(cpp|ndk|ndk_platform|rust).so
- Basierend auf der ToT-Version:
foo-V3-(cpp|ndk|ndk_platform|rust).so
Der AIDL-Compiler erstellt weder ein unstable
-Versionsmodul noch ein nicht versioniertes Modul für eine stabile AIDL-Schnittstelle.
Ab Android 12 enthält der Modulname, der aus einer stabilen AIDL-Schnittstelle generiert wird, immer die Version.
Neue Methoden für die Meta-Benutzeroberfläche
Android 10 fügt mehrere Meta-Interface-Methoden für die stabile AIDL hinzu.
Schnittstellenversion des Remote-Objekts abfragen
Clients können die Version und den Hash der Schnittstelle abfragen, die vom Remoteobjekt implementiert wird, und die zurückgegebenen Werte mit den Werten der Schnittstelle vergleichen, die der Client verwendet.
Beispiel mit dem cpp
-Backend:
sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
// the remote side is using an older interface
}
std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();
Beispiel mit dem ndk
- (und dem ndk_platform
-) Backend:
IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
// the remote side is using an older interface
}
std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);
Beispiel mit dem java
-Backend:
IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
// the remote side is using an older interface
}
String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();
Bei der Java-Sprache MÜSSEN getInterfaceVersion()
und getInterfaceHash()
auf der Remoteseite so implementiert werden. super
wird anstelle von IFoo
verwendet, um Fehler beim Kopieren und Einfügen zu vermeiden. Je nach javac
-Konfiguration ist die Anmerkung @SuppressWarnings("static")
möglicherweise erforderlich, um Warnungen zu deaktivieren:
class MyFoo extends IFoo.Stub {
@Override
public final int getInterfaceVersion() { return super.VERSION; }
@Override
public final String getInterfaceHash() { return super.HASH; }
}
Das liegt daran, dass die generierten Klassen (IFoo
, IFoo.Stub
usw.) zwischen Client und Server freigegeben werden. Die Klassen können sich beispielsweise im Boot-Classpath befinden. Wenn Kurse freigegeben werden, wird der Server auch mit der neuesten Version der Kurse verknüpft, auch wenn er mit einer älteren Version der Benutzeroberfläche erstellt wurde. Wenn diese Meta-Benutzeroberfläche in der freigegebenen Klasse implementiert ist, wird immer die neueste Version zurückgegeben. Wenn Sie die Methode jedoch wie oben implementieren, wird die Versionsnummer der Schnittstelle in den Code des Servers eingebettet (da IFoo.VERSION
eine static final int
ist, die bei der Referenzierung inline eingefügt wird). Die Methode kann also die genaue Version zurückgeben, mit der der Server erstellt wurde.
Ältere Benutzeroberflächen
Es ist möglich, dass ein Client mit der neueren Version einer AIDL-Schnittstelle aktualisiert wird, der Server aber die alte AIDL-Schnittstelle verwendet. In solchen Fällen wird beim Aufrufen einer Methode über eine alte Schnittstelle UNKNOWN_TRANSACTION
zurückgegeben.
Mit stabiler AIDL haben Kunden mehr Kontrolle. Auf der Clientseite können Sie für eine AIDL-Schnittstelle eine Standardimplementierung festlegen. Eine Methode in der Standardimplementierung wird nur dann aufgerufen, wenn die Methode nicht auf der Remoteseite implementiert ist, weil sie mit einer älteren Version der Benutzeroberfläche erstellt wurde. Da Standardeinstellungen global festgelegt werden, sollten sie nicht in potenziell freigegebenen Kontexten verwendet werden.
Beispiel in C++ in Android 13 und höher:
class MyDefault : public IFooDefault {
Status anAddedMethod(...) {
// do something default
}
};
// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());
foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
// remote side is not implementing it
Beispiel in Java:
IFoo.Stub.setDefaultImpl(new IFoo.Default() {
@Override
public xxx anAddedMethod(...) throws RemoteException {
// do something default
}
}); // once per an interface in a process
foo.anAddedMethod(...);
Sie müssen nicht die Standardimplementierung aller Methoden in einer AIDL-Schnittstelle angeben. Methoden, die garantiert auf der Remote-Seite implementiert werden (weil Sie sicher sind, dass die Remote-Steuerung erstellt wurde, als die Methoden in der AIDL-Beschreibung der Schnittstelle enthalten waren), müssen in der Standardklasse impl
nicht überschrieben werden.
Vorhandene AIDL in strukturierte oder stabile AIDL konvertieren
Wenn Sie eine vorhandene AIDL-Schnittstelle und Code haben, der sie verwendet, können Sie die Schnittstelle mit den folgenden Schritten in eine stabile AIDL-Schnittstelle umwandeln.
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.
Konvertieren Sie alle Parcelable-Objekte in Ihrer Benutzeroberfläche in stabile Parcelable-Objekte. Die Benutzeroberflächedateien selbst können unverändert bleiben. Geben Sie dazu die Struktur direkt in AIDL-Dateien an. Verwaltungsklassen müssen neu geschrieben werden, um diese neuen Typen zu verwenden. Das kann vor dem Erstellen eines
aidl_interface
-Pakets erfolgen (siehe unten).Erstellen Sie ein
aidl_interface
-Paket (wie oben beschrieben), das den Namen Ihres Moduls, seine Abhängigkeiten und alle anderen erforderlichen Informationen enthält. Damit er stabil (nicht nur strukturiert) ist, muss er auch versioniert werden. Weitere Informationen finden Sie unter Versionierung von Benutzeroberflächen.