AIDL-Backends

Ein AIDL-Backend ist ein Ziel für die Stub-Code-Generierung. Wenn Sie AIDL-Dateien verwenden, verwenden Sie diese immer in einer bestimmten Sprache mit einer bestimmten Laufzeit. Je nach Kontext sollten Sie unterschiedliche AIDL-Backends verwenden.

AIDL verfügt über die folgenden Backends:

Backend Sprache API-Oberfläche Bauen Sie Systeme auf
Java Java SDK/SystemApi (stabil*) alle
NDK C++ libbinder_ndk (stabil*) helpl_interface
CPP C++ libbinder (instabil) alle
Rost Rost libbinder_rs (instabil) helpl_interface
  • Diese API-Oberflächen sind stabil, aber viele der APIs, beispielsweise für die Dienstverwaltung, sind für die interne Plattformnutzung reserviert und stehen Apps nicht zur Verfügung. Weitere Informationen zur Verwendung von AIDL in Apps finden Sie in der Entwicklerdokumentation .
  • Das Rust-Backend wurde in Android 12 eingeführt; Das NDK-Backend ist ab Android 10 verfügbar.
  • Die Rust-Kiste basiert auf libbinder_ndk . APEXes verwenden die Ordnerkiste auf die gleiche Weise wie alle anderen auf der Systemseite. Der Rust-Anteil wird in einem APEX gebündelt und darin versendet. Es hängt von libbinder_ndk.so auf der Systempartition ab.

Bauen Sie Systeme auf

Abhängig vom Backend gibt es zwei Möglichkeiten, AIDL in Stub-Code zu kompilieren. Weitere Einzelheiten zu den Build-Systemen finden Sie in der Soong-Modulreferenz .

Kern-Build-System

In jedem cc_ oder java_ Android.bp-Modul (oder in ihren Android.mk Entsprechungen) können .aidl Dateien als Quelldateien angegeben werden. In diesem Fall werden die Java/CPP-Backends von AIDL verwendet (nicht das NDK-Backend), und die Klassen zur Verwendung der entsprechenden AIDL-Dateien werden dem Modul automatisch hinzugefügt. Optionen wie local_include_dirs , die dem Build-System den Root-Pfad zu AIDL-Dateien in diesem Modul mitteilen, können in diesen Modulen unter einer Gruppe aidl: angegeben werden. Beachten Sie, dass das Rust-Backend nur für die Verwendung mit Rust vorgesehen ist. rust_ Module werden insofern anders gehandhabt, als AIDL-Dateien nicht als Quelldateien angegeben werden. Stattdessen erzeugt das Modul aidl_interface eine rustlib namens <aidl_interface name>-rust , mit der verknüpft werden kann. Weitere Einzelheiten finden Sie im Rust AIDL-Beispiel .

helpl_interface

Mit diesem Build-System verwendete Typen müssen strukturiert sein. Um strukturiert zu sein, müssen Parcelables Felder direkt enthalten und dürfen keine Deklarationen von Typen sein, die direkt in Zielsprachen definiert sind. Informationen dazu, wie strukturiertes AIDL mit stabilem AIDL zusammenpasst, finden Sie unter Strukturiertes vs. stabiles AIDL .

Typen

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

