Backend AIDL

Un backend AIDL è un obiettivo per la generazione di codice stub. Quando usi i file AIDL, li usi sempre in una lingua particolare con un runtime specifico. A seconda del contesto, dovresti utilizzare diversi backend AIDL.

AIDL ha i seguenti backend:

Backend Lingua Superficie dell'API Costruisci sistemi
Giava Giava SDK/SystemApi (stabile*) Tutto
NDK C++ libbinder_ndk (stabile*) helpl_interface
CPP C++ libbinder (instabile) Tutto
Ruggine Ruggine libbinder_rs (instabile) helpl_interface
  • Queste superfici API sono stabili, ma molte API, come quelle per la gestione dei servizi, sono riservate all'uso della piattaforma interna e non sono disponibili per le app. Per ulteriori informazioni su come utilizzare AIDL nelle app, consulta la documentazione per gli sviluppatori .
  • Il backend Rust è stato introdotto in Android 12; il backend NDK è disponibile a partire da Android 10.
  • La cassa Rust è costruita sopra libbinder_ndk . Gli APEX utilizzano la cassa raccoglitrice allo stesso modo di chiunque altro sul lato del sistema. La porzione Rust viene raggruppata in un APEX e spedita al suo interno. Dipende da libbinder_ndk.so sulla partizione di sistema.

Costruisci sistemi

A seconda del backend, ci sono due modi per compilare AIDL nel codice stub. Per ulteriori dettagli sui sistemi di build, consultare il riferimento del modulo Soong .

Sistema di costruzione del nucleo

In qualsiasi modulo cc_ o java_ Android.bp (o nei relativi equivalenti Android.mk ), i file .aidl possono essere specificati come file sorgente. In questo caso, vengono utilizzati i backend Java/CPP di AIDL (non il backend NDK) e le classi per utilizzare i file AIDL corrispondenti vengono aggiunte automaticamente al modulo. Opzioni come local_include_dirs , che indica al sistema di compilazione il percorso root dei file AIDL in quel modulo, possono essere specificate in questi moduli sotto un gruppo aidl: Tieni presente che il backend Rust può essere utilizzato solo con Rust. i moduli rust_ vengono gestiti diversamente in quanto i file AIDL non sono specificati come file sorgente. Invece, il modulo aidl_interface produce un rustlib chiamato <aidl_interface name>-rust a cui è possibile collegarsi. Per maggiori dettagli, vedere l' esempio Rust AIDL .

helpl_interface

I tipi utilizzati con questo sistema di compilazione devono essere strutturati. Per essere strutturati, i parcelable devono contenere campi direttamente e non essere dichiarazioni di tipi definiti direttamente nelle lingue di destinazione. Per informazioni su come l'AIDL strutturato si adatta all'AIDL stabile, vedere AIDL strutturato e AIDL stabile .

Tipi

Puoi considerare il compilatore aidl come un'implementazione di riferimento per i tipi. Quando crei un'interfaccia, richiama aidl --lang=<backend> ... per vedere il file di interfaccia risultante. Quando usi il modulo aidl_interface , puoi visualizzare l'output in out/soong/.intermediates/<path to module>/ .

Tipo Java/AIDL Tipo C++ Tipo NDK Tipo di ruggine
booleano bool bool bool
byte int8_t int8_t i8
car char16_t char16_t u16
int int32_t int32_t i32
lungo int64_t int64_t i64
galleggiante galleggiante galleggiante f32
Doppio Doppio Doppio f64
Corda android::String16 std::stringa Corda
android.os.Parcelable android:: Parcellabile N / A N / A
IBinder android::IBinder ndk::SpAIBinder raccoglitore::SpIBinder
T[] std::vettore<T> std::vettore<T> In: &[T]
Uscita: Vec<T>
byte[] std::vettore<uint8_t> std::vettore<int8_t> 1 In: &[u8]
Fuori: Vec<u8>
Elenco<T> std::vettore<T> 2 std::vettore<T> 3 In: &[T] 4
Uscita: Vec<T>
DescrizioneFile android::base::unique_fd N / A binder::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
tipo di interfaccia (T) android::sp<T> std::shared_ptr<T> legante::Forte
tipologia parcellabile (T) T T T
tipo di unione (T) 5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T; N]

1. In Android 12 o versioni successive, gli array di byte utilizzano uint8_t anziché int8_t per motivi di compatibilità.

2. Il backend C++ supporta List<T> dove T è uno tra String , IBinder , ParcelFileDescriptor o parcelable. In Android 13 o versioni successive, T può essere qualsiasi tipo non primitivo (inclusi i tipi di interfaccia) ad eccezione degli array. AOSP consiglia di utilizzare tipi di array come T[] , poiché funzionano in tutti i backend.

3. Il backend NDK supporta List<T> dove T è uno tra String , ParcelFileDescriptor o parcelable. In Android 13 o versioni successive, T può essere qualsiasi tipo non primitivo tranne gli array.

4. I tipi vengono passati in modo diverso per il codice Rust a seconda che siano input (un argomento) o output (un valore restituito).

5. I tipi di unione sono supportati in Android 12 e versioni successive.

6. In Android 13 o versioni successive sono supportati gli array a dimensione fissa. Gli array a dimensione fissa possono avere più dimensioni (ad esempio int[3][4] ). Nel backend Java, gli array a dimensione fissa sono rappresentati come tipi di array.

Direzionalità (dentro/fuori/dentro)

Quando si specificano i tipi degli argomenti delle funzioni, è possibile specificarli come in , out o inout . Controlla in quale direzione vengono passate le informazioni per una chiamata IPC. in è la direzione predefinita e indica che i dati vengono passati dal chiamante al chiamato. out significa che i dati vengono passati dal chiamato al chiamante. inout è la combinazione di entrambi. Tuttavia, il team di Android consiglia di evitare di utilizzare lo specificatore di argomento inout . Se utilizzi inout con un'interfaccia con versione e un chiamato precedente, i campi aggiuntivi presenti solo nel chiamante vengono reimpostati sui valori predefiniti. Rispetto a Rust, un tipo inout normale riceve &mut Vec<T> , e un tipo inout list riceve &mut Vec<T> .

UTF8/UTF16

Con il backend CPP puoi scegliere se le stringhe sono utf-8 o utf-16. Dichiara le stringhe come @utf8InCpp String in AIDL per convertirle automaticamente in utf-8. I backend NDK e Rust utilizzano sempre stringhe utf-8. Per ulteriori informazioni sull'annotazione utf8InCpp , vedere Annotazioni in AIDL .

Nullabilità

Puoi annotare i tipi che possono essere null nel backend Java con @nullable per esporre valori null ai backend CPP e NDK. Nel backend Rust questi tipi @nullable sono esposti come Option<T> . I server nativi rifiutano i valori null per impostazione predefinita. Le uniche eccezioni a ciò sono i tipi interface e IBinder , che possono sempre essere null per le letture NDK e le scritture CPP/NDK. Per ulteriori informazioni sull'annotazione nullable , vedere Annotazioni in AIDL .

Pacchi personalizzati

Un parcelable personalizzato è un parcelable implementato manualmente in un backend di destinazione. Utilizzare i pacchetti personalizzati solo quando si tenta di aggiungere il supporto ad altre lingue per un pacchetto personalizzato esistente che non può essere modificato.

Per dichiarare un custom parcelable in modo che AIDL ne sia a conoscenza, la dichiarazione AIDL parcelable si presenta così:

    package my.pack.age;
    parcelable Foo;

Per impostazione predefinita, dichiara un Java parcelable dove my.pack.age.Foo è una classe Java che implementa l'interfaccia Parcelable .

Per una dichiarazione di un backend CPP personalizzato parcellizzabile in AIDL, utilizzare cpp_header :

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

L'implementazione C++ in my/pack/age/Foo.h assomiglia a questa:

    #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);
    };

Per una dichiarazione di un NDK personalizzato impacchettabile in AIDL, utilizzare ndk_header :

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

L'implementazione NDK in android/pack/age/Foo.h assomiglia a questa:

    #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 sperimentale), per la dichiarazione di un Rust personalizzato parcellizzabile in AIDL, utilizzare rust_type :

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

L'implementazione di Rust in rust_crate/src/lib.rs assomiglia a questa:

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

Quindi puoi utilizzare questo parcelable come tipo nei file AIDL, ma non verrà generato da AIDL. Fornire gli operatori < e == per i parcelable personalizzati del backend CPP/NDK per utilizzarli in union .

Valori standard

I parcelable strutturati possono dichiarare valori predefiniti per campo per primitive, String e matrici di questi tipi.

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

Nel backend Java quando mancano i valori predefiniti, i campi vengono inizializzati come valori zero per i tipi primitivi e null per i tipi non primitivi.

In altri backend, i campi vengono inizializzati con valori inizializzati predefiniti quando i valori predefiniti non sono definiti. Ad esempio, nel backend C++, i campi String vengono inizializzati come una stringa vuota e i campi List<T> vengono inizializzati come un vector<T> vuoto. I campi @nullable vengono inizializzati come campi con valore null.

Gestione degli errori

Il sistema operativo Android fornisce tipi di errore integrati per i servizi da utilizzare durante la segnalazione degli errori. Questi vengono utilizzati dal raccoglitore e possono essere utilizzati da qualsiasi servizio che implementa un'interfaccia del raccoglitore. Il loro utilizzo è ben documentato nella definizione AIDL e non richiedono alcuno stato o tipo restituito definito dall'utente.

Parametri di output con errori

Quando una funzione AIDL segnala un errore, la funzione potrebbe non inizializzare o modificare i parametri di output. Nello specifico, i parametri di output possono essere modificati se l'errore si verifica durante l'unparceling invece che durante l'elaborazione della transazione stessa. In generale, quando si riceve un errore da una funzione AIDL, tutti i parametri inout e out nonché il valore restituito (che agisce come un parametro out in alcuni backend) dovrebbero essere considerati in uno stato indefinito.

Quali valori di errore utilizzare

Molti dei valori di errore incorporati possono essere utilizzati in qualsiasi interfaccia AIDL, ma alcuni vengono trattati in modo speciale. Ad esempio, EX_UNSUPPORTED_OPERATION e EX_ILLEGAL_ARGUMENT possono essere utilizzati quando descrivono la condizione di errore, ma EX_TRANSACTION_FAILED non deve essere utilizzato perché viene trattato in modo speciale dall'infrastruttura sottostante. Controlla le definizioni specifiche del backend per ulteriori informazioni su questi valori integrati.

Se l'interfaccia AIDL richiede valori di errore aggiuntivi che non sono coperti dai tipi di errore integrati, può utilizzare lo speciale errore integrato specifico del servizio che consente l'inclusione di un valore di errore specifico del servizio definito dall'utente . Questi errori specifici del servizio sono in genere definiti nell'interfaccia AIDL come const int o enum con supporto int e non vengono analizzati dal raccoglitore.

In Java, gli errori vengono associati a eccezioni, come android.os.RemoteException . Per le eccezioni specifiche del servizio, Java utilizza android.os.ServiceSpecificException insieme all'errore definito dall'utente.

Il codice nativo in Android non utilizza eccezioni. Il backend CPP utilizza android::binder::Status . Il backend NDK utilizza ndk::ScopedAStatus . Ogni metodo generato da AIDL restituisce uno di questi, che rappresenta lo stato del metodo. Il backend Rust utilizza gli stessi valori del codice di eccezione dell'NDK, ma li converte in errori Rust nativi ( StatusCode , ExceptionCode ) prima di consegnarli all'utente. Per errori specifici del servizio, lo Status o ScopedAStatus restituito utilizza EX_SERVICE_SPECIFIC insieme all'errore definito dall'utente.

I tipi di errore incorporati possono essere trovati nei seguenti file:

Backend Definizione
Giava android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
Ruggine android/binder_status.h

Utilizzando vari backend

Queste istruzioni sono specifiche per il codice della piattaforma Android. Questi esempi utilizzano un tipo definito, my.package.IFoo . Per istruzioni su come utilizzare il backend Rust, vedere l' esempio Rust AIDL nella pagina Android Rust Patterns .

Importazione di tipi

Se il tipo definito è un'interfaccia, parcelable o union, puoi importarlo in Java:

import my.package.IFoo;

O nel backend CPP:

#include <my/package/IFoo.h>

O nel backend NDK (nota lo spazio dei nomi aidl extra):

#include <aidl/my/package/IFoo.h>

O nel backend di Rust:

use my_package::aidl::my::package::IFoo;

Sebbene sia possibile importare un tipo annidato in Java, nei backend CPP/NDK è necessario includere l'intestazione per il suo tipo root. Ad esempio, quando si importa un tipo Bar annidato definito in my/package/IFoo.aidl ( IFoo è il tipo radice del file) è necessario includere <my/package/IFoo.h> per il backend CPP (o <aidl/my/package/IFoo.h> per il backend NDK).

Servizi di implementazione

Per implementare un servizio, è necessario ereditare dalla classe stub nativa. Questa classe legge i comandi dal driver del raccoglitore ed esegue i metodi implementati. Immagina di avere un file AIDL come questo:

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

In Java, devi estendere da questa classe:

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

Nel backend CPP:

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

Nel backend NDK (nota lo spazio dei nomi aidl aggiuntivo):

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

Nel backend di Rust:

    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(())
        }
    }

O con Rust asincrono:

    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(())
        }
    }

Registrarsi e ottenere servizi

I servizi sulla piattaforma Android vengono generalmente registrati con il processo servicemanager . Oltre alle API riportate di seguito, alcune API controllano il servizio (ovvero ritornano immediatamente se il servizio non è disponibile). Controlla l'interfaccia servicemanager corrispondente per i dettagli esatti. Queste operazioni possono essere eseguite solo durante la compilazione su piattaforma Android.

In Giava:

    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"));

Nel backend CPP:

    #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"));

Nel backend NDK (nota lo spazio dei nomi aidl extra):

    #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")));

Nel backend di Rust:

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()
}

Nel backend asincrono di Rust, con un runtime a thread singolo:

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
}

Una differenza importante rispetto alle altre opzioni è che non chiamiamo join_thread_pool quando utilizziamo Rust asincrono e un runtime a thread singolo. Questo perché è necessario fornire a Tokio un thread in cui possa eseguire le attività generate. In questo esempio, il thread principale servirà a tale scopo. Qualsiasi attività generata utilizzando tokio::spawn verrà eseguita sul thread principale.

Nel backend asincrono di Rust, con un runtime multi-thread:

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

Con il runtime Tokio multi-thread, le attività generate non vengono eseguite sul thread principale. Pertanto, ha più senso chiamare join_thread_pool sul thread principale in modo che il thread principale non sia semplicemente inattivo. È necessario racchiudere la chiamata in block_in_place per lasciare il contesto asincrono.

Puoi richiedere di ricevere una notifica quando un servizio che ospita un raccoglitore muore. Ciò può aiutare a evitare perdite di proxy di richiamata o assistere nel ripristino degli errori. Effettua queste chiamate sugli oggetti proxy del raccoglitore.

  • In Java, utilizzare android.os.IBinder::linkToDeath .
  • Nel backend CPP, utilizzare android::IBinder::linkToDeath .
  • Nel backend NDK, utilizza AIBinder_linkToDeath .
  • Nel backend Rust, crea un oggetto DeathRecipient , quindi chiama my_binder.link_to_death(&mut my_death_recipient) . Tieni presente che poiché DeathRecipient possiede la richiamata, devi mantenere attivo l'oggetto finché desideri ricevere notifiche.

Informazioni sul chiamante

Quando si riceve una chiamata al raccoglitore del kernel, le informazioni sul chiamante sono disponibili in diverse API. Il PID (o ID processo) si riferisce all'ID del processo Linux del processo che sta inviando una transazione. L'UID (o User ID) si riferisce all'ID utente Linux. Quando si riceve una chiamata unidirezionale, il PID chiamante è 0. All'esterno di un contesto di transazione del raccoglitore, queste funzioni restituiscono il PID e l'UID del processo corrente.

Nel backend Java:

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

Nel backend CPP:

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

Nel backend NDK:

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

Nel backend Rust, quando si implementa l'interfaccia, specificare quanto segue (invece di consentirne l'impostazione predefinita):

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

Segnalazioni di bug e API di debug per i servizi

Quando vengono eseguiti i bugreport (ad esempio, con adb bugreport ), raccolgono informazioni da tutto il sistema per facilitare il debug di vari problemi. Per i servizi AIDL, le segnalazioni di bug utilizzano il dumpsys binario su tutti i servizi registrati con il gestore del servizio per inserire le proprie informazioni nella segnalazione di bug. È inoltre possibile utilizzare dumpsys sulla riga di comando per ottenere informazioni da un servizio con dumpsys SERVICE [ARGS] . Nei backend C++ e Java, puoi controllare l'ordine in cui i servizi vengono scaricati utilizzando argomenti aggiuntivi per addService . Puoi anche utilizzare dumpsys --pid SERVICE per ottenere il PID di un servizio durante il debug.

Per aggiungere un output personalizzato al tuo servizio, puoi sovrascrivere il metodo dump nell'oggetto server come se stessi implementando qualsiasi altro metodo IPC definito in un file AIDL. In questo caso, dovresti limitare il dumping all'autorizzazione dell'app android.permission.DUMP o limitare il dumping a UID specifici.

Nel backend Java:

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

Nel backend CPP:

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

Nel backend NDK:

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

Nel backend Rust, quando si implementa l'interfaccia, specificare quanto segue (invece di consentirne l'impostazione predefinita):

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

Ottenimento dinamico del descrittore dell'interfaccia

Il descrittore di interfaccia identifica il tipo di interfaccia. Ciò è utile durante il debug o quando si dispone di un raccoglitore sconosciuto.

In Java, puoi ottenere il descrittore dell'interfaccia con codice come:

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

Nel backend CPP:

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

I backend NDK e Rust non supportano questa funzionalità.

Ottenimento statico del descrittore dell'interfaccia

A volte (come quando si registrano i servizi @VintfStability ), è necessario sapere qual è staticamente il descrittore dell'interfaccia. In Java, puoi ottenere il descrittore aggiungendo codice come:

    import my.package.IFoo;
    ... IFoo.DESCRIPTOR

Nel backend CPP:

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

Nel backend NDK (nota lo spazio dei nomi aidl aggiuntivo):

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

Nel backend di Rust:

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

Intervallo di enumerazione

Nei backend nativi, puoi scorrere i possibili valori che un'enumerazione può assumere. A causa di considerazioni sulla dimensione del codice, questo non è attualmente supportato in Java.

Per un'enumerazione MyEnum definita in AIDL, l'iterazione viene fornita come segue.

Nel backend CPP:

    ::android::enum_range<MyEnum>()

Nel backend NDK:

   ::ndk::enum_range<MyEnum>()

Nel backend di Rust:

    MyEnum::enum_values()

Gestione dei thread

Ogni istanza di libbinder in un processo mantiene un pool di thread. Per la maggior parte dei casi d'uso, dovrebbe trattarsi esattamente di un pool di thread, condiviso su tutti i backend. L'unica eccezione a ciò è quando il codice del fornitore potrebbe caricare un'altra copia di libbinder per comunicare con /dev/vndbinder . Poiché si trova su un nodo di associazione separato, il pool di thread non è condiviso.

Per il backend Java, il threadpool può solo aumentare di dimensioni (poiché è già avviato):

    BinderInternal.setMaxThreads(<new larger value>);

Per il backend CPP sono disponibili le seguenti operazioni:

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

Allo stesso modo, nel backend NDK:

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

Nel backend di Rust:

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

Con il backend asincrono Rust, sono necessari due threadpool: binder e Tokio. Ciò significa che le applicazioni che utilizzano Rust asincrono necessitano di considerazioni speciali, soprattutto quando si tratta dell'uso di join_thread_pool . Consulta la sezione sulla registrazione dei servizi per ulteriori informazioni al riguardo.

Nomi riservati

C++, Java e Rust riservano alcuni nomi come parole chiave o per usi specifici del linguaggio. Sebbene AIDL non applichi restrizioni in base alle regole del linguaggio, l'utilizzo di nomi di campo o di tipo che corrispondono a un nome riservato potrebbe comportare un errore di compilazione per C++ o Java. Per Rust, il campo o il tipo viene rinominato utilizzando la sintassi "raw identifier", accessibile utilizzando il prefisso r# .

Ti consigliamo di evitare, ove possibile, di utilizzare nomi riservati nelle definizioni AIDL per evitare associazioni non ergonomiche o veri e propri errori di compilazione.

Se hai già nomi riservati nelle definizioni AIDL, puoi rinominare i campi in modo sicuro pur rimanendo compatibile con il protocollo; potrebbe essere necessario aggiornare il codice per continuare a creare, ma tutti i programmi già creati continueranno a interagire.

Nomi da evitare: * Parole chiave C++ * Parole chiave Java * Parole chiave Rust