Ein AIDL-Back-End ist ein Ziel für die Generierung von Stub-Code. Wenn Sie AIDL-Dateien verwenden, Verwenden Sie sie immer in einer bestimmten Sprache und einer bestimmten Laufzeit. Je nach sollten Sie verschiedene AIDL-Back-Ends verwenden.
In der folgenden Tabelle bezieht sich die Stabilität der API-Oberfläche auf die Fähigkeit,
Code für diese API-Oberfläche so zu kompilieren, dass der Code
unabhängig vom system.img
-libbinder.so
-Binärprogramm bereitgestellt.
AIDL hat die folgenden Back-Ends:
Back-End | Sprache | API-Oberfläche | Systeme erstellen |
---|---|---|---|
Java | Java | SDK/SystemApi (stabil*) | alle |
Logo: NDK | C++ | libbinder_ndk (stabil*) | hilfe_schnittstelle |
CPP | C++ | libbinder (instabil) | alle |
Rust | Rust | libbinder_rs (stabil*) | hilfe_schnittstelle |
- Diese API-Oberflächen sind stabil, aber viele der APIs, z. B. für den Dienst, sind für die interne Plattformnutzung reserviert und stehen Apps. Weitere Informationen zur Verwendung von AIDL in Apps finden Sie unter Entwicklerdokumentation.
- Das Rust-Back-End wurde mit Android 12 eingeführt. die Das NDK-Backend ist seit Android 10 verfügbar.
- Der Rust-Kisten ist auf
libbinder_ndk
montiert, wodurch er stabil und tragbar. APEX nutzt die Hülle auf dieselbe Weise wie jeder von ihnen. auf der Systemseite. Der Rostanteil wird in ein APEX gebündelt und versendet darin enthalten sind. Das hängt vomlibbinder_ndk.so
auf der Systempartition ab.
Systeme erstellen
Je nach Back-End gibt es zwei Möglichkeiten, AIDL in einen Stub zu kompilieren. Code. Weitere Informationen zu den Build-Systemen finden Sie in der Referenz zu Soong-Modulen
Kern-Build-System
In einem beliebigen cc_
- oder java_
-Android.bp-Modul (oder in ihren Android.mk
-Entsprechungen)
.aidl
-Dateien können als Quelldateien angegeben werden. In diesem Fall wird die Java/CPP
Back-Ends von AIDL (nicht das NDK-Back-End) und die Klassen zur Verwendung der
werden dem Modul automatisch
entsprechende AIDL-Dateien hinzugefügt. Optionen
wie z. B. local_include_dirs
, das dem Build-System den Stammpfad zum
AIDL-Dateien in diesem Modul können in diesen Modulen unter einer aidl:
angegeben werden.
Gruppe. Beachten Sie, dass das Rust-Back-End nur mit Rust verwendet werden kann. rust_
Module sind
da AIDL-Dateien nicht als Quelldateien festgelegt werden.
Stattdessen erzeugt das Modul aidl_interface
eine rustlib
namens
<aidl_interface name>-rust
, das verknüpft werden kann. Weitere Informationen finden Sie unter
das Rust AIDL-Beispiel.
hilfe_schnittstelle
Die mit diesem Build-System verwendeten Typen müssen strukturiert sein. Um strukturiert zu sein, Parcelables müssen Felder direkt enthalten und dürfen keine Typendeklarationen sein die direkt in den Zielsprachen definiert sind. Wie strukturierte AIDL in stabile AIDL finden Sie unter Strukturierter vs. stabiler AIDL.
Typen
Sie können den Compiler aidl
als Referenzimplementierung für Typen betrachten.
Rufen Sie beim Erstellen einer Schnittstelle aidl --lang=<backend> ...
auf, um den
resultierender Interface-Datei. 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 | Rostart |
---|---|---|---|
boolean | Boolescher Wert | Boolescher Wert | Boolescher Wert |
Byte | int8_t | int8_t | i8 |
Zeichen | char16_t | char16_t | U16 |
int | int32_t | int32_t | i32 |
long | int64_t | int64_t | i64 |
schweben | schweben | schweben | F32 |
Doppelt | Doppelt | Doppelt | F64 |
String | android::String16 | std::string | String |
android.os.Parcelable | android::Parcelable | – | – |
Binder | android::IBinder | ndk::SpAIBinder | binder::SpIBinder |
D[] | 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> |
FileDeskriptor | android::base::unique_fd | – | binder::parcel::ParcelFileDescriptor |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor, | binder::parcel::ParcelFileDescriptor |
Schnittstellentyp (T) | android::sp<T> | std::shared_ptr<T>7 | binder::Stark |
Pakettyp (T) | T | T | T |
Union-Typ (T)5 | T | T | T |
D[N] 6 | std::array<T, N> | std::array<T, N> | [D; N] |
1. In Android 12 oder höher verwenden Byte-Arrays uint8_t anstelle von int8_t aus Kompatibilitätsgründen.
2. Das C++ Back-End unterstützt List<T>
, wobei T
einer der folgenden Werte ist: String
.
IBinder
, ParcelFileDescriptor
oder parcelable. In Android
13 oder höher, T
kann ein beliebiger nicht-primitiver Typ sein
(einschließlich Schnittstellentypen) mit Ausnahme von Arrays. AOSP empfiehlt, dass Sie
Verwenden Sie Array-Typen wie T[]
, da diese in allen Back-Ends funktionieren.
3. Das NDK-Back-End unterstützt List<T>
, wobei T
einer der folgenden Werte ist: String
.
ParcelFileDescriptor
oder parcelable. Android 13
oder höher kann T
ein beliebiger nicht-primitiver Typ sein, mit Ausnahme von Arrays.
4. Typen werden für Rust-Code unterschiedlich übergeben, je nachdem, sind eine Eingabe (ein Argument) oder eine Ausgabe (ein zurückgegebener Wert).
5. Union-Typen werden in Android 12 unterstützt und höher liegen.
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-Back-End werden Arrays mit fester Größe als Arraytypen dargestellt.
7. Verwenden Sie zum Instanziieren eines Binder-SharedRefBase
-Objekts
SharedRefBase::make\<My\>(... args ...)
. Mit dieser Funktion wird ein
std::shared_ptr\<T\>
Objekt
die ebenfalls intern verwaltet wird, falls der Binder einem anderen gehört.
. Wenn Sie das Objekt auf andere Weise erstellen, werden die Eigentumsrechte doppelt erhoben.
Richtwerte (ein/aus/einaus)
Bei der Angabe der Typen der Argumente für Funktionen können Sie
in
, out
oder inout
. Damit wird festgelegt, in welche Richtung
die für einen IPC-Aufruf übergeben wurden. in
ist die Standardrichtung und gibt an, dass die Daten
vom Aufrufer an den Aufgerufenen übergeben. out
bedeutet, dass Daten vom
auf den Anrufer. inout
ist eine Kombination aus beiden. Die
Das Android-Team rät davon ab, den Argumentspezifizierer inout
zu verwenden.
Wenn Sie inout
mit einer versionierten Oberfläche und einem älteren Aufgerufenen verwenden,
Zusätzliche Felder, die nur im Aufrufer vorhanden sind, werden auf ihre Standardeinstellungen zurückgesetzt
Werte. In Bezug auf Rust erhält ein normaler inout
-Typ &mut Vec<T>
und
erhält ein Listentyp inout
&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 utf-8 oder utf-16 sind. Erklärung
Strings als @utf8InCpp String
in AIDL, um sie automatisch in UTF-8 zu konvertieren.
Die Back-Ends NDK und Rust verwenden immer utf-8-Strings. Weitere Informationen zu
Die Annotation utf8InCpp
finden Sie unter Annotationen in AIDL.
Null-Zulässigkeit
Sie können Typen, die im Java-Back-End null sein können, mit @nullable
annotieren.
um den CPP- und NDK-Back-Ends Nullwerte zur Verfügung zu stellen. Im Rust-Back-End
@nullable
-Typen werden als Option<T>
bereitgestellt. Native Server lehnen Nullwerte ab
ist standardmäßig aktiviert. Die einzigen Ausnahmen sind die Typen interface
und IBinder
.
Dieser kann für NDK-Lesevorgänge und CPP/NDK-Schreibvorgänge immer null sein. Weitere Informationen
zur Anmerkung nullable
finden Sie unter
Annotationen in AIDL.
Benutzerdefinierte Parzellen
Ein benutzerdefiniertes Parcelable ist ein Paket, das manuell in einem Ziel implementiert wird. Back-End. Verwenden Sie benutzerdefinierte Parzellen nur, wenn Sie versuchen, Sprachen für ein vorhandenes benutzerdefiniertes Paket, das nicht geändert werden kann.
Um ein benutzerdefiniertes Paket zu deklarieren, damit AIDL darüber informiert wird, muss die AIDL parcelable-Deklaration sieht so aus:
package my.pack.age;
parcelable Foo;
Standardmäßig wird ein Java-Parcelable deklariert, wobei my.pack.age.Foo
ein Java-Code ist.
Klasse, die die Parcelable
-Schnittstelle implementiert.
Verwenden Sie cpp_header
zum Deklarieren eines benutzerdefinierten CPP-Back-End-Parzellenformats in AIDL:
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 ein benutzerdefiniertes NDK-Parcelable 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 (AOSP experimentell) zur Angabe eines benutzerdefinierten Rostschutzes
verwenden Sie 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);
Dann können Sie dieses Paket als Typ in AIDL-Dateien verwenden, aber es ist nicht
die von AIDL generiert wurden. Geben Sie die Operatoren <
und ==
für das CPP-/NDK-Backend an
benutzerdefinierte Grundstücke, um sie in union
zu verwenden.
Standardwerte
Strukturierte Parcelables können Standardwerte pro Feld für Primitive,
String
s und Arrays dieser Typen.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
Im Java-Back-End werden Felder initialisiert, wenn Standardwerte fehlen.
Nullwerte für primitive Typen und null
für nicht primitive Typen.
In anderen Back-Ends werden Felder mit Standardwerten initialisiert, wenn
Standardwerte sind nicht definiert. Im C++-Back-End werden beispielsweise String
-Felder verwendet.
werden als leerer String initialisiert und List<T>
-Felder werden als
Leere vector<T>
. @nullable
-Felder werden als Nullwertfelder initialisiert.
Fehlerbehandlung
Das Android-Betriebssystem bietet integrierte Fehlertypen für Dienste, die bei der Berichterstellung verwendet werden. Fehler. Diese werden von Binder verwendet und können von allen Diensten genutzt werden, die eine Binder-Schnittstelle. Ihre Verwendung ist in der AIDL-Definition gut dokumentiert und erfordern keinen benutzerdefinierten Status oder Rückgabetyp.
Ausgabeparameter mit Fehlern
Wenn eine AIDL-Funktion einen Fehler meldet, wird die Funktion möglicherweise nicht initialisiert oder
Ausgabeparameter zu ändern. Ausgabeparameter können geändert werden, wenn der
Fehler treten nicht während der Verarbeitung auf, sondern beim Auflösen des Pakets.
der Transaktion selbst. Im Allgemeinen gilt: Wenn ein Fehler von einem AIDL
alle inout
- und out
-Parameter sowie den Rückgabewert (der
agiert wie ein out
-Parameter in einigen Back-Ends), sollte als
ein unbestimmter Zustand ist.
Zu verwendende Fehlerwerte
Viele der integrierten Fehlerwerte können in allen AIDL-Schnittstellen verwendet werden, aber einige
gesondert behandelt werden. Beispiel: EX_UNSUPPORTED_OPERATION
und
EX_ILLEGAL_ARGUMENT
können verwendet werden, wenn sie die Fehlerbedingung beschreiben, aber
EX_TRANSACTION_FAILED
darf nicht verwendet werden, da es vom
zugrunde liegende Infrastruktur. Weitere Informationen finden Sie in den Backend-spezifischen Definitionen
Informationen zu diesen integrierten Werten.
Wenn die AIDL-Schnittstelle zusätzliche Fehlerwerte erfordert, die nicht durch
die integrierten Fehlertypen verwenden, dann können sie die speziellen
Fehler, mit dem ein dienstspezifischer Fehlerwert
einbezogen werden kann,
die vom Nutzer definiert wurden. Diese dienstspezifischen Fehler werden in der Regel in der
AIDL-Schnittstelle als const int
- oder int
-gestützte enum
und wird nicht geparst von
binder.
In Java werden Fehler Ausnahmen wie android.os.RemoteException
zugeordnet. Für
dienstspezifische Ausnahmen, Java verwendet android.os.ServiceSpecificException
zusammen mit dem benutzerdefinierten Fehler ein.
Nativer Code in Android verwendet keine Ausnahmen. Das CPP-Back-End verwendet
android::binder::Status
Das NDK-Back-End verwendet ndk::ScopedAStatus
. Jeden
die von AIDL generierte Methode gibt einen dieser Werte zurück, der den Status der
. Das Rust-Back-End verwendet dieselben Ausnahmecodewerte wie das NDK, aber
wandelt sie in native Rust-Fehler (StatusCode
, ExceptionCode
) um, bevor
sie an die Nutzenden zu liefern. Bei dienstspezifischen Fehlern hat der zurückgegebene Wert
Status
oder ScopedAStatus
verwendet EX_SERVICE_SPECIFIC
zusammen mit dem
benutzerdefinierter Fehler.
Die integrierten Fehlertypen finden Sie in den folgenden Dateien:
Back-End | Definition |
---|---|
Java | android/os/Parcel.java |
CPP | binder/Status.h |
Logo: NDK | android/binder_status.h |
Rust | android/binder_status.h |
Verschiedene Back-Ends verwenden
Diese Anleitung gilt speziell für den Code der Android-Plattform. In diesen Beispielen wird ein
definierten Typ, my.package.IFoo
. Anweisungen zur Verwendung des Rust-Back-Ends
Siehe Rust AIDL-Beispiel
zu den Android Rust Patterns
Seite.
Importtypen
Unabhängig davon, ob der definierte Typ „Interface“, „Parcelable“ oder „Union“ ist, können Sie in Java:
import my.package.IFoo;
Oder im CPP-Back-End:
#include <my/package/IFoo.h>
Oder im NDK-Back-End (beachten Sie den zusätzlichen aidl
-Namespace):
#include <aidl/my/package/IFoo.h>
Oder im Rust-Back-End:
use my_package::aidl::my::package::IFoo;
Obwohl Sie einen verschachtelten Typ in Java importieren können, müssen Sie in den CPP/NDK-Back-Ends
fügen Sie den Header für seinen Stammtyp hinzu. Beim Importieren eines verschachtelten Typs
Bar
definiert in my/package/IFoo.aidl
(IFoo
ist der Stammtyp von
-Datei) müssen Sie <my/package/IFoo.h>
für das CPP-Back-End einfügen (oder
<aidl/my/package/IFoo.h>
für das NDK-Back-End).
Dienste implementieren
Um einen Dienst zu implementieren, müssen Sie Werte aus der nativen Stub-Klasse erben. Dieser Kurs liest Befehle aus dem Binder-Treiber und führt die von Ihnen umsetzen. Angenommen, Sie haben eine AIDL-Datei wie diese:
package my.package;
interface IFoo {
int doFoo();
}
In Java müssen Sie von dieser Klasse aus erweitern:
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
Im CPP-Back-End:
#include <my/package/BnFoo.h>
class MyFoo : public my::package::BnFoo {
android::binder::Status doFoo(int32_t* out) override;
}
Im NDK-Back-End (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-Back-End:
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 auf der Android-Plattform sind normalerweise in der servicemanager
registriert
. Zusätzlich zu den unten aufgeführten APIs prüfen einige APIs die
(d. h., sie werden sofort wieder zurückgegeben, wenn der Dienst nicht verfügbar ist).
Genaue Informationen finden Sie in der entsprechenden servicemanager
-Schnittstelle. Diese
Vorgänge können nur bei Kompilierungen über die Android-Plattform durchgeführt werden.
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-Back-End:
#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-Back-End (beachten Sie den zusätzlichen aidl
-Namespace):
#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-Back-End:
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-Back-End 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 nicht
join_thread_pool
, wenn Sie eine asynchrone Rust- und eine Single-Threaded-Laufzeit verwenden. Dies ist
weil Sie Tokio einen Thread
für die Ausführung von Spawn-Aufgaben geben müssen. In
In diesem Beispiel erfüllt der Hauptthread diesen Zweck. Alle Aufgaben, die mithilfe von
tokio::spawn
wird im Hauptthread ausgeführt.
Im asynchronen Rust-Back-End 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();
});
}
Bei der Multithread-Tokio-Laufzeit werden erzeugte Aufgaben nicht auf der Haupt-
Diskussions-Thread. Daher ist es sinnvoller, join_thread_pool
im Haupt-
damit der Hauptthread nicht nur inaktiv ist. Sie müssen den Anruf beenden
block_in_place
, um den asynchronen Kontext zu verlassen.
Link zum Tod
Sie können eine Benachrichtigung anfordern, wenn ein Dienst, der einen Binder hostet, stirbt. Dies kann dazu beitragen, Datenlecks von Callback-Proxys zu vermeiden oder die Fehlerbehebung zu erleichtern. Führen Sie diese Aufrufe für Binder-Proxy-Objekte aus.
- Verwenden Sie in Java
android.os.IBinder::linkToDeath
. - Verwenden Sie im CPP-Back-End
android::IBinder::linkToDeath
. - Verwenden Sie im NDK-Back-End
AIBinder_linkToDeath
. - Erstellen Sie im Rust-Back-End ein
DeathRecipient
-Objekt und rufen Sie dannmy_binder.link_to_death(&mut my_death_recipient)
. Da das FeldDeathRecipient
ist Inhaber des Callbacks. Dieses Objekt muss so lange aktiv bleiben, da Sie Benachrichtigungen erhalten möchten.
Informationen zum Anrufer
Beim Empfang eines Kernel-Binder-Aufrufs sind Anruferinformationen in mehrere APIs. Die PID (oder Prozess-ID) bezieht sich auf die Linux-Prozess-ID des das Senden einer Transaktion. Die UID (User-ID) bezieht sich auf die Linux-Nutzer-ID. Wenn ein Einweganruf eingeht, ist die aufrufende PID 0. Wann? Außerhalb eines Binder-Transaktionskontexts geben diese Funktionen die PID und die UID zurück. des aktuellen Prozesses.
Im Java-Back-End:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
Im CPP-Back-End:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
Im NDK-Back-End:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
Geben Sie im Rust-Back-End bei der Implementierung der Schnittstelle Folgendes an: (anstatt als Standardeinstellung zugelassen zu werden):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
Fehlerberichte und Debugging API für Dienste
Wenn Fehlerberichte erstellt werden (z. B. mit adb bugreport
), werden sie erhoben
Informationen aus dem gesamten System
zur Behebung verschiedener Probleme.
Bei AIDL-Diensten wird für Fehlerberichte bei allen Diensten das Binärprogramm dumpsys
verwendet
die beim Service Manager registriert sind,
Fehlerbericht erstellen. Sie können auch dumpsys
in der Befehlszeile verwenden, um Informationen abzurufen
von einem Dienst mit dumpsys SERVICE [ARGS]
. In den C++- und Java-Back-Ends
kann die Reihenfolge steuern, in der Dienste ausgegeben werden, indem zusätzliche Argumente verwendet werden
an addService
. Sie können auch dumpsys --pid SERVICE
verwenden, um die PID eines
während der Fehlerbehebung.
Sie können den dump
überschreiben, um Ihrem Dienst eine benutzerdefinierte Ausgabe hinzuzufügen
in Ihrem Serverobjekt wie bei jeder anderen IPC-Methode implementieren.
in einer AIDL-Datei definiert ist. In diesem Fall sollten Sie das Dump von Daten auf die Anwendung beschränken.
android.permission.DUMP
erlauben oder das Dumping auf bestimmte UIDs beschränken.
Im Java-Back-End:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
Im CPP-Back-End:
status_t dump(int, const android::android::Vector<android::String16>&) override;
Im NDK-Back-End:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
Geben Sie im Rust-Back-End bei der Implementierung der Schnittstelle Folgendes an: (anstatt als Standardeinstellung zugelassen zu werden):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Schnittstellendeskriptor dynamisch abrufen
Die Schnittstellenbeschreibung gibt den Typ einer Schnittstelle an. Hilfreich 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-Back-End:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
Die Back-Ends NDK und Rust unterstützen diese Funktion nicht.
Schnittstellendeskriptor statisch abrufen
Manchmal, z. B. bei der Registrierung von @VintfStability
-Diensten, müssen Sie
was der Schnittstellendeskriptor statisch ist. In Java erhalten Sie den
Beschreibung hinzu, indem Sie Code wie den folgenden hinzufügen:
import my.package.IFoo;
... IFoo.DESCRIPTOR
Im CPP-Back-End:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
Im NDK-Back-End (beachten Sie den zusätzlichen aidl
-Namespace):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
Im Rust-Back-End:
aidl::my::package::BnFoo::get_descriptor()
Enum-Bereich
In nativen Back-Ends können Sie über die möglichen Werte iterieren, die eine Enumeration annehmen kann. aktiviert. Aufgrund von Überlegungen zur Codegröße wird dies in Java nicht unterstützt.
Für eine in AIDL definierte Aufzählung MyEnum
wird die Iteration wie folgt bereitgestellt.
Im CPP-Back-End:
::android::enum_range<MyEnum>()
Im NDK-Back-End:
::ndk::enum_range<MyEnum>()
Im Rust-Back-End:
MyEnum::enum_values()
Thread-Verwaltung
Jede Instanz von libbinder
in einem Prozess verwaltet einen Threadpool. Für die meisten
Anwendungsfälle sollten genau ein Threadpool sein, der von allen Back-Ends gemeinsam genutzt wird.
Die einzige Ausnahme ist, wenn der Anbietercode möglicherweise eine weitere Kopie von libbinder
lädt
um mit /dev/vndbinder
zu sprechen. Da sich dies auf einem separaten Binderknoten befindet,
Threadpool ist nicht freigegeben.
Für das Java-Back-End kann der Threadpool nur vergrößert werden (da er bereits begonnen):
BinderInternal.setMaxThreads(<new larger value>);
Für das CPP-Back-End 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();
Ähnlich verhält es sich mit dem NDK-Back-End:
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
Im Rust-Back-End:
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-Back-End benötigen Sie zwei Threadpools: binder und Tokio.
Das bedeutet, dass Anwendungen, die asynchronen Rust verwenden, besondere Überlegungen anstellen müssen.
insbesondere, wenn es um die Verwendung von join_thread_pool
geht. Weitere Informationen finden Sie im Abschnitt zur
Registrierung von Diensten.
Reservierte Namen
C++, Java und Rust reservieren einige Namen als Keywords oder für sprachspezifische
verwenden. AIDL erzwingt zwar keine Einschränkungen auf Basis von Sprachregeln,
Feld- oder Typnamen, die mit einem reservierten Namen übereinstimmen, führen möglicherweise zu einer Kompilierung
für C++ oder Java. Für Rust wird das Feld oder der Typ mithilfe der Methode
„Roh-ID“ Syntax, die mit dem Präfix r#
zugänglich ist.
Wir empfehlen, in Ihren AIDL-Definitionen keine reservierten Namen zu verwenden nach Möglichkeit, um unergonomische Bindungen oder vollständige Kompilierungsfehler zu vermeiden.
Wenn Ihre AIDL-Definitionen bereits reservierte Namen enthalten, Felder umbenennen und gleichzeitig protokollkompatibel bleiben; müssen Sie möglicherweise um Code weiterzuentwickeln, aber alle bereits erstellten Programme Interoperabilität.
Zu vermeidende Namen: