Ein AIDL-Backend ist ein Ziel für die Generierung von Stub-Code. Verwenden Sie AIDL-Dateien in einer bestimmten Sprache immer mit einer bestimmten Laufzeitumgebung. 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 der Code unabhängig von der system.img
-Binärdatei libbinder.so
bereitgestellt werden kann.
AIDL hat die folgenden Back-Ends:
Back-End | Sprache | API-Oberfläche | Build-Systeme |
---|---|---|---|
Java | Java | SDK oder 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 Plattformnutzung reserviert und für Apps nicht verfügbar. Weitere Informationen zur Verwendung von AIDL in Apps finden Sie unter Android Interface Definition Language (AIDL).
- Das Rust-Backend wurde in Android 12 eingeführt. Das NDK-Backend ist seit Android 10 verfügbar.
- Die Rust-Crate basiert auf
libbinder_ndk
und ist daher stabil und portabel. APEXes verwenden die Binder-Crate auf der Systemseite auf Standardweise. Der Rust-Teil ist in einem APEX gebündelt und wird darin ausgeliefert. Dieser Teil hängt vonlibbinder_ndk.so
auf der Systempartition ab.
Build-Systeme
Abhängig vom Backend gibt es zwei Möglichkeiten, AIDL in Stub-Code zu kompilieren. Weitere Informationen zu den Build-Systemen finden Sie in der Soong Modules Reference.
Kern-Build-System
In allen cc_
- oder java_
-Android.bp module
-Dateien (oder ihren Android.mk
-Entsprechungen) können Sie AIDL-Dateien (.aidl
) als Quelldateien angeben. In diesem Fall werden die Java- oder CPP-Back-Ends von AIDL verwendet (nicht das NDK-Back-End) und die Klassen zur Verwendung der entsprechenden AIDL-Dateien werden dem Modul automatisch hinzugefügt. In diesen Modulen können Sie unter einer aidl:
-Gruppe Optionen wie local_include_dirs
angeben, die dem Build-System den Root-Pfad zu AIDL-Dateien in diesem Modul mitteilt.
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 wird im aidl_interface
-Modul ein rustlib
namens aidl_interface_name-rust
erstellt, das verknüpft werden kann. Weitere Informationen finden Sie im Rust-AIDL-Beispiel.
aidl_interface
Typen, die mit dem aidl_interface
-Build-System verwendet werden, müssen strukturiert sein. Damit sie strukturiert sind, müssen Parcelables Felder direkt enthalten und dürfen keine Deklarationen von Typen sein, die direkt in Zielsprachen definiert sind. Informationen dazu, wie sich strukturiertes AIDL in stabiles AIDL einfügt, finden Sie unter Strukturiertes und stabiles AIDL.
Typen
Der aidl
-Compiler kann als Referenzimplementierung für Typen betrachtet werden.
Wenn Sie eine Schnittstelle erstellen, rufen Sie aidl --lang=<backend> ...
auf, um die resultierende Schnittstellendatei zu sehen. Wenn Sie das aidl_interface
-Modul verwenden, können Sie die Ausgabe in out/soong/.intermediates/<path to module>/
ansehen.
Java- oder AIDL-Typ | C++-Typ | NDK-Typ | Rosttyp |
---|---|---|---|
boolean |
bool |
bool |
bool |
byte 8 |
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 |
double |
double |
double |
f64 |
String |
android::String16 |
std::string |
In: &str Out: String |
android.os.Parcelable |
android::Parcelable |
– | – |
IBinder |
android::IBinder |
ndk::SpAIBinder |
binder::SpIBinder |
T[] |
std::vector<T> |
std::vector<T> |
In: &[T] Out: Vec<T> |
byte[] |
std::vector |
std::vector 1 |
In: &[u8] Out: Vec<u8> |
List<T> |
std::vector<T> 2 |
std::vector<T> 3 |
In: In: &[T] 4Out: Vec<T> |
FileDescriptor |
android::base::unique_fd |
– | – |
ParcelFileDescriptor |
android::os::ParcelFileDescriptor |
ndk::ScopedFileDescriptor |
binder::parcel::ParcelFileDescriptor |
Schnittstellentyp (T ) |
android::sp<T> |
std::shared_ptr<T> 7 |
binder::Strong |
Parcelable-Typ (T ) |
T |
T |
T |
Unionstyp (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
eines der folgenden Elemente ist: String
, IBinder
, ParcelFileDescriptor
oder „parcelable“. In Android 13 oder höher kann T
ein beliebiger nicht primitiver Typ (einschließlich Schnittstellentypen) außer Arrays sein. AOSP empfiehlt die Verwendung von Arraytypen wie T[]
, da sie in allen Backends funktionieren.
3. Das NDK-Backend unterstützt List<T>
, wobei T
eines der folgenden Elemente ist: String
, ParcelFileDescriptor
oder „parcelable“. In Android 13 oder höher kann T
ein beliebiger nicht primitiver Typ außer Arrays sein.
4. Typen werden für Rust-Code unterschiedlich übergeben, je nachdem, ob sie eine Eingabe (ein Argument) oder eine Ausgabe (ein zurückgegebener Wert) sind.
5. Union-Typen werden in Android 12 und höher unterstützt.
6. In 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 Array-Typen dargestellt.
7. Verwenden Sie SharedRefBase::make\<My\>(... args ...)
, um ein Binder-Objekt SharedRefBase
zu instanziieren. Mit dieser Funktion wird ein std::shared_ptr\<T\>
-Objekt erstellt, das auch intern verwaltet wird, falls der Binder einem anderen Prozess gehört. Wenn Sie das Objekt auf andere Weise erstellen, kommt es zu einer doppelten Inhaberschaft.
8. Siehe auch Java- oder AIDL-Typ byte[]
.
Richtung (in, out und inout)
Wenn Sie die Typen der Argumente für Funktionen angeben, können Sie sie als in
, out
oder inout
angeben. Damit wird die Richtung gesteuert, in die Informationen für einen IPC-Aufruf übergeben werden.
Der Argumentbezeichner
in
gibt an, dass Daten vom Aufrufer an den Aufgerufenen übergeben werden. Der Spezifiziererin
ist die Standardrichtung. Wenn Datentypen aber auchout
sein können, müssen Sie die Richtung angeben.Der Argumentbezeichner
out
bedeutet, dass Daten vom Aufgerufenen an den Aufrufer übergeben werden.Die Argumentspezifikation
inout
ist die Kombination aus beiden. Wir empfehlen jedoch, den Argumentspezifiziererinout
nicht zu verwenden. Wenn Sieinout
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 normalerinout
-Typ&mut T
und eininout
-Listentyp&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);
}
UTF-8 und UTF-16
Mit dem CPP-Backend können Sie auswählen, ob Strings UTF-8 oder UTF-16 sein sollen.
Deklarieren Sie Strings in AIDL als @utf8InCpp String
, um sie automatisch in UTF-8 zu konvertieren. Die NDK- und Rust-Back-Ends verwenden immer UTF-8-Strings. Weitere Informationen zur Annotation utf8InCpp
finden Sie unter utf8InCpp.
Null-Zulässigkeit
Sie können Typen, die null sein können, mit @nullable
annotieren.
Weitere Informationen zur Annotation nullable
finden Sie unter nullable.
Benutzerdefinierte Parcelables
Ein benutzerdefiniertes Parcelable ist ein Parcelable, das manuell in einem Ziel-Backend implementiert wird. Verwenden Sie benutzerdefinierte Parcelables nur, wenn Sie Unterstützung für andere Sprachen für ein vorhandenes benutzerdefiniertes Parcelable hinzufügen möchten, das nicht geändert werden kann.
Hier ein Beispiel für eine AIDL-Parcelable-Deklaration:
package my.pack.age;
parcelable Foo;
Standardmäßig wird damit ein Java-Parcelable deklariert, wobei my.pack.age.Foo
eine Java-Klasse ist, die die Parcelable
-Schnittstelle implementiert.
Verwenden Sie cpp_header
, um ein benutzerdefiniertes CPP-Backend-Parcelable 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
für die Deklaration eines benutzerdefinierten NDK-Parcelable in AIDL:
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 wird für die Deklaration eines benutzerdefinierten Rust-Parcelable in AIDL rust_type
verwendet:
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);
Anschließend können Sie dieses Parcelable als Typ in AIDL-Dateien verwenden, es wird aber nicht von AIDL generiert. Stellen Sie die Operatoren <
und ==
für benutzerdefinierte Parcelables für das CPP- und NDK-Backend bereit, damit sie in union
verwendet werden können.
Standardwerte
Für strukturierte Parcelables können Standardwerte für Primitiven, String
-Felder und Arrays dieser Typen deklariert werden.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
Im Java-Backend werden Felder ohne Standardwerte als Nullwerte für primitive Typen und als null
für nicht primitive Typen initialisiert.
In anderen Back-Ends werden Felder mit standardmäßig initialisierten Werten 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 Nullwerten initialisiert.
Gewerkschaften
AIDL-Unions werden getaggt und ihre Funktionen sind in allen Back-Ends ähnlich. Sie werden auf den Standardwert des ersten Felds festgelegt und haben eine sprachspezifische Interaktionsweise:
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.setStringField("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)
Rust-Beispiel
In Rust werden Unions als Enums implementiert und haben keine expliziten Getters und Setters.
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, die von Diensten zum Melden von Fehlern verwendet werden können. Sie werden von Bindern 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 möglicherweise keine Ausgabeparameter initialisiert oder geändert. Insbesondere können Ausgabeparameter geändert werden, wenn der Fehler beim Entpacken und nicht bei der Verarbeitung der Transaktion selbst auftritt. Wenn Sie im Allgemeinen einen Fehler von einer AIDL-Funktion erhalten, sollten alle inout
- und out
-Parameter sowie der Rückgabewert (der in einigen Backends wie ein out
-Parameter fungiert) als undefiniert betrachtet werden.
Welche Fehlerwerte sollen verwendet werden?
Viele der integrierten Fehlerwerte können in beliebigen AIDL-Schnittstellen verwendet werden, einige werden jedoch auf besondere Weise behandelt. EX_UNSUPPORTED_OPERATION
und EX_ILLEGAL_ARGUMENT
können beispielsweise verwendet werden, wenn sie den Fehlerzustand beschreiben. EX_TRANSACTION_FAILED
darf jedoch nicht verwendet werden, da es von der zugrunde liegenden Infrastruktur speziell behandelt wird. Weitere Informationen zu diesen integrierten Werten finden Sie in den backend-spezifischen 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 Einbeziehung eines dienstspezifischen Fehlerwerts ermöglicht, der vom Nutzer definiert wird. Diese dienstspezifischen Fehler werden in der Regel in der AIDL-Schnittstelle als const int
- oder int
-basierte enum
definiert und nicht von Binder geparst.
In Java werden Fehler Ausnahmen zugeordnet, z. B. android.os.RemoteException
. Für dienstspezifische Ausnahmen verwendet Java android.os.ServiceSpecificException
zusammen mit dem benutzerdefinierten Fehler.
Im 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 Werte zurück, der den Status der Methode darstellt. Das Rust-Backend verwendet dieselben Ausnahme-Code-Werte wie das NDK, konvertiert sie jedoch in native Rust-Fehler (StatusCode
, ExceptionCode
), bevor sie an den Nutzer übergeben werden. Bei dienstspezifischen Fehlern wird für den zurückgegebenen Status
oder ScopedAStatus
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 Backends verwenden
Diese Anleitung bezieht sich speziell 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 unter Android-Rust-Mustern.
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 aidl
-Namespace):
#include <aidl/my/package/IFoo.h>
Oder im Rust-Backend:
use my_package::aidl::my::package::IFoo;
In Java können Sie einen verschachtelten Typ importieren. In den CPP- und NDK-Back-Ends müssen Sie jedoch den Header für den zugehörigen Stammtyp einfügen. 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) einfügen.
Schnittstelle implementieren
Wenn Sie eine Schnittstelle implementieren möchten, müssen Sie von der nativen Stub-Klasse erben. Eine Implementierung einer Schnittstelle wird oft als Dienst bezeichnet, wenn sie beim Dienstmanager oder android.app.ActivityManager
registriert ist, und als Callback, wenn sie von einem Client eines Dienstes registriert wird. Je nach genauer Verwendung werden jedoch verschiedene Namen verwendet, um Schnittstellenimplementierungen zu beschreiben. Die Stub-Klasse liest Befehle aus dem 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 die generierte Stub
-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 asynchronem 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 servicemanager
-Prozess 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 finden Sie in der entsprechenden servicemanager
-Schnittstelle. Sie können diese Vorgänge nur ausführen, wenn Sie für die Android-Plattform kompilieren.
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 run on this thread.
std::future::pending().await
}
Ein wichtiger Unterschied zu den anderen Optionen besteht darin, dass Sie join_thread_pool
bei Verwendung von asynchronem Rust und einer Single-Thread-Laufzeit nicht aufrufen. Das liegt daran, dass Sie Tokio einen Thread zur Verfügung stellen müssen, in dem die erstellten Aufgaben ausgeführt werden können. Im folgenden Beispiel wird der Hauptthread für diesen Zweck verwendet. Alle Aufgaben, die mit tokio::spawn
erstellt werden, werden im Hauptthread ausgeführt.
Im asynchronen Rust-Backend mit einer Multithread-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();
});
}
Mit der Multithread-Tokio-Laufzeit werden erstellte Aufgaben nicht im Hauptthread ausgeführt. Daher ist es sinnvoller, join_thread_pool
im Hauptthread aufzurufen, damit der Hauptthread nicht im Leerlauf ist. Sie müssen den Aufruf in block_in_place
einschließen, um den asynchronen Kontext zu verlassen.
Link zum Tod
Sie können eine Benachrichtigung anfordern, wenn ein Dienst, der einen Binder hostet, beendet wird. So können Sie das Lecken von Callback-Proxys vermeiden oder Fehler beheben. Führen Sie diese Aufrufe für Binder-Proxy-Objekte aus.
- 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)
auf. DaDeathRecipient
den Callback besitzt, müssen Sie dieses Objekt so lange aktiv halten, wie Sie Benachrichtigungen erhalten möchten.
Anruferinformationen
Wenn ein Kernel-Binder-Aufruf empfangen wird, sind die Informationen zum Aufrufer in mehreren APIs verfügbar. Die Prozess-ID (PID) bezieht sich auf die Linux-Prozess-ID des Prozesses, der eine Transaktion sendet. Die Nutzer-ID (UI) bezieht sich auf die Linux-Nutzer-ID. Bei einem unidirektionalen Anruf ist die PID des Anrufers 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 im Rust-Backend bei der Implementierung der Schnittstelle Folgendes an (anstatt den Standardwert zu verwenden):
... = 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 gesammelt, um bei der Fehlerbehebung verschiedener Probleme zu helfen.
Bei AIDL-Diensten verwenden Fehlerberichte das Binärprogramm dumpsys
für alle Dienste, die beim Dienstmanager registriert sind, um ihre Informationen in den Fehlerbericht zu schreiben. 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 ausgegeben werden, mit zusätzlichen Argumenten für addService
steuern. Sie können dumpsys --pid SERVICE
auch verwenden, um beim Debuggen die PID eines Dienstes abzurufen.
Wenn Sie Ihrem Dienst eine benutzerdefinierte Ausgabe hinzufügen möchten, überschreiben Sie die Methode dump
in Ihrem Serverobjekt, so wie Sie jede andere IPC-Methode implementieren, die in einer AIDL-Datei definiert ist. Beschränken Sie das Dumping auf die App-Berechtigung android.permission.DUMP
oder auf bestimmte UIDs.
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 den Standardwert zu verwenden):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Weak Pointers verwenden
Sie können einen schwachen Verweis auf ein Binder-Objekt erstellen.
Java unterstützt WeakReference
, aber keine schwachen Binder-Referenzen auf der nativen Ebene.
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));
Verwenden Sie im Rust-Backend WpIBinder
oder Weak<IFoo>
:
let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();
Schnittstellendeskriptor dynamisch abrufen
Der Schnittstellendeskriptor gibt den Typ einer Schnittstelle an. Das ist nützlich, wenn Sie Fehler beheben oder 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();
Diese Funktion wird von den NDK- und Rust-Back-Ends nicht unterstützt.
Schnittstellendeskriptor statisch abrufen
Manchmal (z. B. bei der Registrierung von @VintfStability
-Diensten) müssen Sie den Schnittstellendescriptor statisch kennen. In Java können Sie den Deskriptor abrufen, 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 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 die möglichen Werte durchlaufen, die ein Enum annehmen kann. Aus Gründen der Code-Größ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. In den meisten Anwendungsfällen sollte es genau einen Threadpool geben, der von allen Back-Ends gemeinsam genutzt wird.
Die einzige Ausnahme ist, wenn durch den Anbietercode eine weitere Kopie von libbinder
geladen wird, um mit /dev/vndbinder
zu kommunizieren. Dies erfolgt auf einem separaten Binder-Knoten, sodass der Threadpool nicht freigegeben wird.
Beim Java-Backend kann die Größe des Threadpools nur erhöht werden, da er bereits gestartet wurde:
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();
Im NDK-Backend sieht es ähnlich aus:
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();
Mit dem asynchronen Rust-Backend benötigen Sie zwei Threadpools: „binder“ und „Tokio“.
Das bedeutet, dass für Apps, die asynchronen Rust-Code verwenden, besondere Überlegungen erforderlich sind, insbesondere bei der Verwendung von join_thread_pool
. Weitere Informationen dazu 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. AIDL erzwingt zwar keine Einschränkungen basierend auf Sprachregeln, aber die Verwendung von Feld- oder Typnamen, die mit einem reservierten Namen übereinstimmen, kann zu einem Kompilierungsfehler für C++ oder Java führen. In Rust wird das Feld oder der Typ mit der Rohkennungs-Syntax umbenannt, auf die mit dem Präfix r#
zugegriffen werden kann.
Wir empfehlen, nach Möglichkeit keine reservierten Namen in Ihren AIDL-Definitionen zu verwenden, um unergonomische Bindungen oder Kompilierungsfehler zu vermeiden.
Wenn Sie bereits reservierte Namen in Ihren AIDL-Definitionen haben, können Sie Felder sicher umbenennen, ohne die Protokollkompatibilität zu beeinträchtigen. Möglicherweise müssen Sie Ihren Code aktualisieren, um weiterarbeiten zu können. Bereits erstellte Programme funktionieren aber weiterhin.
Namen, die Sie vermeiden sollten: