Ein AIDL-Back-End ist ein Ziel für die Generierung von Stub-Code. AIDL-Dateien werden immer in einer bestimmten Sprache mit einer bestimmten Laufzeit verwendet. Je nach Kontext sollten Sie unterschiedliche AIDL-Backends verwenden.
In der folgenden Tabelle bezieht sich die Stabilität der API-Oberfläche auf die Möglichkeit, Code für diese API-Oberfläche so zu kompilieren, dass er unabhängig vom system.img
-libbinder.so
-Binärcode bereitgestellt werden kann.
AIDL hat die folgenden Backends:
Back-End | Sprache | API-Oberfläche | Build-Systeme |
---|---|---|---|
Java | Java | SDK/SystemApi (stabil*) | alle |
NDK | C++ | libbinder_ndk (stabil*) | aidl_interface |
CPP | C++ | libbinder (instabil) | alle |
Rust | Rust | libbinder_rs (stabil*) | aidl_interface |
- Diese API-Oberflächen sind stabil, aber viele der APIs, z. B. für die Dienstverwaltung, sind für die interne Nutzung der Plattform reserviert und nicht für Apps verfügbar. 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.
- Der Rust-Chrom ist auf
libbinder_ndk
aufgebaut, wodurch er stabil und portabel ist. APEX-Objekte verwenden den Binder-Container genauso wie alle anderen Nutzer auf der Systemseite. Der Rust-Teil wird in einem APEX-Archiv verpackt und versendet. Das hängt von derlibbinder_ndk.so
auf der Systempartition ab.
Build-Systeme
Je nach Backend gibt es zwei Möglichkeiten, AIDL in Stub-Code zu kompilieren. Weitere Informationen zu den Build-Systemen finden Sie in der Soong-Modulreferenz.
Kern-Build-System
In jedem cc_
- oder java_
-Android.bp-Modul (oder in den entsprechenden Android.mk
-Dateien) können .aidl
-Dateien als Quelldateien angegeben werden. In diesem Fall werden die Java/C++-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 Stammpfad zu den AIDL-Dateien in diesem Modul angeben, können in diesen Modulen in einer aidl:
-Gruppe angegeben werden. Das Rust-Backend ist nur für die Verwendung mit Rust vorgesehen. rust_
-Module werden anders behandelt, da AIDL-Dateien nicht als Quelldateien angegeben werden.
Stattdessen generiert das aidl_interface
-Modul eine rustlib
namens <aidl_interface name>-rust
, mit der eine Verknüpfung hergestellt werden kann. Weitere Informationen finden Sie im Beispiel für Rust-AIDL.
aidl_interface
Die mit diesem Build-System verwendeten Typen müssen strukturiert sein. Damit sie strukturiert sind, müssen Parcelable-Objekte Felder direkt enthalten und keine Deklarationen von Typen sein, die direkt in Zielsprachen definiert sind. Weitere Informationen dazu, wie strukturiertes AIDL in stabiles AIDL passt, finden Sie unter Strukturiertes und stabiles AIDL im Vergleich.
Typen
Sie können den aidl
-Compiler als Referenzimplementierung für Typen betrachten.
Wenn Sie eine Benutzeroberfläche erstellen, rufen Sie aidl --lang=<backend> ...
auf, um die resultierende Benutzeroberflächendatei aufzurufen. Wenn Sie das aidl_interface
-Modul verwenden, können Sie die Ausgabe in out/soong/.intermediates/<path to module>/
ansehen.
Java-/AIDL-Typ | C++-Typ | NDK-Typ | Rosttyp |
---|---|---|---|
Boolesch | bool | bool | bool |
Byte8 | int8_t | int8_t | i8 |
char | char16_t | char16_t | u16 |
int | int32_t | int32_t | i32 |
long | int64_t | int64_t | i64 |
float | float | float | f32 |
Doppelt | Doppelt | Doppelt | f64 |
String | android::String16 | std::string | Eingabe: &str Ausgabe: String |
android.os.Parcelable | android::Parcelable | – | – |
IBinder | android::IBinder | ndk::SpAIBinder | binder::SpIBinder |
T[] | std::vector<T> | std::vector<T> | Eingabe: &[T] Ausgabe: Vec<T> |
byte[] | std::vector<uint8_t> | std::vector<int8_t>1 | Eingabe: &[u8] Ausgabe: Vec<u8> |
List<T> | std::vector<T>2 | std::vector<T>3 | In: &[T]4 Aus: Vec<T> |
FileDescriptor | android::base::unique_fd | – | binder::parcel::ParcelFileDescriptor |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | binder::parcel::ParcelFileDescriptor |
interface type (T) | android::sp<T> | std::shared_ptr<T>7 | binder::Strong |
Typ „Parcelable“ (T) | T | T | T |
Union-Typ (T)5 | T | T | T |
T[N] 6 | std::array<T, N> | std::array<T, N> | [T; N] |
1. Unter Android 12 oder höher werden für Byte-Arrays aus Kompatibilitätsgründen uint8_t anstelle von int8_t verwendet.
2. Das C++-Backend unterstützt List<T>
, wobei T
eine der folgenden Optionen sein kann: String
, IBinder
, ParcelFileDescriptor
oder parcelable. Unter Android 13 und höher kann T
jeder nicht primitive Typ (einschließlich Schnittstellentypen) sein, mit Ausnahme von Arrays. AOSP empfiehlt die Verwendung von Arraytypen wie T[]
, da sie in allen Back-Ends funktionieren.
3. Das NDK-Backend unterstützt List<T>
, wobei T
eine der folgenden Optionen sein kann: String
, ParcelFileDescriptor
oder parcelable. Unter Android 13 oder höher kann T
jeder nicht primitive Typ sein, mit Ausnahme von Arrays.
4. Typen werden in Rust-Code unterschiedlich übergeben, je nachdem, ob es sich um Eingabe (ein Argument) oder Ausgabe (einen zurückgegebenen Wert) handelt.
5. Union-Typen werden unter Android 12 und höher unterstützt.
6. Unter Android 13 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 mit fester Größe als Arraytypen dargestellt.
7. Verwenden Sie SharedRefBase::make\<My\>(... args ...)
, um ein Binder-SharedRefBase
-Objekt zu instanziieren. Diese Funktion erstellt ein std::shared_ptr\<T\>
-Objekt, das auch intern verwaltet wird, falls der Binder einem anderen Prozess gehört. Wenn Sie das Objekt auf andere Weise erstellen, führt das zu doppelten Eigentumsrechten.
8. Siehe auch Java-/AIDL-Typ byte[]
.
Richtung (in/out/inout)
Wenn Sie die Typen der Argumente für Funktionen angeben, können Sie in
, out
oder inout
angeben. Damit wird festgelegt, in welche Richtung Informationen für einen IPC-Aufruf übergeben werden. in
ist die Standardrichtung und gibt an, dass Daten vom Aufrufer an den Gerufenen übergeben werden. out
bedeutet, dass Daten vom Angerufenen an den Anrufer übergeben werden. inout
ist eine Kombination aus beiden. Das Android-Team empfiehlt jedoch, den Argument-Spezifizierer inout
zu vermeiden.
Wenn Sie inout
mit einer versionierten Benutzeroberfläche und einem älteren Aufgerufenen verwenden, werden die zusätzlichen Felder, die nur im Aufrufer vorhanden sind, auf die Standardwerte zurückgesetzt. In Rust erhält ein normaler inout
-Typ &mut Vec<T>
und ein Listeninout
-Typ &mut Vec<T>
.
interface IRepeatExamples {
MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
MyParcelable RepeatParcelableWithIn(in MyParcelable token);
void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
void RepeatParcelableWithInOut(inout MyParcelable param);
}
UTF8/UTF16
Mit dem CPP-Back-End können Sie auswählen, ob Strings in UTF-8 oder UTF-16 vorliegen. Deklarieren Sie Strings in AIDL als @utf8InCpp String
, um sie automatisch in UTF-8 umzuwandeln.
Die NDK- und Rust-Backends verwenden immer UTF-8-Strings. Weitere Informationen zur utf8InCpp
-Anmerkung finden Sie unter Anmerkungen in AIDL.
Null-Zulässigkeit
Typen, die null sein können, können Sie mit @nullable
annotieren.
Weitere Informationen zur nullable
-Anmerkung finden Sie unter Anmerkungen in AIDL.
Benutzerdefinierte Pakete
Ein benutzerdefiniertes Parcelable ist ein Parcelable, das manuell in einem Ziel-Backend implementiert wird. Verwenden Sie benutzerdefinierte Parcelable-Objekte nur, wenn Sie für ein vorhandenes benutzerdefiniertes Parcelable-Objekt, das nicht geändert werden kann, Unterstützung für andere Sprachen hinzufügen möchten.
Um ein benutzerdefiniertes Parcelable zu deklarieren, damit es von AIDL erkannt wird, sieht die Parcelable-Deklaration in AIDL so aus:
package my.pack.age;
parcelable Foo;
Standardmäßig wird dadurch ein Java-Parcelable deklariert, bei dem my.pack.age.Foo
eine Java-Klasse ist, die die Schnittstelle Parcelable
implementiert.
Verwenden Sie cpp_header
, um ein benutzerdefiniertes CPP-Backend in AIDL zu deklarieren:
package my.pack.age;
parcelable Foo cpp_header "my/pack/age/Foo.h";
Die C++-Implementierung in my/pack/age/Foo.h
sieht so aus:
#include <binder/Parcelable.h>
class MyCustomParcelable : public android::Parcelable {
public:
status_t writeToParcel(Parcel* parcel) const override;
status_t readFromParcel(const Parcel* parcel) override;
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
Verwenden Sie ndk_header
, um eine benutzerdefinierte NDK-Parcelable-Klasse in AIDL zu deklarieren:
package my.pack.age;
parcelable Foo ndk_header "android/pack/age/Foo.h";
Die NDK-Implementierung in android/pack/age/Foo.h
sieht so aus:
#include <android/binder_parcel.h>
class MyCustomParcelable {
public:
binder_status_t writeToParcel(AParcel* _Nonnull parcel) const;
binder_status_t readFromParcel(const AParcel* _Nonnull parcel);
std::string toString() const;
friend bool operator==(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
friend bool operator!=(const MyCustomParcelable& lhs, const MyCustomParcelable& rhs);
};
In Android 15 verwenden Sie für die Deklaration einer benutzerdefinierten Rust-Parcelable in AIDL rust_type
:
package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";
Die Rust-Implementierung in rust_crate/src/lib.rs
sieht so aus:
use binder::{
binder_impl::{BorrowedParcel, UnstructuredParcelable},
impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
StatusCode,
};
#[derive(Clone, Debug, Eq, PartialEq)]
struct Foo {
pub bar: String,
}
impl UnstructuredParcelable for Foo {
fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
parcel.write(&self.bar)?;
Ok(())
}
fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
let bar = parcel.read()?;
Ok(Self { bar })
}
}
impl_deserialize_for_unstructured_parcelable!(Foo);
impl_serialize_for_unstructured_parcelable!(Foo);
Sie können diese Parcelable dann als Typ in AIDL-Dateien verwenden, sie wird jedoch nicht von AIDL generiert. Biete <
- und ==
-Operatoren für benutzerdefinierte Parcelables des CPP/NDK-Backends an, um sie in union
zu verwenden.
Standardwerte
Strukturierte Parcelable-Objekte können pro Feld Standardwerte für primitive Typen, String
s und Arrays dieser Typen angeben.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
Wenn im Java-Backend keine Standardwerte vorhanden sind, werden Felder als Nullwerte für primitive Typen und als null
für nicht primitive Typen initialisiert.
In anderen Back-Ends werden Felder mit Standardwerten initialisiert, wenn keine Standardwerte definiert sind. Im C++-Backend werden String
-Felder beispielsweise als leerer String und List<T>
-Felder als leere vector<T>
initialisiert. @nullable
-Felder werden als Felder mit Nullwert initialisiert.
Gewerkschaften
AIDL-Unions sind getaggt und ihre Funktionen sind in allen Back-Ends ähnlich. Sie werden standardmäßig mit dem Standardwert des ersten Felds erstellt und es gibt eine sprachspezifische Möglichkeit, mit ihnen zu interagieren.
union Foo {
int intField;
long longField;
String stringField;
MyParcelable parcelableField;
...
}
Java-Beispiel
Foo u = Foo.intField(42); // construct
if (u.getTag() == Foo.intField) { // tag query
// use u.getIntField() // getter
}
u.setSringField("abc"); // setter
Beispiel für C++ und NDK
Foo u; // default constructor
assert (u.getTag() == Foo::intField); // tag query
assert (u.get<Foo::intField>() == 0); // getter
u.set<Foo::stringField>("abc"); // setter
assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)
Beispiel für Rost
In Rust werden Unionen als enums implementiert und haben keine expliziten Getter und Setter.
let mut u = Foo::Default(); // default constructor
match u { // tag match + get
Foo::IntField(x) => assert!(x == 0);
Foo::LongField(x) => panic!("Default constructed to first field");
Foo::StringField(x) => panic!("Default constructed to first field");
Foo::ParcelableField(x) => panic!("Default constructed to first field");
...
}
u = Foo::StringField("abc".to_string()); // set
Fehlerbehandlung
Das Android-Betriebssystem bietet integrierte Fehlertypen für Dienste, die beim Melden von Fehlern verwendet werden können. Sie 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.
Ausgabeparameter mit Fehlern
Wenn eine AIDL-Funktion einen Fehler meldet, werden die Ausgabeparameter möglicherweise nicht initialisiert oder geändert. Insbesondere können Ausgabeparameter geändert werden, wenn der Fehler beim Entpacken auftritt, anstatt bei der Verarbeitung der Transaktion selbst. Wenn Sie von einer AIDL-Funktion einen Fehler erhalten, sollten Sie davon ausgehen, dass alle inout
- und out
-Parameter sowie der Rückgabewert (der in einigen Back-Ends wie ein out
-Parameter funktioniert) sich in einem unbestimmten Zustand befinden.
Welche Fehlerwerte zu verwenden sind
Viele der vordefinierten Fehlerwerte können in allen AIDL-Schnittstellen verwendet werden, einige werden jedoch auf besondere Weise behandelt. EX_UNSUPPORTED_OPERATION
und EX_ILLEGAL_ARGUMENT
sind beispielsweise zulässig, wenn sie die Fehlerbedingung beschreiben. EX_TRANSACTION_FAILED
darf jedoch nicht verwendet werden, da es von der zugrunde liegenden Infrastruktur speziell behandelt wird. Weitere Informationen zu diesen vordefinierten Werten finden Sie in den backendspezifischen Definitionen.
Wenn für die AIDL-Schnittstelle zusätzliche Fehlerwerte erforderlich sind, die nicht von den integrierten Fehlertypen abgedeckt werden, kann der spezielle dienstspezifische integrierte Fehler verwendet werden, der die Aufnahme eines vom Nutzer definierten dienstspezifischen Fehlerwerts ermöglicht. Diese dienstspezifischen Fehler werden in der Regel in der AIDL-Schnittstelle als enum
mit const int
- oder int
-Unterstützung definiert und nicht vom Binder geparst.
In Java werden Fehler Ausnahmen zugeordnet, z. B. android.os.RemoteException
. Für servicespezifische Ausnahmen verwendet Java android.os.ServiceSpecificException
zusammen mit dem benutzerdefinierten Fehler.
Für nativen Code in Android werden keine Ausnahmen verwendet. Das CPP-Backend verwendet android::binder::Status
. Das NDK-Backend verwendet ndk::ScopedAStatus
. Jede von AIDL generierte Methode gibt einen dieser Status zurück. Das Rust-Backend verwendet dieselben Ausnahmecodewerte wie das NDK, wandelt sie jedoch in native Rust-Fehler (StatusCode
, ExceptionCode
) um, bevor sie an den Nutzer gesendet werden. Bei dienstspezifischen Fehlern wird für die zurückgegebene Status
oder ScopedAStatus
die EX_SERVICE_SPECIFIC
zusammen mit dem benutzerdefinierten Fehler verwendet.
Die integrierten Fehlertypen finden Sie in den folgenden Dateien:
Back-End | Definition |
---|---|
Java | android/os/Parcel.java |
CPP | binder/Status.h |
NDK | android/binder_status.h |
Rust | android/binder_status.h |
Verschiedene Back-Ends verwenden
Diese Anleitung bezieht sich auf Android-Plattformcode. In diesen Beispielen wird der definierte Typ my.package.IFoo
verwendet. Eine Anleitung zur Verwendung des Rust-Backends finden Sie im Rust-AIDL-Beispiel auf der Seite Android Rust-Muster.
Importtypen
Unabhängig davon, ob der definierte Typ eine Schnittstelle, ein Parcelable 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 Namespace aidl
):
#include <aidl/my/package/IFoo.h>
Oder im Rust-Backend:
use my_package::aidl::my::package::IFoo;
Sie können einen verschachtelten Typ zwar in Java importieren, in den CPP/NDK-Backends müssen Sie jedoch den Header für den 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) angeben.
Dienste implementieren
Wenn Sie einen Dienst implementieren möchten, müssen Sie von der nativen Stub-Klasse ableiten. Diese Klasse liest Befehle vom Binder-Treiber und führt die von Ihnen implementierten Methoden aus. Angenommen, 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 Namespace aidl
):
#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(())
}
}
Oder mit async Rust:
use aidl_interface_name::aidl::my::package::IFoo::{BnFoo, IFooAsyncServer};
use binder;
/// This struct is defined to implement IRemoteService AIDL interface.
pub struct MyFoo;
impl Interface for MyFoo {}
#[async_trait]
impl IFooAsyncServer for MyFoo {
async fn doFoo(&self) -> binder::Result<()> {
...
Ok(())
}
}
Registrieren und Dienste nutzen
Dienste in der Android-Plattform werden in der Regel mit dem Prozess servicemanager
registriert. Zusätzlich zu den folgenden APIs prüfen einige APIs den Dienst. Das bedeutet, dass sie sofort zurückgegeben werden, wenn der Dienst nicht verfügbar ist.
Genaue Informationen findest du in der entsprechenden servicemanager
-Benutzeroberfläche. Diese Vorgänge können nur ausgeführt werden, wenn die Kompilierung für die Plattform Android erfolgt.
In Java:
import android.os.ServiceManager;
// registering
ServiceManager.addService("service-name", myService);
// return if service is started now
myService = IFoo.Stub.asInterface(ServiceManager.checkService("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);
// return if service is started now
status_t err = checkService<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 Namespace aidl
):
#include <android/binder_manager.h>
// registering
binder_exception_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
// return if service is started now
myService = IFoo::fromBinder(ndk::SpAIBinder(AServiceManager_checkService("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(ndk::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()
}
Im asynchronen Rust-Backend mit einer Single-Threaded-Laufzeit:
use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
#[tokio::main(flavor = "current_thread")]
async fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Sleeps forever, but does not join the binder threadpool.
// Spawned tasks will run on this thread.
std::future::pending().await
}
Ein wichtiger Unterschied zu den anderen Optionen besteht darin, dass wir join_thread_pool
nicht aufrufen, wenn wir async Rust und eine einzeilige Laufzeit verwenden. Das liegt daran, dass Sie Tokio einen Thread angeben müssen, in dem die gestarteten Aufgaben ausgeführt werden können. In diesem Beispiel dient dazu der Haupt-Thread. Alle Aufgaben, die mit tokio::spawn
gestartet werden, werden im Haupt-Thread ausgeführt.
Im asynchronen Rust-Backend mit einer mehrstufigen Laufzeit:
use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;
#[tokio::main(flavor = "multi_thread", worker_threads = 2)]
async fn main() {
binder::ProcessState::start_thread_pool();
// [...]
let my_service = MyFoo;
let my_service_binder = BnFoo::new_async_binder(
my_service,
TokioRuntime(Handle::current()),
BinderFeatures::default(),
);
binder::add_service("myservice", my_service_binder).expect("Failed to register service?");
// Sleep forever.
tokio::task::block_in_place(|| {
binder::ProcessState::join_thread_pool();
});
}
Bei der mehrstufigen Tokio-Laufzeit werden erzeugte Aufgaben nicht im Hauptthread ausgeführt. Daher ist es sinnvoller, join_thread_pool
im Hauptthread aufzurufen, damit der Hauptthread nicht nur inaktiv ist. Sie müssen den Aufruf in block_in_place
einschließen, um den asynchronen Kontext zu verlassen.
Link zum Todesfall
Sie können eine Benachrichtigung erhalten, wenn ein Dienst, der einen Binder hostet, ausfällt. So lassen sich Lecks von Rückruf-Proxys vermeiden oder Fehler leichter beheben. Führe diese Aufrufe auf Binder-Proxy-Objekten aus.
- In Java verwenden Sie
android.os.IBinder::linkToDeath
. - Verwende im CPP-Backend
android::IBinder::linkToDeath
. - Verwenden Sie im NDK-Backend
AIBinder_linkToDeath
. - Erstelle im Rust-Backend ein
DeathRecipient
-Objekt und rufe dannmy_binder.link_to_death(&mut my_death_recipient)
auf. Da derDeathRecipient
-Callback demDeathRecipient
-Objekt zugewiesen ist, muss dieses Objekt so lange aktiv bleiben, wie du Benachrichtigungen erhalten möchtest.
Anruferinformationen
Wenn ein Kernel-Binder-Aufruf empfangen wird, sind Informationen zum Anrufer in mehreren APIs verfügbar. Die PID (Prozess-ID) bezieht sich auf die Linux-Prozess-ID des Prozesses, der eine Transaktion sendet. Die UID (Nutzer-ID) bezieht sich auf die Linux-Nutzer-ID. Beim Empfangen eines einseitigen Anrufs ist die anrufende PID 0. Außerhalb eines Binder-Transaktionskontexts geben diese Funktionen die PID und UID des aktuellen Prozesses zurück.
Im Java-Backend:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
Im CPP-Backend:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
Im NDK-Backend:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
Geben Sie beim Implementieren der Schnittstelle im Rust-Backend Folgendes an (anstatt den Standardwert zuzulassen):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
Fehlerberichte und Debugging-API für Dienste
Wenn Fehlerberichte ausgeführt werden (z. B. mit adb bugreport
), werden Informationen aus dem gesamten System erfasst, um verschiedene Probleme zu beheben.
Bei AIDL-Diensten wird in Fehlerberichten das Binärformat dumpsys
für alle beim Dienstmanager registrierten Dienste verwendet, um die Informationen in den Fehlerbericht zu übertragen. Sie können auch dumpsys
in der Befehlszeile verwenden, um Informationen von einem Dienst mit dumpsys SERVICE [ARGS]
abzurufen. In den C++- und Java-Backends können Sie die Reihenfolge, in der Dienste gedumpt werden, mithilfe zusätzlicher Argumente für addService
steuern. Sie können dumpsys --pid SERVICE
auch verwenden, um während des Debuggens die PID eines Dienstes abzurufen.
Wenn Sie Ihrem Dienst benutzerdefinierte Ausgabe hinzufügen möchten, können Sie die dump
-Methode in Ihrem Serverobjekt überschreiben, so wie Sie jede andere in einer AIDL-Datei definierte IPC-Methode implementieren. Dabei sollten Sie das Dumping auf die App-Berechtigung android.permission.DUMP
oder 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 beim Implementieren der Schnittstelle im Rust-Backend Folgendes an (anstatt den Standardwert zuzulassen):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Schwache Zeiger verwenden
Sie können einen schwachen Verweis auf ein Binder-Objekt halten.
Java unterstützt zwar WeakReference
, aber keine schwachen Binderreferenzen in der nativen Schicht.
Im CPP-Backend ist der schwache Typ wp<IFoo>
.
Verwenden Sie im NDK-Backend ScopedAIBinder_Weak
:
#include <android/binder_auto_utils.h>
AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));
Im Rust-Backend verwenden Sie WpIBinder
oder Weak<IFoo>
:
let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();
Interface-Deskriptor dynamisch abrufen
Der Interface-Deskriptor gibt den Typ einer Schnittstelle an. Das ist hilfreich beim Debuggen oder wenn Sie einen unbekannten Binder haben.
In Java können Sie den Interface-Descriptor 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();
Das NDK- und das Rust-Backend unterstützen diese Funktion nicht.
Schnittstellendeskriptor statisch abrufen
Manchmal (z. B. bei der Registrierung von @VintfStability
-Diensten) müssen Sie wissen, was der Interface-Beschreibungsblock statisch ist. In Java können Sie den Descriptor mit folgendem Code abrufen:
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 Namespace aidl
):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
Im Rust-Backend:
aidl::my::package::BnFoo::get_descriptor()
Enum-Bereich
In nativen Back-Ends können Sie über die möglichen Werte eines Enumerationstyps iterieren. Aus Gründen der Codegröße wird dies in Java nicht unterstützt.
Für ein in AIDL definiertes Enum MyEnum
wird die Iteration so bereitgestellt:
Im CPP-Backend:
::android::enum_range<MyEnum>()
Im NDK-Backend:
::ndk::enum_range<MyEnum>()
Im Rust-Backend:
MyEnum::enum_values()
Threadverwaltung
Jede Instanz von libbinder
in einem Prozess verwaltet einen Threadpool. Für die meisten Anwendungsfälle sollte dies genau ein Threadpool sein, der für alle Back-Ends freigegeben ist.
Die einzige Ausnahme ist, wenn der Anbietercode eine weitere Kopie von libbinder
lädt, um mit /dev/vndbinder
zu kommunizieren. Da sich dieser Vorgang auf einem separaten Binderknoten befindet, wird der Threadpool nicht gemeinsam genutzt.
Beim Java-Backend kann der Threadpool nur vergrößert werden, da er bereits gestartet ist:
BinderInternal.setMaxThreads(<new larger value>);
Für das CPP-Backend sind die folgenden Vorgänge 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();
Ähnliches gilt für das 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();
Für das asynchrone Rust-Backend benötigen Sie zwei Threadpools: binder und Tokio.
Das bedeutet, dass bei Apps mit async Rust besondere Überlegungen angestellt werden müssen, insbesondere bei der Verwendung von join_thread_pool
. Weitere Informationen finden Sie im Abschnitt zum Registrieren von Diensten.
Reservierte Namen
In C++, Java und Rust sind einige Namen als Keywords oder für die sprachspezifische Verwendung reserviert. In AIDL werden keine Einschränkungen auf der Grundlage von Sprachregeln erzwungen. Die Verwendung von Feld- oder Typennamen, die mit einem reservierten Namen übereinstimmen, kann jedoch zu einem Kompilierungsfehler in C++ oder Java führen. In Rust wird das Feld oder der Typ mit der Syntax „rohe Kennung“ umbenannt, auf die über das Präfix r#
zugegriffen werden kann.
Wir empfehlen, nach Möglichkeit keine reservierten Namen in Ihren AIDL-Definitionen zu verwenden, um unergonomische Bindungen oder einen vollständigen Kompilierungsfehler zu vermeiden.
Wenn Sie in Ihren AIDL-Definitionen bereits reservierte Namen haben, können Sie Felder gefahrlos umbenennen und gleichzeitig protokollkompatibel bleiben. Möglicherweise müssen Sie Ihren Code aktualisieren, um mit dem Erstellen fortzufahren. Bereits erstellte Programme sind jedoch weiterhin interoperabel.
Zu vermeidende Namen: