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-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 vonaidl_interface
-Modulen, die verwendet werden. Wenn eine Ihrer AIDL-Schnittstellen eine Schnittstelle oder ein Paket aus einem anderenaidl_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 unterapi_dir
fixiert sind, werden ab Android 11 dieversions
unteraidl_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 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 soll 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, sofernunstable
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 insystem.img
und zugehörigen Partitionen oder alle Anbieterelemente, z. B.vendor.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 diese Option auftrue
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 auftrue
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 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 fürndk_header
undcpp_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, 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;
}
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 keinowner:
-Feld angegeben ist, eingefroren werden.AIDL_FROZEN_OWNERS="aosp test"
– Für den Build müssen alle stabilen AIDL-Schnittstellen eingefroren sein und 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. 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ä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 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 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-Endndk
oderndk_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 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 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 infoo-V2-backend
geändert.foo-unstable-backend
, das auf die Version mit den Nutzungsbedingungen verweist, 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-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.
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. 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).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.