Java/AIDL-Typ C++-Typ NDK-Typ Rosttyp
Boolescher Wert bool bool bool
Byte int8_t int8_t i8
verkohlen 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
Zeichenfolge android::String16 std::string Zeichenfolge
android.os.Parcelable android::Parcelable N / A N / A
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] 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>
FileDescriptor android::base::unique_fd N / A binder::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
Schnittstellentyp (T) android::sp<T> std::shared_ptr<T> Bindemittel::Stark
Paketierbarer Typ (T) T T T
Verbindungstyp (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 entweder String , IBinder , ParcelFileDescriptor oder Parcelable ist. In Android 13 oder höher kann T jeder nicht-primitive Typ (einschließlich Schnittstellentypen) außer Arrays sein. AOSP empfiehlt die Verwendung von Array-Typen wie T[] , da diese in allen Backends funktionieren.

3. Das NDK-Backend unterstützt List<T> , wobei T entweder String , ParcelFileDescriptor oder Parcelable ist. In Android 13 oder höher kann T jeder nicht-primitive Typ außer Arrays sein.

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 in Android 12 und höher unterstützt.

6. In Android 13 oder höher werden Arrays mit fester Größe unterstützt. Arrays fester Größe können mehrere Dimensionen haben (z. B. int[3][4] ). Im Java-Backend werden Arrays fester Größe als Array-Typen dargestellt.

Direktionalität (ein/aus/ein)

Wenn Sie die Typen der Argumente für Funktionen angeben, können Sie diese als in , out oder inout angeben. Dies steuert, in welche Richtung Informationen für einen IPC-Aufruf weitergeleitet werden. in ist die Standardrichtung und gibt an, dass Daten vom Anrufer zum Angerufenen weitergeleitet werden. out bedeutet, dass Daten vom Angerufenen an den Anrufer weitergegeben werden. inout ist die Kombination aus beidem. Das Android-Team empfiehlt jedoch, die Verwendung des Argumentspezifizierers inout zu vermeiden. Wenn Sie inout mit einer versionierten Schnittstelle und einem älteren Aufrufer verwenden, werden die zusätzlichen Felder, die nur im Aufrufer vorhanden sind, auf ihre Standardwerte zurückgesetzt. In Bezug auf Rust empfängt ein normaler inout Typ &mut Vec<T> und ein Listen inout -Typ &mut Vec<T> .

UTF8/UTF16

Mit dem CPP-Backend können Sie wählen, ob Strings utf-8 oder utf-16 sind. Deklarieren Sie Zeichenfolgen in AIDL als @utf8InCpp String , um sie automatisch in utf-8 zu konvertieren. Die NDK- und Rust-Backends verwenden immer UTF-8-Strings. Weitere Informationen zur utf8InCpp Annotation finden Sie unter Annotationen in AIDL .

Nullbarkeit

Sie können Typen, die im Java-Backend null sein können, mit @nullable annotieren, um Nullwerte für die CPP- und NDK-Backends verfügbar zu machen. Im Rust-Backend werden diese @nullable Typen als Option<T> verfügbar gemacht. Native Server lehnen standardmäßig Nullwerte ab. Die einzigen Ausnahmen hiervon sind interface und IBinder Typen, die für NDK-Lesevorgänge und CPP/NDK-Schreibvorgänge immer null sein können. Weitere Informationen zur nullable Annotation finden Sie unter Annotationen in AIDL .

Individuelle Paketsendungen

Ein benutzerdefiniertes Parcelable ist ein Parcelable, das manuell in einem Ziel-Backend implementiert wird. Verwenden Sie benutzerdefinierte Parcelables nur, wenn Sie versuchen, Unterstützung für andere Sprachen für ein vorhandenes benutzerdefiniertes Parcelable hinzuzufügen, das nicht geändert werden kann.

Um eine benutzerdefinierte Parzellierung zu deklarieren, damit AIDL davon erfährt, sieht die AIDL-Parzellierung-Deklaration wie folgt aus:

    package my.pack.age;
    parcelable Foo;

Standardmäßig wird dadurch ein Java-Parcelable deklariert, wobei my.pack.age.Foo eine Java-Klasse ist, die die Parcelable Schnittstelle implementiert.

Für eine Deklaration eines benutzerdefinierten CPP-Backend-Parcelables in AIDL verwenden Sie cpp_header :

    package my.pack.age;
    parcelable Foo cpp_header "my/pack/age/Foo.h";

Die C++-Implementierung in my/pack/age/Foo.h sieht folgendermaßen 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);
    };

Für eine Deklaration eines benutzerdefinierten NDK-Parcelables in AIDL verwenden Sie ndk_header :

    package my.pack.age;
    parcelable Foo ndk_header "android/pack/age/Foo.h";

Die NDK-Implementierung in android/pack/age/Foo.h sieht folgendermaßen 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) zur Deklaration eines benutzerdefinierten Rust-Parcelables in AIDL rust_type :

package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";

Die Rust-Implementierung in rust_crate/src/lib.rs sieht folgendermaßen 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 Parcelable als Typ in AIDL-Dateien verwenden, es wird jedoch nicht von AIDL generiert. Stellen Sie < - und == Operatoren für benutzerdefinierte CPP/NDK-Backend-Parcelables bereit, um sie in union zu verwenden.

Standardwerte

Strukturierte Parcelables können Standardwerte pro Feld für Grundelemente, String s und Arrays dieser Typen deklarieren.

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

Wenn im Java-Backend Standardwerte fehlen, werden Felder für primitive Typen als Nullwerte und für nicht-primitive Typen als null initialisiert.

In anderen Backends werden Felder mit standardmäßig initialisierten Werten initialisiert, wenn keine Standardwerte definiert sind. Beispielsweise werden im C++-Backend String Felder als leere Zeichenfolge und List<T> -Felder als leeres vector<T> initialisiert. @nullable Felder werden als Nullwertfelder initialisiert.

Fehlerbehandlung

Das Android-Betriebssystem bietet integrierte Fehlertypen für Dienste, die sie beim Melden von Fehlern verwenden können. 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 sie erfordern keinen benutzerdefinierten Status oder Rückgabetyp.

Ausgabeparameter mit Fehlern

Wenn eine AIDL-Funktion einen Fehler meldet, initialisiert oder ändert die Funktion Ausgabeparameter möglicherweise nicht. Insbesondere können Ausgabeparameter geändert werden, wenn der Fehler beim Entpacken auftritt und nicht während der Verarbeitung der Transaktion selbst. Im Allgemeinen sollten beim Erhalten eines Fehlers von einer AIDL-Funktion alle inout und out Parameter sowie der Rückgabewert (der in einigen Backends wie ein out Parameter wirkt) davon ausgegangen werden, dass sie sich in einem unbestimmten Zustand befinden.

Welche Fehlerwerte verwendet werden sollen

Viele der integrierten Fehlerwerte können in allen AIDL-Schnittstellen verwendet werden, einige werden jedoch auf besondere Weise behandelt. Beispielsweise können EX_UNSUPPORTED_OPERATION und EX_ILLEGAL_ARGUMENT 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 die AIDL-Schnittstelle zusätzliche Fehlerwerte erfordert, die nicht von den integrierten Fehlertypen abgedeckt werden, können sie den speziellen dienstspezifischen integrierten Fehler verwenden, der die Einbeziehung eines dienstspezifischen Fehlerwerts ermöglicht, der vom Benutzer definiert wird . Diese dienstspezifischen Fehler werden in der Regel in der AIDL-Schnittstelle als const int oder int -backed enum definiert und vom Binder nicht analysiert.

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

Nativer Code in Android verwendet keine Ausnahmen. 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 Ausnahmecodewerte wie das NDK, konvertiert sie jedoch in native Rust-Fehler ( StatusCode , ExceptionCode ), bevor es sie an den Benutzer übermittelt. 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:

Backend Definition
Java android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
Rost android/binder_status.h

Verwendung verschiedener Backends

Diese Anweisungen gelten speziell für den Android-Plattformcode. Diese Beispiele verwenden einen definierten Typ, my.package.IFoo . Anweisungen zur Verwendung des Rust-Backends finden Sie im Rust AIDL-Beispiel auf der Seite „Android Rust Patterns“ .

Typen importieren

Unabhängig davon, ob es sich bei dem definierten Typ um eine Schnittstelle, ein Parcelable oder eine Union handelt, 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;

Obwohl Sie einen verschachtelten Typ in Java importieren können, müssen Sie in den CPP/NDK-Backends den Header für seinen Stammtyp einschließen. Wenn Sie beispielsweise einen verschachtelten Typ Bar importieren, der in my/package/IFoo.aidl definiert ist ( IFoo ist der Stammtyp der Datei), müssen Sie <my/package/IFoo.h> für das CPP-Backend (oder <aidl/my/package/IFoo.h> einschließen <aidl/my/package/IFoo.h> für das NDK-Backend).

Dienstleistungen umsetzen

Um einen Dienst zu implementieren, müssen Sie von der nativen Stub-Klasse erben. Diese Klasse liest Befehle vom Binder-Treiber und führt die von Ihnen implementierten Methoden aus. Stellen Sie sich vor, Sie hätten eine AIDL-Datei wie diese:

    package my.package;
    interface IFoo {
        int doFoo();
    }

In Java müssen Sie diese Klasse erweitern:

    import my.package.IFoo;
    public class MyFoo extends IFoo.Stub {
        @Override
        int doFoo() { ... }
    }

Im CPP-Backend:

    #include <my/package/BnFoo.h>
    class MyFoo : public my::package::BnFoo {
        android::binder::Status doFoo(int32_t* out) override;
    }

Im NDK-Backend (beachten Sie den zusätzlichen aidl Namespace):

    #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 Inanspruchnahme von Dienstleistungen

Dienste in der Android-Plattform werden normalerweise beim servicemanager Prozess registriert. Zusätzlich zu den unten aufgeführten APIs überprüfen einige APIs den Dienst (das heißt, sie kehren sofort zurück, wenn der Dienst nicht verfügbar ist). Genaue Details finden Sie in der entsprechenden servicemanager Schnittstelle. Diese Vorgänge können nur beim Kompilieren mit der 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-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 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-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-Thread-Laufzeit:

use myfoo::MyFoo;
use binder;
use binder_tokio::TokioRuntime;
use aidl_interface_name::aidl::my::package::IFoo::BnFoo;

#[tokio::main(flavor = "current_thread")]
async fn main() {
    binder::ProcessState::start_thread_pool();
    // [...]
    let my_service = MyFoo;
    let my_service_binder = BnFoo::new_async_binder(
        my_service,
        TokioRuntime(Handle::current()),
        BinderFeatures::default(),
    );

    binder::add_service("myservice", my_service_binder).expect("Failed to register service?");

    // Sleeps forever, but does not join the binder threadpool.
    // Spawned tasks will run on this thread.
    std::future::pending().await
}

Ein wichtiger Unterschied zu den anderen Optionen besteht darin, dass wir join_thread_pool nicht aufrufen, wenn wir asynchrones Rust und eine Single-Threaded-Laufzeit verwenden. Dies liegt daran, dass Sie Tokio einen Thread geben müssen, in dem es erzeugte Aufgaben ausführen kann. In diesem Beispiel dient der Hauptthread diesem Zweck. Alle mit tokio::spawn erzeugten Aufgaben 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-Laufzeitumgebung Tokio werden erzeugte Aufgaben nicht im Hauptthread ausgeführt. Daher ist es sinnvoller, join_thread_pool im Hauptthread aufzurufen, damit der Hauptthread nicht nur 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 Ordner hostet, ausfällt. Dies kann dazu beitragen, undichte Callback-Proxys zu vermeiden oder bei der Fehlerbeseitigung zu helfen. Führen Sie diese Aufrufe für Binder-Proxy-Objekte durch.

  • 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. Beachten Sie, dass Sie dieses Objekt so lange am Leben halten müssen, wie Sie Benachrichtigungen erhalten möchten, da der DeathRecipient Eigentümer des Rückrufs ist.

Anruferinformationen

Beim Empfang eines Kernel-Binder-Aufrufs sind Aufruferinformationen 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 (oder Benutzer-ID) bezieht sich auf die Linux-Benutzer-ID. Beim Empfang eines Einwegaufrufs ist die aufrufende PID 0. Außerhalb eines Binder-Transaktionskontexts geben diese Funktionen die PID und UID des aktuellen Prozesses zurück.

Im Java-Backend:

    ... = Binder.getCallingPid();
    ... = Binder.getCallingUid();

Im CPP-Backend:

    ... = IPCThreadState::self()->getCallingPid();
    ... = IPCThreadState::self()->getCallingUid();

Im NDK-Backend:

    ... = AIBinder_getCallingPid();
    ... = AIBinder_getCallingUid();

Geben Sie im Rust-Backend bei der Implementierung der Schnittstelle Folgendes an (anstatt es als Standard zuzulassen):

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

Fehlerberichte und Debugging-API für Dienste

Wenn Bugreports ausgeführt werden (z. B. mit adb bugreport ), sammeln sie Informationen aus dem gesamten System, um bei der Fehlerbehebung verschiedener Probleme zu helfen. Für AIDL-Dienste verwenden Fehlerberichte die Binärdatei dumpsys für alle beim Dienstmanager registrierten Dienste, um deren Informationen in den Fehlerbericht einzufügen. Sie können dumpsys auch 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 steuern, in der Dienste ausgegeben werden, indem Sie zusätzliche Argumente für addService verwenden. Sie können dumpsys --pid SERVICE auch verwenden, um beim Debuggen die PID eines Dienstes abzurufen.

Um Ihrem Dienst eine benutzerdefinierte Ausgabe hinzuzufügen, können Sie die dump Methode in Ihrem Serverobjekt überschreiben, so wie Sie jede andere IPC-Methode implementieren würden, die in einer AIDL-Datei definiert ist. Dabei sollten Sie das Dumping auf die App-Berechtigung android.permission.DUMP oder auf bestimmte UIDs beschränken.

Im Java-Backend:

    @Override
    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
        @Nullable String[] args) {...}

Im CPP-Backend:

    status_t dump(int, const android::android::Vector<android::String16>&) override;

Im NDK-Backend:

    binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;

Geben Sie im Rust-Backend bei der Implementierung der Schnittstelle Folgendes an (anstatt es als Standard zuzulassen):

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

Schnittstellendeskriptor dynamisch abrufen

Der Schnittstellendeskriptor identifiziert den Typ einer Schnittstelle. Dies ist nützlich beim Debuggen oder wenn Sie einen unbekannten Ordner haben.

In Java können Sie den Schnittstellendeskriptor mit Code wie dem folgenden abrufen:

    service = /* get ahold of service object */
    ... = service.asBinder().getInterfaceDescriptor();

Im CPP-Backend:

    service = /* get ahold of service object */
    ... = IInterface::asBinder(service)->getInterfaceDescriptor();

Die NDK- und Rust-Backends unterstützen diese Funktionalität nicht.

Schnittstellendeskriptor statisch abrufen

Manchmal (z. B. bei der Registrierung @VintfStability Diensten) müssen Sie statisch wissen, wie der Schnittstellendeskriptor lautet. In Java können Sie den Deskriptor erhalten, indem Sie Code hinzufügen wie:

    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 aidl Namespace):

    #include <aidl/my/package/BnFoo.h>
    ... aidl::my::package::BnFoo::descriptor

Im Rust-Backend:

    aidl::my::package::BnFoo::get_descriptor()

Enum-Bereich

In nativen Backends können Sie die möglichen Werte, die eine Aufzählung annehmen kann, iterieren. Aus Gründen der Codegröße wird dies derzeit in Java nicht unterstützt.

Für eine in AIDL definierte Aufzählung MyEnum wird die Iteration wie folgt bereitgestellt.

Im CPP-Backend:

    ::android::enum_range<MyEnum>()

Im NDK-Backend:

   ::ndk::enum_range<MyEnum>()

Im Rust-Backend:

    MyEnum::enum_values()

Thread-Management

Jede Instanz von libbinder in einem Prozess verwaltet einen Threadpool. In den meisten Anwendungsfällen sollte dies genau ein Threadpool sein, der von allen Backends gemeinsam genutzt wird. Die einzige Ausnahme besteht darin, dass der Herstellercode möglicherweise eine weitere Kopie von libbinder lädt, um mit /dev/vndbinder zu kommunizieren. Da sich dieser auf einem separaten Binderknoten befindet, wird der Threadpool nicht gemeinsam genutzt.

Für das Java-Backend kann der Threadpool nur vergrößert werden (da er bereits gestartet ist):

    BinderInternal.setMaxThreads(<new larger value>);

Für das CPP-Backend stehen folgende Operationen zur Verfügung:

    // 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();

Ebenso im NDK-Backend:

    bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
    ABinderProcess_startThreadPool();
    ABinderProcess_joinThreadPool();

Im Rust-Backend:

    binder::ProcessState::start_thread_pool();
    binder::add_service(“myservice”, my_service_binder).expect(“Failed to register service?”);
    binder::ProcessState::join_thread_pool();

Mit dem asynchronen Rust-Backend benötigen Sie zwei Threadpools: Binder und Tokio. Dies bedeutet, dass Anwendungen, die asynchrones Rust verwenden, besondere Überlegungen erfordern, insbesondere wenn es um die Verwendung von join_thread_pool geht. Weitere Informationen hierzu finden Sie im Abschnitt zur Registrierung von Diensten .

Reservierte Namen

C++, Java und Rust reservieren einige Namen als Schlüsselwörter oder für die sprachspezifische Verwendung. Während AIDL keine auf Sprachregeln basierenden Einschränkungen erzwingt, kann die Verwendung von Feld- oder Typnamen, die mit einem reservierten Namen übereinstimmen, zu einem Kompilierungsfehler für C++ oder Java führen. Für Rust wird das Feld oder der Typ mithilfe der „Raw Identifier“-Syntax umbenannt, auf die über das Präfix r# zugegriffen werden kann.

Wir empfehlen Ihnen, die Verwendung reservierter Namen in Ihren AIDL-Definitionen nach Möglichkeit zu vermeiden, um unergonomische Bindungen oder einen völligen Kompilierungsfehler zu vermeiden.

Wenn Sie in Ihren AIDL-Definitionen bereits reservierte Namen haben, können Sie Felder bedenkenlos umbenennen und gleichzeitig die Protokollkompatibilität bewahren. Möglicherweise müssen Sie Ihren Code aktualisieren, um mit der Erstellung fortzufahren. Alle bereits erstellten Programme funktionieren jedoch weiterhin.

Zu vermeidende Namen: * C++-Schlüsselwörter * Java-Schlüsselwörter * Rust-Schlüsselwörter