AIDL-Back-Ends

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 mit einer bestimmten Laufzeit. Je nach Kontext sollten Sie verschiedene AIDL-Back-Ends verwenden.

In der folgenden Tabelle bezieht sich 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 libbinder.so-Binärdatei system.img bereitgestellt werden kann.

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 APIs, z. B. die für die Dienstverwaltung, sind für die interne Plattformnutzung reserviert und für Anwendungen nicht verfügbar. Weitere Informationen zur Verwendung von AIDL in Anwendungen finden Sie in der Entwicklerdokumentation.
  • Das Rust-Back-End wurde mit Android 12 eingeführt. Das NDK-Back-End ist seit Android 10 verfügbar.
  • Der Rust-Kisten ist auf libbinder_ndk montiert, wodurch er stabil und tragbar ist. APEX verwendet die Binderkiste auf dieselbe Weise wie jeder andere Nutzer auf der Systemseite. Der Rust-Teil wird in einem APEX gebündelt und darin versendet. Das hängt vom libbinder_ndk.so auf der Systempartition ab.

Systeme erstellen

Je nach Back-End gibt es zwei Möglichkeiten, AIDL in Stub-Code zu kompilieren. Weitere Informationen zu den Build-Systemen finden Sie in der Referenz zu Soong-Modulen.

Kern-Build-System

In jedem cc_- oder java_-Android.bp-Modul (oder in deren Android.mk-Entsprechungen) können .aidl-Dateien als Quelldateien angegeben werden. In diesem Fall werden die Java/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. Optionen wie local_include_dirs, die dem Build-System den Stammpfad zu AIDL-Dateien in diesem Modul mitteilt, können in diesen Modulen in einer Gruppe aidl: angegeben werden. Beachten Sie, dass das Rust-Back-End nur für Rust verwendet werden kann. rust_-Module werden anders behandelt, als AIDL-Dateien nicht als Quelldateien angegeben werden. Stattdessen erzeugt das Modul aidl_interface eine rustlib namens <aidl_interface name>-rust, die verknüpft werden kann. Weitere Informationen finden Sie im Rust-AIDL-Beispiel.

hilfe_schnittstelle

Die mit diesem Build-System verwendeten Typen müssen strukturiert sein. Parcelables müssen direkt Felder enthalten und dürfen keine Deklarationen von Typen sein, die direkt in den Zielsprachen definiert sind, um strukturiert zu sein. Informationen dazu, wie strukturierte AIDL in die stabile AIDL passt, finden Sie unter Strukturierter AIDL im Vergleich zu stabiler AIDL.

Typen

Sie können den Compiler aidl als Referenzimplementierung für Typen betrachten. Wenn Sie eine Schnittstelle erstellen, rufen Sie aidl --lang=<backend> ... auf, um die resultierende Schnittstellendatei anzusehen. Wenn Sie das Modul aidl_interface verwenden, können Sie die Ausgabe in out/soong/.intermediates/<path to module>/ ansehen.

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
lang 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> [T; N]

1. Unter Android 12 oder höher verwenden Byte-Arrays aus Kompatibilitätsgründen uint8_t anstelle von int8_t.

2. Das C++-Back-End unterstützt List<T>, wobei T entweder String, IBinder, ParcelFileDescriptor oder ein Paket ist. In Android 13 oder höher kann T ein beliebiger nicht-primitiver Typ (einschließlich Schnittstellentypen) sein, mit Ausnahme von Arrays. AOSP empfiehlt die Verwendung von Arraytypen wie T[], da diese in allen Back-Ends funktionieren.

3. Das NDK-Back-End unterstützt List<T>, wobei T entweder String, ParcelFileDescriptor oder parzellenbar ist. In 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, ob es sich um eine Eingabe (ein Argument) oder eine Ausgabe (ein zurückgegebener Wert) handelt.

5. Union-Typen werden ab Android 12 unterstützt.

6. Unter Android 13 und 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 SharedRefBase::make\<My\>(... args ...), um ein Binder-SharedRefBase-Objekt zu instanziieren. Diese Funktion erstellt ein std::shared_ptr\<T\>-Objekt, das ebenfalls intern verwaltet wird, falls der Binder zu einem anderen Prozess gehört. Wird das Objekt auf andere Weise erstellt, werden doppelte Eigentümerschaft ausgelöst.

Richtwerte (ein/aus/einaus)

Die Typen der Argumente für Funktionen können als in, out oder inout angegeben werden. Damit wird gesteuert, in welche Richtung Informationen für einen IPC-Aufruf übergeben werden. in ist die Standardrichtung und gibt an, dass Daten vom Aufrufer an den Aufgerufenen übergeben werden. out bedeutet, dass Daten vom Aufgerufenen an den Aufrufer übergeben werden. inout ist eine Kombination aus beiden. Das Android-Team empfiehlt jedoch, den Argumentspezifizierer inout nicht zu verwenden. Wenn Sie inout mit einer versionierten Schnittstelle und einem älteren Aufgerufenen verwenden, werden die zusätzlichen Felder, die nur im Aufrufer vorhanden sind, auf die Standardwerte zurückgesetzt. In Bezug auf Rust empfängt ein normaler inout-Typ &mut Vec<T> und ein Listen-inout-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 utf-8 oder utf-16 sind. Deklarieren Sie Strings in AIDL als @utf8InCpp String, um sie automatisch in UTF-8 umzuwandeln. Die Back-Ends NDK und Rust verwenden immer utf-8-Strings. Weitere Informationen zur 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 Nullwerte für die CPP- und NDK-Back-Ends bereitzustellen. Im Rust-Back-End werden diese @nullable-Typen als Option<T> bereitgestellt. Native Server lehnen Nullwerte standardmäßig ab. Die einzigen Ausnahmen sind die Typen interface und IBinder, die für NDK-Lese- und CPP/NDK-Schreibvorgänge immer null sein können. Weitere Informationen zur Annotation nullable finden Sie unter Annotationen in AIDL.

Benutzerdefinierte Parzellen

Ein benutzerdefiniertes Paket ist ein Paket, das manuell in einem Ziel-Back-End implementiert wird. Verwenden Sie benutzerdefinierte Parzellen nur dann, wenn Sie versuchen, andere Sprachen für eine vorhandene benutzerdefiniertes Paket zu unterstützen, das nicht geändert werden kann.

Um ein benutzerdefiniertes Paket zu deklarieren, damit AIDL darüber Bescheid weiß, sieht die AIDL-Parcelable-Deklaration so aus:

    package my.pack.age;
    parcelable Foo;

Standardmäßig wird damit ein Java-Paket deklariert, wobei my.pack.age.Foo eine Java-Klasse ist, die die Schnittstelle Parcelable 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);
    };

Verwenden Sie in Android 15 (AOSP experimentell) rust_type zur Deklaration eines benutzerdefinierten Rust-Parzellenobjekts in AIDL:

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 Paket als Typ in AIDL-Dateien verwenden, es wird jedoch nicht von AIDL generiert. Geben Sie die Operatoren < und == für benutzerdefinierte CPP/NDK-Back-End-Parzellenables an, um sie in union zu verwenden.

Standardwerte

Strukturierte Parcelables können Standardwerte pro Feld für Primitive, Strings und Arrays dieser Typen deklarieren.

    parcelable Foo {
      int numField = 42;
      String stringField = "string value";
      char charValue = 'a';
      ...
    }

Wenn im Java-Back-End Standardwerte fehlen, werden Felder für einfache Typen mit Nullwerten und für nicht primitive Typen mit null initialisiert.

In anderen Back-Ends werden Felder mit Standardwerten initialisiert, wenn keine Standardwerte definiert sind. Im C++-Back-End werden beispielsweise String-Felder als leerer String und List<T>-Felder als leere 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. 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 erfordert keinen benutzerdefinierten Status oder Rückgabetyp.

Ausgabeparameter mit Fehlern

Wenn eine AIDL-Funktion einen Fehler meldet, darf die Funktion keine Ausgabeparameter initialisieren oder ändern. Ausgabeparameter können geändert werden, wenn der Fehler beim Aufheben der Paketzustellung und nicht während der Verarbeitung der Transaktion selbst auftritt. Wenn von einer AIDL-Funktion ein Fehler zurückgegeben wird, sollten alle inout- und out-Parameter sowie der Rückgabewert, der auf einigen Back-Ends wie ein out-Parameter funktioniert, als unbestimmter Zustand angesehen werden.

Zu verwendende Fehlerwerte

Viele der integrierten Fehlerwerte können in allen AIDL-Schnittstellen verwendet werden, einige werden jedoch gesondert behandelt. Beispielsweise können EX_UNSUPPORTED_OPERATION und EX_ILLEGAL_ARGUMENT verwendet werden, wenn sie die Fehlerbedingung beschreiben. EX_TRANSACTION_FAILED darf jedoch nicht verwendet werden, da dies von der zugrunde liegenden Infrastruktur als speziell behandelt wird. Weitere Informationen zu diesen integrierten Werten finden Sie in den Back-End-spezifischen Definitionen.

Wenn die AIDL-Schnittstelle zusätzliche Fehlerwerte erfordert, die nicht von den integrierten Fehlertypen abgedeckt sind, können sie den speziellen dienstspezifischen integrierten Fehler verwenden, der die Aufnahme 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-gestützte enum definiert und nicht von Binder geparst.

In Java werden Fehler Ausnahmen wie android.os.RemoteException zugeordnet. Für dienstspezifische Ausnahmen verwendet Java android.os.ServiceSpecificException zusammen mit dem benutzerdefinierten Fehler.

Nativer Code in Android verwendet keine Ausnahmen. Das CPP-Back-End verwendet android::binder::Status. Das NDK-Back-End verwendet ndk::ScopedAStatus. Jede von AIDL generierte Methode gibt einen dieser Werte zurück, der den Status der Methode darstellt. Das Rust-Back-End verwendet dieselben Ausnahmecodewerte wie das NDK, wandelt sie jedoch vor der Übermittlung an den Nutzer in native Rust-Fehler (StatusCode, ExceptionCode) um. Bei dienstspezifischen Fehlern verwendet der zurückgegebene Status oder ScopedAStatus EX_SERVICE_SPECIFIC zusammen mit dem benutzerdefinierten 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 der definierte Typ my.package.IFoo verwendet. Eine Anleitung zur Verwendung des Rust-Back-Ends finden Sie im Rust-AIDL-Beispiel auf der Seite Android-Rost-Muster.

Importtypen

Unabhängig davon, ob der definierte Typ „Interface“, „Parcelable“ oder „Union“ ist, können Sie ihn in Java importieren:

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 den Header für den Stammtyp angeben. 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-Back-End oder <aidl/my/package/IFoo.h> für das NDK-Back-End einfügen.

Dienste implementieren

Um einen Dienst zu implementieren, müssen Sie Werte aus der nativen Stub-Klasse erben. Diese 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 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 werden normalerweise mit dem Prozess servicemanager registriert. Zusätzlich zu den unten aufgeführten APIs prüfen einige APIs den Dienst. Das heißt, dass sie sofort zurückgegeben werden, wenn der Dienst nicht verfügbar ist. Genaue Informationen finden Sie in der entsprechenden servicemanager-Schnittstelle. Diese Vorgänge können nur bei der Kompilierung auf der Android-Plattform ausgefü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 join_thread_pool nicht aufrufen, wenn eine asynchrone Rust- und eine Single-Threaded-Laufzeit verwendet wird. Das liegt daran, dass Sie Tokio einen Thread zur Ausführung von Spawn-Aufgaben zur Verfügung stellen müssen. In diesem Beispiel erfüllt der Hauptthread diesen Zweck. Alle mit tokio::spawn erzeugten Aufgaben werden 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 Tokio-Laufzeit mit mehreren Threads werden erzeugte Aufgaben nicht im Hauptthread ausgeführt. Daher ist es sinnvoller, join_thread_pool für den Hauptthread aufzurufen, damit der Hauptthread nicht nur inaktiv ist. Sie müssen den Aufruf in block_in_place zusammenfassen, um den asynchronen Kontext zu verlassen.

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 dann my_binder.link_to_death(&mut my_death_recipient) auf. Da der Callback DeathRecipient gehört, muss dieses Objekt aktiv bleiben, solange du Benachrichtigungen erhalten möchtest.

Informationen zum Anrufer

Beim Empfang eines Kernel-Binder-Aufrufs sind Anruferinformationen in mehreren APIs verfügbar. Die PID (oder Prozess-ID) bezieht sich auf die Linux-Prozess-ID des Prozesses, der eine Transaktion sendet. Die UID (User-ID) bezieht sich auf die Linux-Nutzer-ID. Wenn ein Einweganruf eingeht, ist die aufrufende PID 0. Außerhalb eines Binder-Transaktionskontexts geben diese Funktionen die PID und die UID des aktuellen Prozesses zurück.

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 beim Implementieren der Schnittstelle Folgendes an (anstatt die Standardeinstellung zuzulassen):

    ... = ThreadState::get_calling_pid();
    ... = ThreadState::get_calling_uid();

Fehlerberichte und Debugging API für Dienste

Wenn Fehlerberichte erstellt werden (z. B. mit adb bugreport), erheben sie Informationen aus dem gesamten System, um die Behebung verschiedener Probleme zu erleichtern. Bei AIDL-Diensten verwenden Fehlerberichte das binäre dumpsys für alle Dienste, die beim Dienstmanager registriert sind, um ihre 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-Back-Ends können Sie mit zusätzlichen Argumenten für addService die Reihenfolge steuern, in der Dienste in Dump umgewandelt werden. Sie können auch dumpsys --pid SERVICE verwenden, um die PID eines Dienstes während der Fehlerbehebung abzurufen.

Wenn Sie Ihrem Dienst eine benutzerdefinierte Ausgabe hinzufügen möchten, können Sie die Methode dump in Ihrem Serverobjekt überschreiben, so wie Sie jede andere IPC-Methode implementieren, die in einer AIDL-Datei definiert ist. In diesem Fall sollten Sie das Dumping auf die App-Berechtigung android.permission.DUMP oder 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 beim Implementieren der Schnittstelle Folgendes an (anstatt die Standardeinstellung zuzulassen):

    fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>

Schnittstellendeskriptor dynamisch abrufen

Die Schnittstellenbeschreibung gibt den Typ einer Schnittstelle an. Dies ist bei der Fehlerbehebung oder bei einem unbekannten Binder nützlich.

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. beim Registrieren von @VintfStability-Diensten) müssen Sie wissen, wie statisch der Schnittstellendeskriptor ist. In Java können Sie den Deskriptor abrufen, 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 übernehmen kann. 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. In den meisten Anwendungsfällen sollte dies genau ein Threadpool sein, der von allen Back-Ends gemeinsam genutzt wird. Die einzige Ausnahme ist, wenn vom Anbietercode eine weitere Kopie von libbinder geladen wird, um mit /dev/vndbinder zu kommunizieren. Da sich dies auf einem separaten Binderknoten befindet, wird der Threadpool nicht freigegeben.

Für das Java-Back-End kann der Threadpool nur vergrößert werden, da er bereits gestartet wurde:

    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 erfordern, insbesondere in Bezug auf die Verwendung von join_thread_pool. Weitere Informationen hierzu finden Sie im Abschnitt zum Registrieren von Diensten.

Reservierte Namen

C++, Java und Rust reservieren einige Namen als Schlüsselwörter oder für die sprachspezifische Verwendung. AIDL erzwingt zwar keine Einschränkungen auf Basis von Sprachregeln, die Verwendung von Feld- oder Typnamen, die mit einem reservierten Namen übereinstimmen, kann jedoch zu einem Kompilierungsfehler für C++ oder Java führen. Bei Rust wird das Feld oder der Typ mithilfe der Syntax "Rohkennung" umbenannt, auf die mit dem Präfix r# zugegriffen werden kann.

Wir empfehlen, in Ihren AIDL-Definitionen nach Möglichkeit keine reservierten Namen zu verwenden, um unergonomische Bindungen oder Fehler bei der vollständigen Kompilierung zu vermeiden.

Wenn Ihre AIDL-Definitionen bereits reservierte Namen enthalten, können Sie Felder problemlos umbenennen und gleichzeitig protokollkompatibel bleiben. Möglicherweise müssen Sie Ihren Code aktualisieren, um mit dem Erstellen fortzufahren. Alle bereits erstellten Programme funktionieren jedoch weiterhin.

Zu vermeidende Namen: