AIDL-Back-Ends

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 von libbinder_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
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
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::vector1 In: &[u8]
Out: Vec<u8>
List<T> std::vector<T>2 std::vector<T>3 In: In: &[T]4
Out: 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 Spezifizierer in ist die Standardrichtung. Wenn Datentypen aber auch out 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 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 ihre Standardwerte zurückgesetzt. In Bezug auf Rust erhält ein normaler inout-Typ &mut T und ein inout-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.

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 dann my_binder.link_to_death(&mut my_death_recipient) auf. Da DeathRecipient 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: