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 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 vom libbinder_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, Strings 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.

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). Da das Feld DeathRecipient 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: