AIDL-Backends

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

AIDL hat die folgenden Backends:

Backend Sprache API-Oberfläche Systeme bauen
Java Java SDK/SystemApi (stabil*) alles
NDK C++ libbinder_ndk (stabil*) aidl_interface
CPP C++ libbinder (instabil) alles
Rost Rost libbinder_rs (instabil) 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 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 seit Android 10 verfügbar.
  • Die Rust-Kiste baut auf libbinder_ndk auf. APEXs verwenden die Binder-Crate auf die gleiche Weise wie alle anderen auf der Systemseite. Die Rust-Portion wird in einem APEX gebündelt und darin versendet. Es hängt von der libbinder_ndk.so auf der Systempartition ab.

Systeme bauen

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

Core-Build-System

In jedem cc_ oder java_ Android.bp-Modul (oder in ihren Android.mk Äquivalenten) 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 aidl: -Gruppe 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 , gegen die gelinkt werden kann. Weitere Einzelheiten finden Sie im Rust-AIDL-Beispiel .

aidl_interface

Siehe Stable AIDL . Mit diesem Build-System verwendete Typen müssen strukturiert sein; das heißt direkt in AIDL ausgedrückt. Das bedeutet, dass benutzerdefinierte Parcelables nicht verwendet werden können.

Typen

Sie können den aidl Compiler als Referenzimplementierung für Typen betrachten. Wenn Sie eine Schnittstelle erstellen, rufen 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>/ .

Java/AIDL-Typ C++-Typ NDK-Typ Rosttyp
boolesch 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
Schnur android::String16 std::string Schnur
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> binder::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 paketfähig ist. In Android T (AOSP experimentell) oder höher kann T jeder nicht-primitive Typ (einschließlich Schnittstellentypen) mit Ausnahme von Arrays sein. AOSP empfiehlt die Verwendung von Array-Typen wie T[] , da sie in allen Backends funktionieren.

3. Das NDK-Backend unterstützt List<T> , wobei T entweder String , ParcelFileDescriptor oder parcelable ist. In Android T (AOSP experimentell) oder höher kann T jeder nicht primitive Typ sein, außer 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 in Android 12 und höher unterstützt.

6. In Android T (AOSP experimentell) 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 fester Größe als Array-Typen dargestellt.

Direktionalität (in/out/inout)

Wenn Sie die Typen der Argumente für Funktionen angeben, können Sie diese als in , out oder inout . Dies steuert, in welche Richtung Informationen für einen IPC-Aufruf weitergegeben werden. in ist die Standardrichtung und zeigt an, dass Daten vom Aufrufer an den Angerufenen übergeben werden. out bedeutet, dass Daten vom Angerufenen an den Aufrufer weitergegeben werden. inout ist die Kombination aus beidem. Das Android-Team empfiehlt jedoch, die Verwendung des Argumentbezeichners inout zu vermeiden. 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 Vec<T> und ein List inout Typ erhält &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 als @utf8InCpp String -Zeichenfolge in AIDL, um sie automatisch in utf-8 zu konvertieren. Die NDK- und Rust-Backends verwenden immer utf-8-Strings. Weitere Informationen zur Annotation utf8InCpp finden Sie unter Annotations in AIDL .

Nullfähigkeit

Sie können Typen, die im Java-Back-End null sein können, mit @nullable , um null-Werte für die CPP- und NDK-Back-Ends verfügbar zu machen. Im Rust-Backend werden diese @nullable Typen als Option<T> bereitgestellt. Native Server lehnen standardmäßig Nullwerte ab. Die einzigen Ausnahmen davon 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 -Anmerkung finden Sie unter Anmerkungen in AIDL .

Benutzerdefinierte Parcelables

In den C++- und Java-Back-Ends im Core-Build-System können Sie ein Paket deklarieren, das manuell in einem Ziel-Back-End (in C++ oder in Java) implementiert wird.

    package my.package;
    parcelable Foo;

oder mit C++-Header-Deklaration:

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

Dann können Sie dieses Paket als Typ in AIDL-Dateien verwenden, aber es wird nicht von AIDL generiert.

Rust unterstützt keine benutzerdefinierten Parcelables.

Standardwerte

Strukturierte String können pro Feld Standardwerte für Primitive, Strings 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 null und für nicht primitive Typen als Nullwerte initialisiert.

In anderen Backends werden Felder mit initialisierten Standardwerten initialisiert, wenn keine Standardwerte definiert sind. Beispielsweise werden im C++-Back-End String -Felder als leere Zeichenfolge und List<T> -Felder als leerer 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 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.

Wenn eine AIDL-Funktion einen Fehler meldet, kann die Funktion Ausgabeparameter nicht initialisieren oder ändern. Insbesondere können Ausgabeparameter geändert werden, wenn der Fehler während des Entpackens auftritt und nicht während der Verarbeitung der Transaktion selbst. Wenn Sie einen Fehler von einer AIDL-Funktion erhalten, sollten im Allgemeinen alle inout und out -Parameter sowie der Rückgabewert (der sich in einigen Backends wie ein out -Parameter verhält) als in einem unbestimmten Zustand betrachtet werden.

Wenn die AIDL-Schnittstelle zusätzliche Fehlerwerte erfordert, die von den integrierten Fehlertypen nicht 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 nicht von Binder geparst.

In Java werden Fehler Ausnahmen wie 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 eine davon zurück, die den Status der Methode darstellt. Das Rust-Backend verwendet dieselben Ausnahmecodewerte wie das NDK, wandelt sie jedoch in native Rust-Fehler ( StatusCode , ExceptionCode ) um, bevor es sie an den Benutzer ausliefert. Für dienstspezifische Fehler verwendet der zurückgegebene Status oder ScopedAStatus EX_SERVICE_SPECIFIC zusammen mit dem benutzerdefinierten Fehler.

Die eingebauten 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 beziehen sich speziell auf 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 der definierte Typ eine Schnittstelle, ein Paket 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;

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> für das NDK-Backend).

Dienstleistungen implementieren

Um einen Dienst zu implementieren, müssen Sie von der nativen Stub-Klasse erben. Diese Klasse liest Befehle aus dem Binder-Treiber und führt die von Ihnen implementierten Methoden aus. Stellen Sie sich vor, Sie haben 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(())
        }
    }

Registrierung und Inanspruchnahme von Diensten

Dienste in der Plattform Android werden normalerweise mit dem servicemanager -Prozess registriert. Zusätzlich zu den unten aufgeführten APIs prüfen einige APIs den Dienst (was bedeutet, dass sie sofort zurückkehren, wenn der Dienst nicht verfügbar ist). Genaue Details finden Sie in der entsprechenden servicemanager -Oberfläche. Diese Operationen können nur durchgeführt werden, wenn für die Plattform Android kompiliert wird.

In Java:

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // getting
    myService = IFoo.Stub.asInterface(ServiceManager.getService("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);
    // getting
    status_t err = getService<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
    status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // getting
    myService = IFoo::fromBinder(SpAIBinder(AServiceManager_getService("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(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()
}

Sie können anfordern, eine Benachrichtigung zu erhalten, wenn ein Dienst, der einen Binder hostet, stirbt. Dies kann dazu beitragen, undichte Callback-Proxys zu vermeiden oder die Fehlerbehebung zu unterstützen. 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) . Beachten Sie, dass Sie dieses Objekt so lange am Leben erhalten müssen, wie Sie Benachrichtigungen erhalten möchten, da der DeathRecipient den Rückruf besitzt.

Fehlerberichte und Debugging-API für Dienste

Wenn Fehlerberichte ausgeführt werden (z. B. mit adb bugreport ), sammeln sie Informationen aus dem gesamten System, um beim Debuggen verschiedener Probleme zu helfen. Für AIDL-Dienste verwenden Fehlerberichte die Binärdatei dumpsys für alle Dienste, die beim Dienstmanager registriert sind, um ihre Informationen in den Fehlerbericht zu schreiben. Sie können dumpsys auch in der Befehlszeile verwenden, um Informationen von einem Dienst mit dumpsys SERVICE [ARGS] . 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 . Sie können auch dumpsys --pid SERVICE verwenden, um die PID eines Dienstes während des Debuggens abzurufen.

Um Ihrem Dienst eine benutzerdefinierte Ausgabe hinzuzufügen, können Sie die dump -Methode in Ihrem Serverobjekt überschreiben, als würden Sie jede andere IPC-Methode implementieren, die in einer AIDL-Datei definiert ist. Dabei sollten Sie das Dumping auf die App-Berechtigung android.permission.DUMP beschränken oder das Dumping 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 standardmäßig 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 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();

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

Schnittstellendeskriptor statisch abrufen

Manchmal (z. B. beim Registrieren @VintfStability Diensten) müssen Sie wissen, wie der Schnittstellendeskriptor statisch lautet. In Java können Sie den Deskriptor erhalten, 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 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 über die möglichen Werte iterieren, die eine Aufzählung annehmen kann. Aufgrund von Überlegungen zur Codegröße wird dies derzeit in Java nicht unterstützt.

Für eine in MyEnum 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_range()

Thread-Verwaltung

Jede Instanz von libbinder in einem Prozess verwaltet einen Threadpool. Für die meisten Anwendungsfälle sollte dies genau ein Threadpool sein, der von allen Backends gemeinsam genutzt wird. Die einzige Ausnahme hiervon ist, wenn Herstellercode eine andere Kopie von libbinder laden könnte, um mit /dev/vndbinder zu kommunizieren. Da sich dies auf einem separaten Binderknoten befindet, wird der Threadpool nicht gemeinsam genutzt.

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

    BinderInternal.setMaxThreads(<new larger value>);

Für das CPP-Backend sind die folgenden Operationen 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 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();