Android 11 führt die Möglichkeit ein, AIDL für HALs in Android zu verwenden. Dadurch ist es möglich, Teile von Android ohne HIDL zu implementieren. Übergang von HALs zur ausschließlichen Verwendung von AIDL, sofern möglich (wenn Upstream-HALs HIDL verwenden, muss HIDL verwendet werden).
HALs, die AIDL verwenden, um zwischen Framework-Komponenten, wie z. B. in system.img
, und Hardwarekomponenten, wie z. B. in vendor.img
, zu kommunizieren, müssen Stable AIDL verwenden. Um jedoch innerhalb einer Partition zu kommunizieren, beispielsweise von einer HAL zu einer anderen, gibt es keine Beschränkung für den zu verwendenden IPC-Mechanismus.
Motivation
AIDL gibt es schon länger als HIDL und wird an vielen anderen Stellen verwendet, beispielsweise zwischen Android-Framework-Komponenten oder in Apps. Da AIDL jetzt Stabilitätsunterstützung bietet, ist es möglich, einen ganzen Stack mit einer einzigen IPC-Laufzeitumgebung zu implementieren. AIDL hat auch ein besseres Versionierungssystem als HIDL.
- Die Verwendung einer einzigen IPC-Sprache bedeutet, dass Sie nur eine Sache lernen, debuggen, optimieren und sichern müssen.
- AIDL unterstützt die direkte Versionierung für die Eigentümer einer Schnittstelle:
- Eigentümer können Methoden am Ende von Schnittstellen oder Felder zu Parcelables hinzufügen. Dies bedeutet, dass es im Laufe der Jahre einfacher ist, den Code zu versionieren, und auch die jährlichen Kosten geringer sind (Typen können vor Ort geändert werden, und es sind keine zusätzlichen Bibliotheken für jede Schnittstellenversion erforderlich).
- Erweiterungsschnittstellen können zur Laufzeit anstatt im Typsystem angefügt werden, sodass nachgelagerte Erweiterungen nicht auf neuere Schnittstellenversionen umbasiert werden müssen.
- Eine vorhandene AIDL-Schnittstelle kann direkt verwendet werden, wenn ihr Besitzer sich entscheidet, sie zu stabilisieren. Zuvor musste eine vollständige Kopie der Schnittstelle in HIDL erstellt werden.
Schreiben einer AIDL-HAL-Schnittstelle
Damit eine AIDL-Schnittstelle zwischen System und Anbieter verwendet werden kann, sind zwei Änderungen an der Schnittstelle erforderlich:
- Jede Typdefinition muss mit
@VintfStability
kommentiert werden. - Die
aidl_interface
Deklaration mussstability: "vintf",
.
Nur der Eigentümer einer Schnittstelle kann diese Änderungen vornehmen.
Wenn Sie diese Änderungen vornehmen, muss sich die Schnittstelle im VINTF-Manifest befinden , damit sie funktioniert. Testen Sie dies (und verwandte Anforderungen, z. B. die Überprüfung, ob freigegebene Schnittstellen eingefroren sind) mit dem VTS-Test vts_treble_vintf_vendor_test
. Sie können eine @VintfStability
Schnittstelle ohne diese Anforderungen verwenden, indem Sie entweder AIBinder_forceDowngradeToLocalStability
im NDK-Backend, android::Stability::forceDowngradeToLocalStability
im C++-Backend oder android.os.Binder#forceDowngradeToSystemStability
im Java-Backend für ein Binderobjekt aufrufen, bevor es gesendet wird zu einem anderen Prozess. Das Herunterstufen eines Dienstes auf Anbieterstabilität wird in Java nicht unterstützt, da alle Apps in einem Systemkontext ausgeführt werden.
Deaktivieren Sie außerdem das CPP-Back-End, um eine maximale Codeportabilität zu erreichen und potenzielle Probleme wie unnötige zusätzliche Bibliotheken zu vermeiden.
Beachten Sie, dass die Verwendung von backends
im folgenden Codebeispiel korrekt ist, da es drei Backends gibt (Java, NDK und CPP). Der folgende Code zeigt, wie das CPP-Backend speziell ausgewählt wird, um es zu deaktivieren.
aidl_interface: {
...
backends: {
cpp: {
enabled: false,
},
},
}
Suchen von AIDL HAL-Schnittstellen
AOSP Stable AIDL-Schnittstellen für HALs befinden sich in denselben Basisverzeichnissen wie HIDL-Schnittstellen, in aidl
Ordnern.
- Hardware/Schnittstellen
- Frameworks/Hardware/Schnittstellen
- System/Hardware/Schnittstellen
Sie sollten Erweiterungsschnittstellen in andere hardware/interfaces
Unterverzeichnisse im vendor
oder hardware
legen.
Erweiterungsschnittstellen
Android verfügt mit jeder Version über eine Reihe offizieller AOSP-Schnittstellen. Wenn Android-Partner diesen Schnittstellen Funktionen hinzufügen möchten, sollten sie diese nicht direkt ändern, da dies bedeuten würde, dass ihre Android-Laufzeit nicht mit der AOSP-Android-Laufzeit kompatibel ist. Bei GSM-Geräten stellt die Vermeidung der Änderung dieser Schnittstellen auch sicher, dass das GSI-Image weiterhin funktionieren kann.
Erweiterungen können auf zwei verschiedene Arten registriert werden:
- zur Laufzeit siehe angehängte Erweiterungen .
- eigenständig, weltweit und in VINTF registriert.
Wenn jedoch eine Erweiterung registriert wird, wenn herstellerspezifische Komponenten (d. h. nicht Teil des Upstream-AOSP) die Schnittstelle verwenden, besteht keine Möglichkeit eines Zusammenführungskonflikts. Wenn jedoch Downstream-Änderungen an Upstream-AOSP-Komponenten vorgenommen werden, können Zusammenführungskonflikte entstehen, und die folgenden Strategien werden empfohlen:
- die Interface-Ergänzungen können im nächsten Release auf AOSP hochgeladen werden
- Schnittstellenergänzungen, die weitere Flexibilität ohne Zusammenführungskonflikte ermöglichen, können in der nächsten Version hochgeladen werden
Erweiterung Parcelables: ParcelableHolder
ParcelableHolder
ist ein Parcelable
, das ein anderes Parcelable
enthalten kann. Der Hauptanwendungsfall von ParcelableHolder
besteht darin, ein Parcelable
erweiterbar zu machen. Zum Beispiel Bilder, von denen Geräteimplementierer erwarten, dass sie in der Lage sein werden, ein AOSP-definiertes Parcelable
, AospDefinedParcelable
, um ihre Mehrwertfunktionen zu erweitern.
Zuvor konnten Geräteimplementierer ohne ParcelableHolder
keine AOSP-definierte stabile AIDL-Schnittstelle ändern, da es ein Fehler wäre, weitere Felder hinzuzufügen:
parcelable AospDefinedParcelable {
int a;
String b;
String x; // ERROR: added by a device implementer
int[] y; // added by a device implementer
}
Wie im vorangehenden Code zu sehen ist, wird diese Praxis gebrochen, da die vom Geräteimplementierer hinzugefügten Felder möglicherweise einen Konflikt verursachen, wenn Parcelable in den nächsten Versionen von Android überarbeitet wird.
Mit ParcelableHolder
kann der Eigentümer eines Parcelable einen Erweiterungspunkt in einem Parcelable
definieren.
parcelable AospDefinedParcelable {
int a;
String b;
ParcelableHolder extension;
}
Dann können die Geräteimplementierer ihr eigenes Parcelable
für ihre Erweiterung definieren.
parcelable OemDefinedParcelable {
String x;
int[] y;
}
Schließlich kann das neue Parcelable
über das Feld ParcelableHolder
an das ursprüngliche Parcelable
angehängt werden.
// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;
ap.extension.setParcelable(op);
...
OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);
// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();
ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);
...
std::shared_ptr<OemDefinedParcelable> op_ptr;
ap.extension.getParcelable(&op_ptr);
// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);
...
std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);
// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });
ap.extension.set_parcelable(Rc::clone(&op));
...
let op = ap.extension.get_parcelable::<OemDefinedParcelable>();
Erstellen gegen die AIDL-Laufzeit
AIDL hat drei verschiedene Backends: Java, NDK, CPP. Um Stable AIDL zu verwenden, müssen Sie immer die Systemkopie von libbinder unter system/lib*/libbinder.so
und auf /dev/binder
sprechen. Für Code auf dem Anbieter-Image bedeutet dies, dass libbinder
(vom VNDK) nicht verwendet werden kann: Diese Bibliothek hat eine instabile C++-API und instabile Interna. Stattdessen muss nativer Anbietercode das NDK-Backend von AIDL verwenden, mit libbinder_ndk
(das vom System libbinder.so
unterstützt wird) und mit den von aidl_interface
Einträgen erstellten -ndk_platform
Bibliotheken verknüpfen.
AIDL HAL-Serverinstanznamen
Gemäß Konvention haben AIDL-HAL-Dienste einen Instanznamen im Format $package.$type/$instance
. Beispielsweise wird eine Instanz des Vibrators HAL als android.hardware.vibrator.IVibrator/default
registriert.
Schreiben eines AIDL-HAL-Servers
@VintfStability
AIDL-Server müssen im VINTF-Manifest deklariert werden, zum Beispiel so:
<hal format="aidl">
<name>android.hardware.vibrator</name>
<version>1</version>
<fqname>IVibrator/default</fqname>
</hal>
Andernfalls sollten sie einen AIDL-Dienst normal registrieren. Beim Ausführen von VTS-Tests wird erwartet, dass alle deklarierten AIDL-HALs verfügbar sind.
Schreiben eines AIDL-Clients
AIDL-Clients müssen sich in der Kompatibilitätsmatrix beispielsweise so deklarieren:
<hal format="aidl" optional="true">
<name>android.hardware.vibrator</name>
<version>1-2</version>
<interface>
<name>IVibrator</name>
<instance>default</instance>
</interface>
</hal>
Konvertieren einer vorhandenen HAL von HIDL in AIDL
Verwenden Sie das Tool hidl2aidl
, um eine HIDL-Schnittstelle in AIDL zu konvertieren.
hidl2aidl
Funktionen:
- Erstellen Sie
.aidl
Dateien basierend auf den.hal
Dateien für das angegebene Paket - Erstellen Sie Build-Regeln für das neu erstellte AIDL-Paket mit allen aktivierten Back-Ends
- Erstellen Sie Übersetzungsmethoden in den Java-, CPP- und NDK-Backends zum Übersetzen von den HIDL-Typen in die AIDL-Typen
- Erstellen Sie Build-Regeln für Übersetzungsbibliotheken mit erforderlichen Abhängigkeiten
- Erstellen Sie statische Asserts, um sicherzustellen, dass HIDL- und AIDL-Enumeratoren dieselben Werte in den CPP- und NDK-Back-Ends haben
Befolgen Sie diese Schritte, um ein Paket von .hal-Dateien in .aidl-Dateien zu konvertieren:
Erstellen Sie das Tool, das sich in
system/tools/hidl/hidl2aidl
.Das Erstellen dieses Tools aus der neuesten Quelle bietet die umfassendste Erfahrung. Sie können die neueste Version verwenden, um Schnittstellen auf älteren Zweigen aus früheren Versionen zu konvertieren.
m hidl2aidl
Führen Sie das Tool mit einem Ausgabeverzeichnis gefolgt von dem zu konvertierenden Paket aus.
Verwenden Sie optional das Argument
-l
, um den Inhalt einer neuen Lizenzdatei am Anfang aller generierten Dateien hinzuzufügen. Achten Sie darauf, die richtige Lizenz und das richtige Datum zu verwenden.hidl2aidl -o <output directory> -l <file with license> <package>
Zum Beispiel:
hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
Lesen Sie die generierten Dateien durch und beheben Sie alle Probleme mit der Konvertierung.
-
conversion.log
enthält alle unbehandelten Probleme, die zuerst behoben werden müssen. - Die generierten
.aidl
Dateien enthalten möglicherweise Warnungen und Vorschläge, die möglicherweise Maßnahmen erfordern. Diese Kommentare beginnen mit//
. - Nutzen Sie die Gelegenheit, das Paket aufzuräumen und zu verbessern.
- Überprüfen Sie die Annotation
@JavaDerive
auf möglicherweise benötigte Features, wie z. B.toString
oderequals
.
-
Erstellen Sie nur die Ziele, die Sie benötigen.
- Deaktivieren Sie Backends, die nicht verwendet werden. Bevorzugen Sie das NDK-Backend gegenüber dem CPP-Backend, siehe Laufzeit auswählen .
- Entfernen Sie Übersetzungsbibliotheken oder ihren generierten Code, der nicht verwendet wird.
Siehe Hauptunterschiede zwischen AIDL und HIDL .
- Die Verwendung des integrierten
Status
und der Ausnahmen von AIDL verbessert in der Regel die Schnittstelle und beseitigt die Notwendigkeit für einen anderen schnittstellenspezifischen Statustyp. - AIDL-Schnittstellenargumente in Methoden sind standardmäßig nicht
@nullable
wie in HIDL.
- Die Verwendung des integrierten
Sepolicy für AIDL HALs
Ein AIDL-Diensttyp, der für Anbietercode sichtbar ist, muss das hal_service_type
Attribut haben. Ansonsten ist die sepolicy-Konfiguration dieselbe wie bei jedem anderen AIDL-Dienst (obwohl es spezielle Attribute für HALs gibt). Hier ist eine Beispieldefinition eines HAL-Dienstkontexts:
type hal_foo_service, service_manager_type, hal_service_type;
Für die meisten von der Plattform definierten Dienste wird bereits ein Dienstkontext mit dem richtigen Typ hinzugefügt (z. B. wäre android.hardware.foo.IFoo/default
bereits als hal_foo_service
markiert). Wenn jedoch ein Framework-Client mehrere Instanznamen unterstützt, müssen zusätzliche Instanznamen in gerätespezifischen service_contexts
-Dateien hinzugefügt werden.
android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0
HAL-Attribute müssen hinzugefügt werden, wenn wir einen neuen HAL-Typ erstellen. Ein bestimmtes HAL-Attribut kann mehreren Diensttypen zugeordnet sein (von denen jeder mehrere Instanzen haben kann, wie wir gerade besprochen haben). Für eine HAL, foo
, haben wir hal_attribute(foo)
. Dieses Makro definiert die Attribute hal_foo_client
und hal_foo_server
. Für eine bestimmte Domäne ordnen die Makros hal_client_domain
und hal_server_domain
eine Domäne einem bestimmten HAL-Attribut zu. Beispielsweise entspricht der Systemserver, der ein Client dieser HAL ist, der Richtlinie hal_client_domain(system_server, hal_foo)
. Ein HAL-Server enthält in ähnlicher Weise hal_server_domain(my_hal_domain, hal_foo)
. Typischerweise erstellen wir für ein bestimmtes HAL-Attribut auch eine Domäne wie hal_foo_default
als Referenz oder Beispiel-HALs. Einige Geräte verwenden diese Domänen jedoch für ihre eigenen Server. Die Unterscheidung zwischen Domänen für mehrere Server ist nur dann von Bedeutung, wenn wir mehrere Server haben, die dieselbe Schnittstelle bedienen und in ihren Implementierungen unterschiedliche Berechtigungssätze benötigen. In all diesen Makros ist hal_foo
eigentlich kein sepolicy-Objekt. Stattdessen wird dieses Token von diesen Makros verwendet, um auf die Gruppe von Attributen zu verweisen, die einem Client-Server-Paar zugeordnet sind.
Bisher haben wir jedoch hal_foo_service
und hal_foo
(das Attributpaar von hal_attribute(foo)
) noch nicht verknüpft. Ein HAL-Attribut wird mithilfe des Makros hal_attribute_service mit AIDL-HAL-Diensten hal_attribute_service
(HIDL-HALs verwenden das Makro hal_attribute_hwservice
). Zum Beispiel hal_attribute_service(hal_foo, hal_foo_service)
. Das bedeutet, dass hal_foo_client
Prozesse die HAL abrufen können und hal_foo_server
Prozesse die HAL registrieren können. Die Durchsetzung dieser Registrierungsregeln erfolgt durch den Kontextmanager ( servicemanager
). Beachten Sie, dass Dienstnamen möglicherweise nicht immer HAL-Attributen entsprechen. Zum Beispiel könnten wir hal_attribute_service(hal_foo, hal_foo2_service)
. Da dies impliziert, dass die Dienste immer zusammen verwendet werden, könnten wir im Allgemeinen jedoch erwägen, den hal_foo2_service
zu entfernen und hal_foo_service
für alle unsere Dienstkontexte zu verwenden. Die meisten HALs, die mehrere hal_attribute_service
setzen, liegen daran, dass der ursprüngliche HAL-Attributname nicht allgemein genug ist und nicht geändert werden kann.
Alles zusammengenommen sieht ein Beispiel-HAL so aus:
public/attributes:
// define hal_foo, hal_foo_client, hal_foo_server
hal_attribute(foo)
public/service.te
// define hal_foo_service
type hal_foo_service, hal_service_type, protected_service, service_manager_type
public/hal_foo.te:
// allow binder connection from client to server
binder_call(hal_foo_client, hal_foo_server)
// allow client to find the service, allow server to register the service
hal_attribute_service(hal_foo, hal_foo_service)
// allow binder communication from server to service_manager
binder_use(hal_foo_server)
private/service_contexts:
// bind an AIDL service name to the selinux type
android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0
private/<some_domain>.te:
// let this domain use the hal service
binder_use(some_domain)
hal_client_domain(some_domain, hal_foo)
vendor/<some_hal_server_domain>.te
// let this domain serve the hal service
hal_server_domain(some_hal_server_domain, hal_foo)
Angeschlossene Erweiterungsschnittstellen
Eine Erweiterung kann an jede Binder-Schnittstelle angehängt werden, unabhängig davon, ob es sich um eine direkt beim Service Manager registrierte Schnittstelle der obersten Ebene oder um eine Unterschnittstelle handelt. Wenn Sie eine Erweiterung erhalten, müssen Sie bestätigen, dass der Typ der Erweiterung wie erwartet ist. Erweiterungen können nur aus dem Prozess dienenden Binder gesetzt werden.
Angehängte Erweiterungen sollten immer dann verwendet werden, wenn eine Erweiterung die Funktionalität einer vorhandenen HAL modifiziert. Wenn eine völlig neue Funktionalität benötigt wird, muss dieser Mechanismus nicht verwendet werden, und eine Erweiterungsschnittstelle kann direkt beim Dienstmanager registriert werden. Angefügte Erweiterungsschnittstellen sind am sinnvollsten, wenn sie an Unterschnittstellen angefügt sind, da diese Hierarchien tief oder aus mehreren Instanzen bestehen können. Die Verwendung einer globalen Erweiterung zum Spiegeln der Binder-Schnittstellenhierarchie eines anderen Dienstes würde eine umfangreiche Buchhaltung erfordern, um eine gleichwertige Funktionalität wie direkt angeschlossene Erweiterungen bereitzustellen.
Um eine Erweiterung für Binder festzulegen, verwenden Sie die folgenden APIs:
- Im NDK-Backend:
AIBinder_setExtension
- Im Java-Backend:
android.os.Binder.setExtension
- Im CPP-Backend:
android::Binder::setExtension
Um eine Erweiterung für einen Binder zu erhalten, verwenden Sie die folgenden APIs:
- Im NDK-Backend:
AIBinder_getExtension
- Im Java-Backend:
android.os.IBinder.getExtension
- Im CPP-Backend:
android::IBinder::getExtension
Weitere Informationen zu diesen APIs finden Sie in der Dokumentation der Funktion getExtension
im entsprechenden Backend. Ein Beispiel zur Verwendung von Erweiterungen finden Sie unter hardware/interfaces/tests/extension/vibrator .
Wesentliche AIDL/HIDL-Unterschiede
Beachten Sie bei der Verwendung von AIDL-HALs oder der Verwendung von AIDL-HAL-Schnittstellen die Unterschiede zum Schreiben von HIDL-HALs.
- Die Syntax der AIDL-Sprache ist näher an Java. Die HIDL-Syntax ähnelt der von C++.
- Alle AIDL-Schnittstellen haben eingebaute Fehlerstatus. Anstatt benutzerdefinierte Statustypen zu erstellen, erstellen Sie konstante Statusints in Schnittstellendateien und verwenden
EX_SERVICE_SPECIFIC
in den CPP/NDK-Backends undServiceSpecificException
im Java-Backend. Siehe Fehlerbehandlung . - AIDL startet Threadpools nicht automatisch, wenn Binder-Objekte gesendet werden. Sie müssen manuell gestartet werden (siehe Threadverwaltung ).
- AIDL bricht bei ungeprüften Transportfehlern nicht ab (HIDL
Return
bricht bei ungeprüften Fehlern ab). - AIDL kann nur einen Typ pro Datei deklarieren.
- AIDL-Argumente können zusätzlich zum Ausgabeparameter als in/out/inout angegeben werden (es gibt keine "synchronen Callbacks").
- AIDL verwendet anstelle von handle ein fd als primitiven Typ.
- HIDL verwendet Hauptversionen für inkompatible Änderungen und Nebenversionen für kompatible Änderungen. In AIDL werden rückwärtskompatible Änderungen vorgenommen. AIDL hat kein explizites Konzept von Hauptversionen; Stattdessen wird dies in Paketnamen integriert. Beispielsweise könnte AIDL den Paketnamen
bluetooth2
verwenden. - AIDL erbt standardmäßig keine Echtzeitpriorität. Die
setInheritRt
-Funktion muss pro Binder verwendet werden, um die Prioritätsvererbung in Echtzeit zu aktivieren.
Android 11 führt die Möglichkeit ein, AIDL für HALs in Android zu verwenden. Dadurch ist es möglich, Teile von Android ohne HIDL zu implementieren. Übergang von HALs zur ausschließlichen Verwendung von AIDL, sofern möglich (wenn Upstream-HALs HIDL verwenden, muss HIDL verwendet werden).
HALs, die AIDL verwenden, um zwischen Framework-Komponenten, wie z. B. in system.img
, und Hardwarekomponenten, wie z. B. in vendor.img
, zu kommunizieren, müssen Stable AIDL verwenden. Um jedoch innerhalb einer Partition zu kommunizieren, beispielsweise von einer HAL zu einer anderen, gibt es keine Beschränkung für den zu verwendenden IPC-Mechanismus.
Motivation
AIDL gibt es schon länger als HIDL und wird an vielen anderen Stellen verwendet, beispielsweise zwischen Android-Framework-Komponenten oder in Apps. Da AIDL jetzt Stabilitätsunterstützung bietet, ist es möglich, einen ganzen Stack mit einer einzigen IPC-Laufzeitumgebung zu implementieren. AIDL hat auch ein besseres Versionierungssystem als HIDL.
- Die Verwendung einer einzigen IPC-Sprache bedeutet, dass Sie nur eine Sache lernen, debuggen, optimieren und sichern müssen.
- AIDL unterstützt die direkte Versionierung für die Eigentümer einer Schnittstelle:
- Eigentümer können Methoden am Ende von Schnittstellen oder Felder zu Parcelables hinzufügen. Dies bedeutet, dass es im Laufe der Jahre einfacher ist, den Code zu versionieren, und auch die jährlichen Kosten geringer sind (Typen können vor Ort geändert werden, und es sind keine zusätzlichen Bibliotheken für jede Schnittstellenversion erforderlich).
- Erweiterungsschnittstellen können zur Laufzeit anstatt im Typsystem angefügt werden, sodass nachgelagerte Erweiterungen nicht auf neuere Schnittstellenversionen umbasiert werden müssen.
- Eine vorhandene AIDL-Schnittstelle kann direkt verwendet werden, wenn ihr Besitzer sich entscheidet, sie zu stabilisieren. Zuvor musste eine vollständige Kopie der Schnittstelle in HIDL erstellt werden.
Schreiben einer AIDL-HAL-Schnittstelle
Damit eine AIDL-Schnittstelle zwischen System und Anbieter verwendet werden kann, sind zwei Änderungen an der Schnittstelle erforderlich:
- Jede Typdefinition muss mit
@VintfStability
kommentiert werden. - Die
aidl_interface
Deklaration mussstability: "vintf",
.
Nur der Eigentümer einer Schnittstelle kann diese Änderungen vornehmen.
Wenn Sie diese Änderungen vornehmen, muss sich die Schnittstelle im VINTF-Manifest befinden , damit sie funktioniert. Testen Sie dies (und verwandte Anforderungen, z. B. die Überprüfung, ob freigegebene Schnittstellen eingefroren sind) mit dem VTS-Test vts_treble_vintf_vendor_test
. Sie können eine @VintfStability
Schnittstelle ohne diese Anforderungen verwenden, indem Sie entweder AIBinder_forceDowngradeToLocalStability
im NDK-Backend, android::Stability::forceDowngradeToLocalStability
im C++-Backend oder android.os.Binder#forceDowngradeToSystemStability
im Java-Backend für ein Binderobjekt aufrufen, bevor es gesendet wird zu einem anderen Prozess. Das Herunterstufen eines Dienstes auf Anbieterstabilität wird in Java nicht unterstützt, da alle Apps in einem Systemkontext ausgeführt werden.
Deaktivieren Sie außerdem das CPP-Back-End, um eine maximale Codeportabilität zu erreichen und potenzielle Probleme wie unnötige zusätzliche Bibliotheken zu vermeiden.
Beachten Sie, dass die Verwendung von backends
im folgenden Codebeispiel korrekt ist, da es drei Backends gibt (Java, NDK und CPP). Der folgende Code zeigt, wie das CPP-Backend speziell ausgewählt wird, um es zu deaktivieren.
aidl_interface: {
...
backends: {
cpp: {
enabled: false,
},
},
}
Suchen von AIDL HAL-Schnittstellen
AOSP Stable AIDL-Schnittstellen für HALs befinden sich in denselben Basisverzeichnissen wie HIDL-Schnittstellen, in aidl
Ordnern.
- Hardware/Schnittstellen
- Frameworks/Hardware/Schnittstellen
- System/Hardware/Schnittstellen
Sie sollten Erweiterungsschnittstellen in andere hardware/interfaces
Unterverzeichnisse im vendor
oder hardware
legen.
Erweiterungsschnittstellen
Android verfügt mit jeder Version über eine Reihe offizieller AOSP-Schnittstellen. Wenn Android-Partner diesen Schnittstellen Funktionen hinzufügen möchten, sollten sie diese nicht direkt ändern, da dies bedeuten würde, dass ihre Android-Laufzeit nicht mit der AOSP-Android-Laufzeit kompatibel ist. Bei GSM-Geräten stellt die Vermeidung der Änderung dieser Schnittstellen auch sicher, dass das GSI-Image weiterhin funktionieren kann.
Erweiterungen können auf zwei verschiedene Arten registriert werden:
- zur Laufzeit siehe angehängte Erweiterungen .
- eigenständig, weltweit und in VINTF registriert.
Wenn jedoch eine Erweiterung registriert wird, wenn herstellerspezifische Komponenten (d. h. nicht Teil des Upstream-AOSP) die Schnittstelle verwenden, besteht keine Möglichkeit eines Zusammenführungskonflikts. Wenn jedoch Downstream-Änderungen an Upstream-AOSP-Komponenten vorgenommen werden, können Zusammenführungskonflikte entstehen, und die folgenden Strategien werden empfohlen:
- die Interface-Ergänzungen können im nächsten Release auf AOSP hochgeladen werden
- Schnittstellenergänzungen, die weitere Flexibilität ohne Zusammenführungskonflikte ermöglichen, können in der nächsten Version hochgeladen werden
Erweiterung Parcelables: ParcelableHolder
ParcelableHolder
ist ein Parcelable
, das ein anderes Parcelable
enthalten kann. Der Hauptanwendungsfall von ParcelableHolder
besteht darin, ein Parcelable
erweiterbar zu machen. Zum Beispiel Bilder, von denen Geräteimplementierer erwarten, dass sie in der Lage sein werden, ein AOSP-definiertes Parcelable
, AospDefinedParcelable
, um ihre Mehrwertfunktionen zu erweitern.
Zuvor konnten Geräteimplementierer ohne ParcelableHolder
keine AOSP-definierte stabile AIDL-Schnittstelle ändern, da es ein Fehler wäre, weitere Felder hinzuzufügen:
parcelable AospDefinedParcelable {
int a;
String b;
String x; // ERROR: added by a device implementer
int[] y; // added by a device implementer
}
Wie im vorangehenden Code zu sehen ist, wird diese Praxis gebrochen, da die vom Geräteimplementierer hinzugefügten Felder möglicherweise einen Konflikt verursachen, wenn Parcelable in den nächsten Versionen von Android überarbeitet wird.
Mit ParcelableHolder
kann der Eigentümer eines Parcelable einen Erweiterungspunkt in einem Parcelable
definieren.
parcelable AospDefinedParcelable {
int a;
String b;
ParcelableHolder extension;
}
Dann können die Geräteimplementierer ihr eigenes Parcelable
für ihre Erweiterung definieren.
parcelable OemDefinedParcelable {
String x;
int[] y;
}
Schließlich kann das neue Parcelable
über das Feld ParcelableHolder
an das ursprüngliche Parcelable
angehängt werden.
// Java
AospDefinedParcelable ap = ...;
OemDefinedParcelable op = new OemDefinedParcelable();
op.x = ...;
op.y = ...;
ap.extension.setParcelable(op);
...
OemDefinedParcelable op = ap.extension.getParcelable(OemDefinedParcelable.class);
// C++
AospDefinedParcelable ap;
OemDefinedParcelable op;
std::shared_ptr<OemDefinedParcelable> op_ptr = make_shared<OemDefinedParcelable>();
ap.extension.setParcelable(op);
ap.extension.setParcelable(op_ptr);
...
std::shared_ptr<OemDefinedParcelable> op_ptr;
ap.extension.getParcelable(&op_ptr);
// NDK
AospDefinedParcelable ap;
OemDefinedParcelable op;
ap.extension.setParcelable(op);
...
std::optional<OemDefinedParcelable> op;
ap.extension.getParcelable(&op);
// Rust
let mut ap = AospDefinedParcelable { .. };
let op = Rc::new(OemDefinedParcelable { .. });
ap.extension.set_parcelable(Rc::clone(&op));
...
let op = ap.extension.get_parcelable::<OemDefinedParcelable>();
Erstellen gegen die AIDL-Laufzeit
AIDL hat drei verschiedene Backends: Java, NDK, CPP. Um Stable AIDL zu verwenden, müssen Sie immer die Systemkopie von libbinder unter system/lib*/libbinder.so
und auf /dev/binder
sprechen. Für Code auf dem Anbieter-Image bedeutet dies, dass libbinder
(vom VNDK) nicht verwendet werden kann: Diese Bibliothek hat eine instabile C++-API und instabile Interna. Stattdessen muss nativer Anbietercode das NDK-Backend von AIDL verwenden, mit libbinder_ndk
(das vom System libbinder.so
unterstützt wird) und mit den von aidl_interface
Einträgen erstellten -ndk_platform
Bibliotheken verknüpfen.
AIDL HAL-Serverinstanznamen
Gemäß Konvention haben AIDL-HAL-Dienste einen Instanznamen im Format $package.$type/$instance
. Beispielsweise wird eine Instanz des Vibrators HAL als android.hardware.vibrator.IVibrator/default
registriert.
Schreiben eines AIDL-HAL-Servers
@VintfStability
AIDL-Server müssen im VINTF-Manifest deklariert werden, zum Beispiel so:
<hal format="aidl">
<name>android.hardware.vibrator</name>
<version>1</version>
<fqname>IVibrator/default</fqname>
</hal>
Andernfalls sollten sie einen AIDL-Dienst normal registrieren. Beim Ausführen von VTS-Tests wird erwartet, dass alle deklarierten AIDL-HALs verfügbar sind.
Schreiben eines AIDL-Clients
AIDL-Clients müssen sich in der Kompatibilitätsmatrix beispielsweise so deklarieren:
<hal format="aidl" optional="true">
<name>android.hardware.vibrator</name>
<version>1-2</version>
<interface>
<name>IVibrator</name>
<instance>default</instance>
</interface>
</hal>
Konvertieren einer vorhandenen HAL von HIDL in AIDL
Verwenden Sie das Tool hidl2aidl
, um eine HIDL-Schnittstelle in AIDL zu konvertieren.
hidl2aidl
Funktionen:
- Erstellen Sie
.aidl
Dateien basierend auf den.hal
Dateien für das angegebene Paket - Erstellen Sie Build-Regeln für das neu erstellte AIDL-Paket mit allen aktivierten Back-Ends
- Erstellen Sie Übersetzungsmethoden in den Java-, CPP- und NDK-Backends zum Übersetzen von den HIDL-Typen in die AIDL-Typen
- Erstellen Sie Build-Regeln für Übersetzungsbibliotheken mit erforderlichen Abhängigkeiten
- Erstellen Sie statische Asserts, um sicherzustellen, dass HIDL- und AIDL-Enumeratoren dieselben Werte in den CPP- und NDK-Back-Ends haben
Befolgen Sie diese Schritte, um ein Paket von .hal-Dateien in .aidl-Dateien zu konvertieren:
Erstellen Sie das Tool, das sich in
system/tools/hidl/hidl2aidl
.Das Erstellen dieses Tools aus der neuesten Quelle bietet die umfassendste Erfahrung. Sie können die neueste Version verwenden, um Schnittstellen auf älteren Zweigen aus früheren Versionen zu konvertieren.
m hidl2aidl
Führen Sie das Tool mit einem Ausgabeverzeichnis gefolgt von dem zu konvertierenden Paket aus.
Verwenden Sie optional das Argument
-l
, um den Inhalt einer neuen Lizenzdatei am Anfang aller generierten Dateien hinzuzufügen. Achten Sie darauf, die richtige Lizenz und das richtige Datum zu verwenden.hidl2aidl -o <output directory> -l <file with license> <package>
Zum Beispiel:
hidl2aidl -o . -l my_license.txt android.hardware.nfc@1.2
Lesen Sie die generierten Dateien durch und beheben Sie alle Probleme mit der Konvertierung.
-
conversion.log
enthält alle unbehandelten Probleme, die zuerst behoben werden müssen. - Die generierten
.aidl
Dateien enthalten möglicherweise Warnungen und Vorschläge, die möglicherweise Maßnahmen erfordern. Diese Kommentare beginnen mit//
. - Nutzen Sie die Gelegenheit, das Paket aufzuräumen und zu verbessern.
- Überprüfen Sie die Annotation
@JavaDerive
auf möglicherweise benötigte Features, wie z. B.toString
oderequals
.
-
Erstellen Sie nur die Ziele, die Sie benötigen.
- Deaktivieren Sie Backends, die nicht verwendet werden. Bevorzugen Sie das NDK-Backend gegenüber dem CPP-Backend, siehe Laufzeit auswählen .
- Entfernen Sie Übersetzungsbibliotheken oder ihren generierten Code, der nicht verwendet wird.
Siehe Hauptunterschiede zwischen AIDL und HIDL .
- Die Verwendung des integrierten
Status
und der Ausnahmen von AIDL verbessert in der Regel die Schnittstelle und beseitigt die Notwendigkeit für einen anderen schnittstellenspezifischen Statustyp. - AIDL-Schnittstellenargumente in Methoden sind standardmäßig nicht
@nullable
wie in HIDL.
- Die Verwendung des integrierten
Sepolicy für AIDL HALs
Ein AIDL-Diensttyp, der für Anbietercode sichtbar ist, muss das hal_service_type
Attribut haben. Ansonsten ist die sepolicy-Konfiguration dieselbe wie bei jedem anderen AIDL-Dienst (obwohl es spezielle Attribute für HALs gibt). Hier ist eine Beispieldefinition eines HAL-Dienstkontexts:
type hal_foo_service, service_manager_type, hal_service_type;
Für die meisten von der Plattform definierten Dienste wird bereits ein Dienstkontext mit dem richtigen Typ hinzugefügt (z. B. wäre android.hardware.foo.IFoo/default
bereits als hal_foo_service
markiert). Wenn jedoch ein Framework-Client mehrere Instanznamen unterstützt, müssen zusätzliche Instanznamen in gerätespezifischen service_contexts
-Dateien hinzugefügt werden.
android.hardware.foo.IFoo/custom_instance u:object_r:hal_foo_service:s0
HAL-Attribute müssen hinzugefügt werden, wenn wir einen neuen HAL-Typ erstellen. Ein bestimmtes HAL-Attribut kann mehreren Diensttypen zugeordnet sein (von denen jeder mehrere Instanzen haben kann, wie wir gerade besprochen haben). Für eine HAL, foo
, haben wir hal_attribute(foo)
. Dieses Makro definiert die Attribute hal_foo_client
und hal_foo_server
. Für eine bestimmte Domäne ordnen die Makros hal_client_domain
und hal_server_domain
eine Domäne einem bestimmten HAL-Attribut zu. Beispielsweise entspricht der Systemserver, der ein Client dieser HAL ist, der Richtlinie hal_client_domain(system_server, hal_foo)
. Ein HAL-Server enthält in ähnlicher Weise hal_server_domain(my_hal_domain, hal_foo)
. Typischerweise erstellen wir für ein bestimmtes HAL-Attribut auch eine Domäne wie hal_foo_default
als Referenz oder Beispiel-HALs. Einige Geräte verwenden diese Domänen jedoch für ihre eigenen Server. Die Unterscheidung zwischen Domänen für mehrere Server ist nur dann von Bedeutung, wenn wir mehrere Server haben, die dieselbe Schnittstelle bedienen und in ihren Implementierungen unterschiedliche Berechtigungssätze benötigen. In all diesen Makros ist hal_foo
eigentlich kein sepolicy-Objekt. Stattdessen wird dieses Token von diesen Makros verwendet, um auf die Gruppe von Attributen zu verweisen, die einem Client-Server-Paar zugeordnet sind.
Bisher haben wir jedoch hal_foo_service
und hal_foo
(das Attributpaar von hal_attribute(foo)
) noch nicht verknüpft. Ein HAL-Attribut wird mithilfe des Makros hal_attribute_service mit AIDL-HAL-Diensten hal_attribute_service
(HIDL-HALs verwenden das Makro hal_attribute_hwservice
). Zum Beispiel hal_attribute_service(hal_foo, hal_foo_service)
. Das bedeutet, dass hal_foo_client
Prozesse die HAL abrufen können und hal_foo_server
Prozesse die HAL registrieren können. Die Durchsetzung dieser Registrierungsregeln erfolgt durch den Kontextmanager ( servicemanager
). Beachten Sie, dass Dienstnamen möglicherweise nicht immer HAL-Attributen entsprechen. Zum Beispiel könnten wir hal_attribute_service(hal_foo, hal_foo2_service)
. Da dies impliziert, dass die Dienste immer zusammen verwendet werden, könnten wir im Allgemeinen jedoch erwägen, den hal_foo2_service
zu entfernen und hal_foo_service
für alle unsere Dienstkontexte zu verwenden. Die meisten HALs, die mehrere hal_attribute_service
setzen, liegen daran, dass der ursprüngliche HAL-Attributname nicht allgemein genug ist und nicht geändert werden kann.
Alles zusammengenommen sieht ein Beispiel-HAL so aus:
public/attributes:
// define hal_foo, hal_foo_client, hal_foo_server
hal_attribute(foo)
public/service.te
// define hal_foo_service
type hal_foo_service, hal_service_type, protected_service, service_manager_type
public/hal_foo.te:
// allow binder connection from client to server
binder_call(hal_foo_client, hal_foo_server)
// allow client to find the service, allow server to register the service
hal_attribute_service(hal_foo, hal_foo_service)
// allow binder communication from server to service_manager
binder_use(hal_foo_server)
private/service_contexts:
// bind an AIDL service name to the selinux type
android.hardware.foo.IFooXxxx/default u:object_r:hal_foo_service:s0
private/<some_domain>.te:
// let this domain use the hal service
binder_use(some_domain)
hal_client_domain(some_domain, hal_foo)
vendor/<some_hal_server_domain>.te
// let this domain serve the hal service
hal_server_domain(some_hal_server_domain, hal_foo)
Angeschlossene Erweiterungsschnittstellen
Eine Erweiterung kann an jede Binder-Schnittstelle angehängt werden, unabhängig davon, ob es sich um eine direkt beim Service Manager registrierte Schnittstelle der obersten Ebene oder um eine Unterschnittstelle handelt. Wenn Sie eine Erweiterung erhalten, müssen Sie bestätigen, dass der Typ der Erweiterung wie erwartet ist. Erweiterungen können nur aus dem Prozess dienenden Binder gesetzt werden.
Angehängte Erweiterungen sollten immer dann verwendet werden, wenn eine Erweiterung die Funktionalität einer vorhandenen HAL modifiziert. Wenn eine völlig neue Funktionalität benötigt wird, muss dieser Mechanismus nicht verwendet werden, und eine Erweiterungsschnittstelle kann direkt beim Dienstmanager registriert werden. Angefügte Erweiterungsschnittstellen sind am sinnvollsten, wenn sie an Unterschnittstellen angefügt sind, da diese Hierarchien tief oder aus mehreren Instanzen bestehen können. Die Verwendung einer globalen Erweiterung zum Spiegeln der Binder-Schnittstellenhierarchie eines anderen Dienstes würde eine umfangreiche Buchhaltung erfordern, um eine gleichwertige Funktionalität wie direkt angeschlossene Erweiterungen bereitzustellen.
Um eine Erweiterung für Binder festzulegen, verwenden Sie die folgenden APIs:
- Im NDK-Backend:
AIBinder_setExtension
- Im Java-Backend:
android.os.Binder.setExtension
- Im CPP-Backend:
android::Binder::setExtension
Um eine Erweiterung für einen Binder zu erhalten, verwenden Sie die folgenden APIs:
- Im NDK-Backend:
AIBinder_getExtension
- Im Java-Backend:
android.os.IBinder.getExtension
- Im CPP-Backend:
android::IBinder::getExtension
Weitere Informationen zu diesen APIs finden Sie in der Dokumentation der Funktion getExtension
im entsprechenden Backend. Ein Beispiel zur Verwendung von Erweiterungen finden Sie unter hardware/interfaces/tests/extension/vibrator .
Wesentliche AIDL/HIDL-Unterschiede
Beachten Sie bei der Verwendung von AIDL-HALs oder der Verwendung von AIDL-HAL-Schnittstellen die Unterschiede zum Schreiben von HIDL-HALs.
- Die Syntax der AIDL-Sprache ist näher an Java. Die HIDL-Syntax ähnelt der von C++.
- Alle AIDL-Schnittstellen haben eingebaute Fehlerstatus. Anstatt benutzerdefinierte Statustypen zu erstellen, erstellen Sie konstante Statusints in Schnittstellendateien und verwenden
EX_SERVICE_SPECIFIC
in den CPP/NDK-Backends undServiceSpecificException
im Java-Backend. Siehe Fehlerbehandlung . - AIDL startet Threadpools nicht automatisch, wenn Binder-Objekte gesendet werden. Sie müssen manuell gestartet werden (siehe Threadverwaltung ).
- AIDL bricht bei ungeprüften Transportfehlern nicht ab (HIDL
Return
bricht bei ungeprüften Fehlern ab). - AIDL kann nur einen Typ pro Datei deklarieren.
- AIDL-Argumente können zusätzlich zum Ausgabeparameter als in/out/inout angegeben werden (es gibt keine "synchronen Callbacks").
- AIDL verwendet anstelle von handle ein fd als primitiven Typ.
- HIDL verwendet Hauptversionen für inkompatible Änderungen und Nebenversionen für kompatible Änderungen. In AIDL werden rückwärtskompatible Änderungen vorgenommen. AIDL hat kein explizites Konzept von Hauptversionen; Stattdessen wird dies in Paketnamen integriert. Beispielsweise könnte AIDL den Paketnamen
bluetooth2
verwenden. - AIDL erbt standardmäßig keine Echtzeitpriorität. Die
setInheritRt
-Funktion muss pro Binder verwendet werden, um die Prioritätsvererbung in Echtzeit zu aktivieren.