Ein AIDL-Backend ist ein Ziel für die Stub-Code-Generierung. Bei der Verwendung von AIDL-Dateien verwenden Sie diese immer in einer bestimmten Sprache mit einer bestimmten Laufzeit. Je nach Kontext sollten Sie unterschiedliche AIDL-Backends verwenden.
AIDL hat die folgenden Backends:
Backend | Sprache | API-Oberfläche | Systeme bauen |
---|---|---|---|
Java | Java | SDK/SystemApi (stabil*) | alles |
NDK | C++ | libbinder_ndk (stabil*) | aidl_interface |
CPP | C++ | libbinder (instabil) | alles |
Rost | Rost | libbinder_rs (instabil) | aidl_interface |
- Diese API-Oberflächen sind stabil, aber viele der APIs, z. B. für die Dienstverwaltung, sind für die interne Plattformnutzung reserviert und stehen Apps nicht zur Verfügung. Weitere Informationen zur Verwendung von AIDL in Apps finden Sie in der Entwicklerdokumentation .
- Das Rust-Backend wurde in Android 12 eingeführt; das NDK-Backend ist seit Android 10 verfügbar.
- Die Rust-Kiste baut auf
libbinder_ndk
auf. APEXs verwenden die Binder-Crate auf die gleiche Weise wie alle anderen auf der Systemseite. Die Rust-Portion wird in einem APEX gebündelt und darin versendet. Es hängt von derlibbinder_ndk.so
auf der Systempartition ab.
Systeme bauen
Je nach Backend gibt es zwei Möglichkeiten, AIDL in Stub-Code zu kompilieren. Weitere Einzelheiten zu den Build-Systemen finden Sie in der Soong-Modulreferenz .
Core-Build-System
In jedem cc_
oder java_
Android.bp-Modul (oder in ihren Android.mk
Äquivalenten) können .aidl
Dateien als Quelldateien angegeben werden. In diesem Fall werden die Java/CPP-Backends von AIDL verwendet (nicht das NDK-Backend), und die Klassen zur Verwendung der entsprechenden AIDL-Dateien werden dem Modul automatisch hinzugefügt. Optionen wie local_include_dirs
, die dem Build-System den Root-Pfad zu AIDL-Dateien in diesem Modul mitteilen, können in diesen Modulen unter einer aidl:
-Gruppe angegeben werden. Beachten Sie, dass das Rust-Backend nur für die Verwendung mit Rust vorgesehen ist. rust_
Module werden insofern anders gehandhabt, als AIDL-Dateien nicht als Quelldateien angegeben werden. Stattdessen erzeugt das Modul aidl_interface
eine rustlib
namens <aidl_interface name>-rust
, gegen die gelinkt werden kann. Weitere Einzelheiten finden Sie im Rust-AIDL-Beispiel .
aidl_interface
Siehe Stable AIDL . Mit diesem Build-System verwendete Typen müssen strukturiert sein; das heißt direkt in AIDL ausgedrückt. Das bedeutet, dass benutzerdefinierte Parcelables nicht verwendet werden können.
Typen
Sie können den aidl
Compiler als Referenzimplementierung für Typen betrachten. Wenn Sie eine Schnittstelle erstellen, rufen aidl --lang=<backend> ...
auf, um die resultierende Schnittstellendatei anzuzeigen. Wenn Sie das Modul aidl_interface
verwenden, können Sie die Ausgabe in out/soong/.intermediates/<path to module>/
.
Java/AIDL-Typ | C++-Typ | NDK-Typ | Rosttyp |
---|---|---|---|
boolesch | bool | bool | bool |
Byte | int8_t | int8_t | i8 |
verkohlen | char16_t | char16_t | u16 |
int | int32_t | int32_t | i32 |
lang | int64_t | int64_t | i64 |
schweben | schweben | schweben | f32 |
doppelt | doppelt | doppelt | f64 |
Schnur | android::String16 | std::string | Schnur |
android.os.Parcelable | android::Parcelable | N / A | N / A |
IBinder | android::IBinder | ndk::SpAIBinder | binder::SpIBinder |
T[] | std::vector<T> | std::vector<T> | In: &T Aus: Vec<T> |
Byte[] | std::vector<uint8_t> | std::vector<int8_t> 1 | In: &[u8] Aus: Vec<u8> |
Liste<T> | std::vector<T> 2 | std::vector<T> 3 | In: &[T] 4 Aus: Vec<T> |
FileDescriptor | android::base::unique_fd | N / A | binder::parcel::ParcelFileDescriptor |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | binder::parcel::ParcelFileDescriptor |
Schnittstellentyp (T) | android::sp<T> | std::shared_ptr<T> | binder::Stark |
Paketierbarer Typ (T) | T | T | T |
Verbindungstyp (T) 5 | T | T | T |
T[N] 6 | std::array<T, N> | std::array<T, N> | [T; N] |
1. In Android 12 oder höher verwenden Byte-Arrays aus Kompatibilitätsgründen uint8_t anstelle von int8_t.
2. Das C++-Backend unterstützt List<T>
, wobei T
entweder String
, IBinder
, ParcelFileDescriptor
oder paketfähig ist. In Android T (AOSP experimentell) oder höher kann T
jeder nicht-primitive Typ (einschließlich Schnittstellentypen) mit Ausnahme von Arrays sein. AOSP empfiehlt die Verwendung von Array-Typen wie T[]
, da sie in allen Backends funktionieren.
3. Das NDK-Backend unterstützt List<T>
, wobei T
entweder String
, ParcelFileDescriptor
oder parcelable ist. In Android T (AOSP experimentell) oder höher kann T
jeder nicht primitive Typ sein, außer Arrays.
4. Typen werden für Rust-Code unterschiedlich übergeben, je nachdem, ob es sich um eine Eingabe (ein Argument) oder eine Ausgabe (ein zurückgegebener Wert) handelt.
5. Union-Typen werden in Android 12 und höher unterstützt.
6. In Android T (AOSP experimentell) oder höher werden Arrays mit fester Größe unterstützt. Arrays mit fester Größe können mehrere Dimensionen haben (z. B. int[3][4]
). Im Java-Backend werden Arrays fester Größe als Array-Typen dargestellt.
Direktionalität (in/out/inout)
Wenn Sie die Typen der Argumente für Funktionen angeben, können Sie diese als in
, out
oder inout
. Dies steuert, in welche Richtung Informationen für einen IPC-Aufruf weitergegeben werden. in
ist die Standardrichtung und zeigt an, dass Daten vom Aufrufer an den Angerufenen übergeben werden. out
bedeutet, dass Daten vom Angerufenen an den Aufrufer weitergegeben werden. inout
ist die Kombination aus beidem. Das Android-Team empfiehlt jedoch, die Verwendung des Argumentbezeichners inout
zu vermeiden. Wenn Sie inout
mit einer versionierten Schnittstelle und einem älteren Aufgerufenen verwenden, werden die zusätzlichen Felder, die nur im Aufrufer vorhanden sind, auf ihre Standardwerte zurückgesetzt. In Bezug auf Rust erhält ein normaler inout
Typ &mut Vec<T>
und ein List inout
Typ erhält &mut Vec<T>
.
UTF8/UTF16
Mit dem CPP-Backend können Sie wählen, ob Strings utf-8 oder utf-16 sind. Deklarieren Sie Zeichenfolgen als @utf8InCpp String
-Zeichenfolge in AIDL, um sie automatisch in utf-8 zu konvertieren. Die NDK- und Rust-Backends verwenden immer utf-8-Strings. Weitere Informationen zur Annotation utf8InCpp
finden Sie unter Annotations in AIDL .
Nullfähigkeit
Sie können Typen, die im Java-Back-End null sein können, mit @nullable
, um null-Werte für die CPP- und NDK-Back-Ends verfügbar zu machen. Im Rust-Backend werden diese @nullable
Typen als Option<T>
bereitgestellt. Native Server lehnen standardmäßig Nullwerte ab. Die einzigen Ausnahmen davon sind interface
und IBinder
Typen, die für NDK-Lesevorgänge und CPP/NDK-Schreibvorgänge immer null sein können. Weitere Informationen zur nullable
-Anmerkung finden Sie unter Anmerkungen in AIDL .
Benutzerdefinierte Parcelables
In den C++- und Java-Back-Ends im Core-Build-System können Sie ein Paket deklarieren, das manuell in einem Ziel-Back-End (in C++ oder in Java) implementiert wird.
package my.package;
parcelable Foo;
oder mit C++-Header-Deklaration:
package my.package;
parcelable Foo cpp_header "my/package/Foo.h";
Dann können Sie dieses Paket als Typ in AIDL-Dateien verwenden, aber es wird nicht von AIDL generiert.
Rust unterstützt keine benutzerdefinierten Parcelables.
Standardwerte
Strukturierte String
können pro Feld Standardwerte für Primitive, Strings und Arrays dieser Typen deklarieren.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
Wenn im Java-Backend Standardwerte fehlen, werden Felder für primitive Typen als null
und für nicht primitive Typen als Nullwerte initialisiert.
In anderen Backends werden Felder mit initialisierten Standardwerten initialisiert, wenn keine Standardwerte definiert sind. Beispielsweise werden im C++-Back-End String
-Felder als leere Zeichenfolge und List<T>
-Felder als leerer vector<T>
initialisiert. @nullable
-Felder werden als Nullwertfelder initialisiert.
Fehlerbehandlung
Das Android-Betriebssystem bietet integrierte Fehlertypen für Dienste, die beim Melden von Fehlern verwendet werden können. Diese werden von Binder verwendet und können von allen Diensten verwendet werden, die eine Binder-Schnittstelle implementieren. Ihre Verwendung ist in der AIDL-Definition gut dokumentiert und sie erfordern keinen benutzerdefinierten Status oder Rückgabetyp.
Wenn eine AIDL-Funktion einen Fehler meldet, kann die Funktion Ausgabeparameter nicht initialisieren oder ändern. Insbesondere können Ausgabeparameter geändert werden, wenn der Fehler während des Entpackens auftritt und nicht während der Verarbeitung der Transaktion selbst. Wenn Sie einen Fehler von einer AIDL-Funktion erhalten, sollten im Allgemeinen alle inout
und out
-Parameter sowie der Rückgabewert (der sich in einigen Backends wie ein out
-Parameter verhält) als in einem unbestimmten Zustand betrachtet werden.
Wenn die AIDL-Schnittstelle zusätzliche Fehlerwerte erfordert, die von den integrierten Fehlertypen nicht abgedeckt werden, können sie den speziellen dienstspezifischen integrierten Fehler verwenden, der die Einbeziehung eines dienstspezifischen Fehlerwerts ermöglicht, der vom Benutzer definiert wird . Diese dienstspezifischen Fehler werden in der Regel in der AIDL-Schnittstelle als const int
oder int
-backed enum
definiert und nicht von Binder geparst.
In Java werden Fehler Ausnahmen wie android.os.RemoteException
. Für dienstspezifische Ausnahmen verwendet Java android.os.ServiceSpecificException
zusammen mit dem benutzerdefinierten Fehler.
Nativer Code in Android verwendet keine Ausnahmen. Das CPP-Backend verwendet android::binder::Status
. Das NDK-Backend verwendet ndk::ScopedAStatus
. Jede von AIDL generierte Methode gibt eine davon zurück, die den Status der Methode darstellt. Das Rust-Backend verwendet dieselben Ausnahmecodewerte wie das NDK, wandelt sie jedoch in native Rust-Fehler ( StatusCode
, ExceptionCode
) um, bevor es sie an den Benutzer ausliefert. Für dienstspezifische Fehler verwendet der zurückgegebene Status
oder ScopedAStatus
EX_SERVICE_SPECIFIC
zusammen mit dem benutzerdefinierten Fehler.
Die eingebauten Fehlertypen finden Sie in den folgenden Dateien:
Backend | Definition |
---|---|
Java | android/os/Parcel.java |
CPP | binder/Status.h |
NDK | android/binder_status.h |
Rost | android/binder_status.h |
Verwendung verschiedener Backends
Diese Anweisungen beziehen sich speziell auf den Android-Plattformcode. Diese Beispiele verwenden einen definierten Typ my.package.IFoo
. Anweisungen zur Verwendung des Rust-Backends finden Sie im Rust-AIDL-Beispiel auf der Seite Android Rust Patterns .
Typen importieren
Unabhängig davon, ob der definierte Typ eine Schnittstelle, ein Paket oder eine Union ist, können Sie ihn in Java importieren:
import my.package.IFoo;
Oder im CPP-Backend:
#include <my/package/IFoo.h>
Oder im NDK-Backend (beachten Sie den zusätzlichen aidl
Namespace):
#include <aidl/my/package/IFoo.h>
Oder im Rust-Backend:
use my_package::aidl::my::package::IFoo;
Obwohl Sie einen verschachtelten Typ in Java importieren können, müssen Sie in den CPP/NDK-Backends den Header für seinen Stammtyp einschließen. Wenn Sie beispielsweise einen verschachtelten Typ Bar
importieren, der in my/package/IFoo.aidl
definiert ist ( IFoo
ist der Stammtyp der Datei), müssen Sie <my/package/IFoo.h>
für das CPP-Backend (oder <aidl/my/package/IFoo.h>
für das NDK-Backend).
Dienstleistungen implementieren
Um einen Dienst zu implementieren, müssen Sie von der nativen Stub-Klasse erben. Diese Klasse liest Befehle aus dem Binder-Treiber und führt die von Ihnen implementierten Methoden aus. Stellen Sie sich vor, Sie haben eine AIDL-Datei wie diese:
package my.package;
interface IFoo {
int doFoo();
}
In Java müssen Sie diese Klasse erweitern:
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
Im CPP-Backend:
#include <my/package/BnFoo.h>
class MyFoo : public my::package::BnFoo {
android::binder::Status doFoo(int32_t* out) override;
}
Im NDK-Backend (beachten Sie den zusätzlichen aidl
Namespace):
#include <aidl/my/package/BnFoo.h>
class MyFoo : public aidl::my::package::BnFoo {
ndk::ScopedAStatus doFoo(int32_t* out) override;
}
Im Rust-Backend:
use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFoo};
use binder;
/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyFoo;
impl Interface for MyFoo {}
impl IFoo for MyFoo {
fn doFoo(&self) -> binder::Result<()> {
...
Ok(())
}
}
Registrierung und Inanspruchnahme von Diensten
Dienste in der Plattform Android werden normalerweise mit dem servicemanager
-Prozess registriert. Zusätzlich zu den unten aufgeführten APIs prüfen einige APIs den Dienst (was bedeutet, dass sie sofort zurückkehren, wenn der Dienst nicht verfügbar ist). Genaue Details finden Sie in der entsprechenden servicemanager
-Oberfläche. Diese Operationen können nur durchgeführt werden, wenn für die Plattform Android kompiliert wird.
In Java:
import android.os.ServiceManager;
// registering
ServiceManager.addService("service-name", myService);
// getting
myService = IFoo.Stub.asInterface(ServiceManager.getService("service-name"));
// waiting until service comes up (new in Android 11)
myService = IFoo.Stub.asInterface(ServiceManager.waitForService("service-name"));
// waiting for declared (VINTF) service to come up (new in Android 11)
myService = IFoo.Stub.asInterface(ServiceManager.waitForDeclaredService("service-name"));
Im CPP-Backend:
#include <binder/IServiceManager.h>
// registering
defaultServiceManager()->addService(String16("service-name"), myService);
// getting
status_t err = getService<IFoo>(String16("service-name"), &myService);
// waiting until service comes up (new in Android 11)
myService = waitForService<IFoo>(String16("service-name"));
// waiting for declared (VINTF) service to come up (new in Android 11)
myService = waitForDeclaredService<IFoo>(String16("service-name"));
Im NDK-Backend (beachten Sie den zusätzlichen aidl
Namespace):
#include <android/binder_manager.h>
// registering
status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// getting
myService = IFoo::fromBinder(SpAIBinder(AServiceManager_getService("service-name")));
// is a service declared in the VINTF manifest
// VINTF services have the type in the interface instance name.
bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
// wait until a service is available (if isDeclared or you know it's available)
myService = IFoo::fromBinder(SpAIBinder(AServiceManager_waitForService("service-name")));
Im Rust-Backend:
use myfoo::MyFoo;
use binder;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_binder(
my_service,
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Does not return - spawn or perform any work you mean to do before this call.
binder::ProcessState::join_thread_pool()
}
Verbindung zum Tod
Sie können anfordern, eine Benachrichtigung zu erhalten, wenn ein Dienst, der einen Binder hostet, stirbt. Dies kann dazu beitragen, undichte Callback-Proxys zu vermeiden oder die Fehlerbehebung zu unterstützen. Führen Sie diese Aufrufe für Binder-Proxy-Objekte durch.
- Verwenden Sie in Java
android.os.IBinder::linkToDeath
. - Verwenden Sie im CPP-Backend
android::IBinder::linkToDeath
. - Verwenden Sie im NDK-Backend
AIBinder_linkToDeath
. - Erstellen Sie im Rust-Backend ein
DeathRecipient
Objekt und rufen Sie dannmy_binder.link_to_death(&mut my_death_recipient)
. Beachten Sie, dass Sie dieses Objekt so lange am Leben erhalten müssen, wie Sie Benachrichtigungen erhalten möchten, da derDeathRecipient
den Rückruf besitzt.
Fehlerberichte und Debugging-API für Dienste
Wenn Fehlerberichte ausgeführt werden (z. B. mit adb bugreport
), sammeln sie Informationen aus dem gesamten System, um beim Debuggen verschiedener Probleme zu helfen. Für AIDL-Dienste verwenden Fehlerberichte die Binärdatei dumpsys
für alle Dienste, die beim Dienstmanager registriert sind, um ihre Informationen in den Fehlerbericht zu schreiben. Sie können dumpsys
auch in der Befehlszeile verwenden, um Informationen von einem Dienst mit dumpsys SERVICE [ARGS]
. In den C++- und Java-Backends können Sie die Reihenfolge steuern, in der Dienste ausgegeben werden, indem Sie zusätzliche Argumente für addService
. Sie können auch dumpsys --pid SERVICE
verwenden, um die PID eines Dienstes während des Debuggens abzurufen.
Um Ihrem Dienst eine benutzerdefinierte Ausgabe hinzuzufügen, können Sie die dump
-Methode in Ihrem Serverobjekt überschreiben, als würden Sie jede andere IPC-Methode implementieren, die in einer AIDL-Datei definiert ist. Dabei sollten Sie das Dumping auf die App-Berechtigung android.permission.DUMP
beschränken oder das Dumping auf bestimmte UIDs beschränken.
Im Java-Backend:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
Im CPP-Backend:
status_t dump(int, const android::android::Vector<android::String16>&) override;
Im NDK-Backend:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
Geben Sie im Rust-Backend bei der Implementierung der Schnittstelle Folgendes an (anstatt es standardmäßig zuzulassen):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Schnittstellendeskriptor dynamisch abrufen
Der Schnittstellendeskriptor identifiziert den Typ einer Schnittstelle. Dies ist nützlich beim Debuggen oder wenn Sie einen unbekannten Binder haben.
In Java können Sie den Schnittstellendeskriptor mit folgendem Code abrufen:
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
Im CPP-Backend:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
Die NDK- und Rust-Backends unterstützen diese Funktionalität nicht.
Schnittstellendeskriptor statisch abrufen
Manchmal (z. B. beim Registrieren @VintfStability
Diensten) müssen Sie wissen, wie der Schnittstellendeskriptor statisch lautet. In Java können Sie den Deskriptor erhalten, indem Sie Code wie den folgenden hinzufügen:
import my.package.IFoo;
... IFoo.DESCRIPTOR
Im CPP-Backend:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
Im NDK-Backend (beachten Sie den zusätzlichen aidl
Namespace):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
Im Rust-Backend:
aidl::my::package::BnFoo::get_descriptor()
Enum-Bereich
In nativen Backends können Sie über die möglichen Werte iterieren, die eine Aufzählung annehmen kann. Aufgrund von Überlegungen zur Codegröße wird dies derzeit in Java nicht unterstützt.
Für eine in MyEnum
definierte Aufzählung MyEnum wird die Iteration wie folgt bereitgestellt.
Im CPP-Backend:
::android::enum_range<MyEnum>()
Im NDK-Backend:
::ndk::enum_range<MyEnum>()
Im Rust-Backend:
MyEnum::enum_range()
Thread-Verwaltung
Jede Instanz von libbinder
in einem Prozess verwaltet einen Threadpool. Für die meisten Anwendungsfälle sollte dies genau ein Threadpool sein, der von allen Backends gemeinsam genutzt wird. Die einzige Ausnahme hiervon ist, wenn Herstellercode eine andere Kopie von libbinder
laden könnte, um mit /dev/vndbinder
zu kommunizieren. Da sich dies auf einem separaten Binderknoten befindet, wird der Threadpool nicht gemeinsam genutzt.
Für das Java-Backend kann der Threadpool nur größer werden (da er bereits gestartet ist):
BinderInternal.setMaxThreads(<new larger value>);
Für das CPP-Backend sind die folgenden Operationen verfügbar:
// set max threadpool count (default is 15)
status_t err = ProcessState::self()->setThreadPoolMaxThreadCount(numThreads);
// create threadpool
ProcessState::self()->startThreadPool();
// add current thread to threadpool (adds thread to max thread count)
IPCThreadState::self()->joinThreadPool();
Ähnlich im NDK-Backend:
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
Im Rust-Backend:
binder::ProcessState::start_thread_pool();
binder::add_service(“myservice”, my_service_binder).expect(“Failed to register service?”);
binder::ProcessState::join_thread_pool();