Backend AIDL

Un backend AIDL è una destinazione per la generazione di codice stub. Quando utilizzi i file AIDL, usarle sempre in una lingua specifica con un runtime specifico. In base a del contesto, dovresti usare backend AIDL diversi.

Nella tabella seguente, la stabilità della superficie dell'API si riferisce alla capacità per compilare il codice a fronte di questa piattaforma API in modo che possa essere forniti in modo indipendente dal programma binario system.img libbinder.so.

AIDL ha i seguenti backend:

Backend Lingua Piattaforma API Crea sistemi
Java Java SDK/SystemApi (stabile*) tutti
ND C++ libbinder_ndk (stabile*) interfaccia_aidl
CPP C++ libbinder (instabile) tutti
Rust Rust libbinder_rs (stabile*) interfaccia_aidl
  • Queste piattaforme API sono stabili, ma molte API, come quelle per i servizi per la gestione, sono riservate all'uso interno della piattaforma e non sono disponibili app. Per ulteriori informazioni su come utilizzare AIDL nelle app, vedi documentazione per gli sviluppatori.
  • Il backend Rust è stato introdotto in Android 12; il Il backend NDK è disponibile a partire da Android 10.
  • La cassa Rust è costruita su libbinder_ndk, il che consente di essere stabile e portabile. Gli APEX usano la cassa raccoglitrice come chiunque altro altro lato sistema. La parte "ruggine" viene inserita in un APEX e spedita al suo interno. Dipende da libbinder_ndk.so della partizione di sistema.

Crea sistemi

A seconda del backend, ci sono due modi per compilare AIDL in stub le API nel tuo codice. Per ulteriori dettagli sui sistemi di compilazione, consulta Riferimento al modulo Soong.

Sistema di build principale

In qualsiasi modulo Android.bp cc_ o java_ (o nei rispettivi Android.mk equivalenti), I file .aidl possono essere specificati come file di origine. In questo caso, il linguaggio Java/CPP i backend di AIDL (non il backend NDK) e le classi per l'utilizzo i file AIDL corrispondenti vengono aggiunti al modulo automaticamente. Opzioni come local_include_dirs, che indica al sistema di compilazione il percorso root I file AIDL in quel modulo possono essere specificati in questi moduli in un aidl: gruppo. Tieni presente che il backend Rust può essere utilizzato solo con Rust. rust_ moduli sono e vengono gestiti in modo diverso, in quanto i file AIDL non sono specificati come file di origine. Il modulo aidl_interface produce invece un rustlib chiamato <aidl_interface name>-rust a cui è possibile collegarsi. Per ulteriori dettagli, vedi nell'esempio Rust AIDL.

interfaccia_aidl

I tipi utilizzati con questo sistema di compilazione devono essere strutturati. Per essere strutturati, i parcelables devono contenere campi direttamente e non essere dichiarazioni di tipi definiti direttamente nelle lingue di destinazione. Per capire come l'AIDL strutturato si integra con AIDL stabile, consulta AIDL strutturato e stabile.

Tipi

Puoi considerare il compilatore aidl come implementazione di riferimento per i tipi. Quando crei un'interfaccia, richiama aidl --lang=<backend> ... per visualizzare risultante dal file di interfaccia. Quando utilizzi 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
boolean bool bool bool
byte int8_t int8_t i8
carattere car16_t car16_t U16
int int32_t int32_t i32
lunghi int64_t int64_t i64
in virgola mobile in virgola mobile in virgola mobile F32
doppio doppio doppio F64
Stringa android::String16 std::stringa Stringa
android.os.Parcelable android::Ordinabile N/D N/D
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
G[] std::vector<T> std::vector<T> In: &[T]
Fuori: Vec<T>
byte[] std::vector<uint8_t> std::vector<int8_t>1 In: &[u8]
Out: Vec<u8>
Elenco<T> std::vector<T>2 std::vector<T>3 In: &[T]4
Out: Vec<T>
Descriptor android::base::unique_fd N/D binder::parcel::ParcelFileDescriptor
DescrittoreFileFile android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
tipo di interfaccia (T) android::sp<T> std::shared_ptr<T>7 binder::Forza
tipo parcelable (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à. di Gemini Advanced.

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

3. Il backend NDK supporta List<T>, dove T è uno di String, ParcelFileDescriptor o parcelable. In Android 13 o superiore, T può essere qualsiasi tipo non primitivo, ad eccezione degli array. .

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

5. I tipi di unione sono supportati in Android 12 e in alto. di Gemini Advanced.

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

7. Per creare un'istanza di un oggetto binder SharedRefBase, utilizza SharedRefBase::make\<My\>(... args ...). Questa funzione crea un'istanza std::shared_ptr\<T\> oggetto che è anche gestito internamente, nel caso in cui il raccoglitore sia di proprietà di un altro e il processo di sviluppo. La creazione dell'oggetto in altri modi causa la doppia proprietà. di Gemini Advanced.

Direzionalità (in/out/inout)

Quando specifichi i tipi di argomenti delle funzioni, puoi specificare come in, out o inout. Questa opzione determina la direzione in cui le informazioni vengono passato per una chiamata IPC. in è la direzione predefinita e indica che i dati sono passati dal chiamante al chiamante. out indica che i dati vengono trasmessi chiamato direttamente al chiamante. inout è la combinazione di entrambi. Tuttavia, Il team di Android consiglia di evitare di utilizzare l'indicatore di argomento inout. Se utilizzi inout con un'interfaccia con il controllo delle versioni e un chiamante precedente, Vengono ripristinati i valori predefiniti dei campi aggiuntivi presenti solo nel chiamante e i relativi valori. Per quanto riguarda Rust, un normale tipo inout riceve &mut Vec<T> e un elenco di tipo inout riceve &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

Con il backend CPP puoi scegliere se le stringhe devono essere utf-8 o utf-16. Dichiara 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 l'annotazione utf8InCpp, consulta la sezione Annotazioni in AIDL.

Nullabilità

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

Lotti personalizzati

Un parcelable personalizzato è un elemento parcelable che viene implementato manualmente in una destinazione. di un backend cloud. Utilizza i particella personalizzata solo quando stai tentando di aggiungere supporto ad altri lingue per un elemento parcelable personalizzato esistente che non può essere modificato.

Per dichiarare una particella personalizzata in modo che AIDL ne sia a conoscenza, l'AIDL dichiarazione parcelable ha il seguente aspetto:

    package my.pack.age;
    parcelable Foo;

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

Per una dichiarazione di un backend CPP personalizzato condivisibile in AIDL, utilizza cpp_header:

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

L'implementazione C++ in my/pack/age/Foo.h ha il seguente aspetto:

    #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 valore NDK personalizzato parcelabile in AIDL, utilizza ndk_header:

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

L'implementazione di NDK in android/pack/age/Foo.h ha il seguente aspetto:

    #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 (sperimentale AOSP), per la dichiarazione di Rust personalizzata partibile in AIDL, usa rust_type:

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

L'implementazione di Rust in rust_crate/src/lib.rs ha il seguente aspetto:

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 usare questo partibile come tipo nei file AIDL, ma non generati da AIDL. Fornisci gli operatori < e == per il backend CPP/NDK lotti personalizzati da utilizzare in union.

Valori predefiniti

Le particella strutturate possono dichiarare valori predefiniti per campo per le primitive, String e array di questi tipi.

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

Nel backend Java, quando mancano 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 per impostazione predefinita quando i valori predefiniti non sono definiti. Ad esempio, nel backend C++, i campi String vengono inizializzati come stringa vuota, mentre i campi List<T> vengono inizializzati come vector<T> vuoto. I campi @nullable sono inizializzati come campi con valore nullo.

Gestione degli errori

Il sistema operativo Android fornisce tipi di errori integrati per i servizi da utilizzare durante la generazione di report errori. Vengono utilizzati da binder e possono essere utilizzati da qualsiasi servizio che implementa un'interfaccia binder. Il loro utilizzo è ben documentato nella definizione dell'AIDL e non richiedono uno stato definito dall'utente o un tipo restituito.

Parametri di output con errori

Quando una funzione AIDL segnala un errore, la funzione potrebbe non essere inizializzata o modificare i parametri di output. In particolare, i parametri di output possono essere modificati se si verifica durante la separazione dei pacchetti, anziché che si verifica durante l'elaborazione della transazione stessa. In generale, quando ricevi un errore da un AIDL funzione, tutti i parametri inout e out e il valore restituito (che agisce come un parametro out in alcuni backend) deve essere considerato in indefinito.

Quali valori di errore utilizzare

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

Se l'interfaccia AIDL richiede ulteriori valori di errore che non sono coperti dalle i tipi di errore integrati, allora potrebbero usare lo stato che consente l'inclusione di un valore di errore specifico del servizio definiti dall'utente. Questi errori specifici dei servizi in genere vengono definiti Interfaccia AIDL come enum con supporto di const int o int e non viene analizzata da .

In Java, gli errori vengono mappati a eccezioni, ad esempio android.os.RemoteException. Per 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 evento generato da AIDL restituisce uno di questi, che rappresenta lo stato del . Il backend Rust utilizza gli stessi valori di codice di eccezione di NDK, ma li converte in errori Rust nativi (StatusCode, ExceptionCode) prima per mostrarli all'utente. Per errori specifici del servizio, il valore Status o ScopedAStatus utilizza EX_SERVICE_SPECIFIC insieme a definito dall'utente.

I tipi di errori integrati sono disponibili nei seguenti file:

Backend Definizione
Java android/os/Parcel.java
CPP binder/Status.h
ND android/binder_status.h
Rust android/binder_status.h

Utilizzare vari backend

Queste istruzioni sono specifiche per il codice della piattaforma Android. Questi esempi utilizzano un parametro tipo definito, my.package.IFoo. Per istruzioni su come utilizzare il backend Rust, vedi l'esempio di Rust AIDL sui modelli di ruggine di Android .

Tipi di importazione

Che il tipo definito sia un'interfaccia, un particolabile o un'unione, puoi importare in Java:

import my.package.IFoo;

Nel backend CPP:

#include <my/package/IFoo.h>

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

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

Oppure, nel backend Rust:

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

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

Implementare i servizi

Per implementare un servizio, è necessario ereditare la classe stub nativa. Questo corso legge i comandi dal driver binder ed esegue i metodi che da implementare. Immagina di avere un file AIDL come questo:

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

In Java, devi estenderti 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 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(())
        }
    }

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

Registrati e ricevi servizi

In genere, i servizi sulla piattaforma Android sono registrati presso il servicemanager e il processo di sviluppo. Oltre alle API riportate di seguito, alcune API controllano la (in altre parole, tornano immediatamente se il servizio non è disponibile). Per i dettagli esatti, controlla l'interfaccia di servicemanager corrispondente. Questi operazioni possono essere eseguite solo durante la compilazione sulla piattaforma Android.

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

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 aggiuntivo):

    #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 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 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 dalle altre opzioni è che non chiamiamo join_thread_pool quando utilizzi Rust asincrono e un runtime a thread singolo. Questo è perché devi dare a Tokio un thread in cui può eseguire le attività generate. Nella in questo esempio il thread principale serverà a questo scopo. Qualsiasi attività generata utilizzando tokio::spawn verrà eseguito sul thread principale.

Nel backend asincrono 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 nella . Di conseguenza, è più logico chiamare join_thread_pool sull'istanza principale in modo che il thread principale non sia solo inattivo. Devi terminare la chiamata block_in_place per uscire dal contesto asincrono.

Puoi richiedere di ricevere una notifica quando un servizio che ospita un raccoglitore muore. In questo modo è possibile evitare la perdita dei proxy di callback o agevolare il ripristino degli errori. Effettua queste chiamate su oggetti proxy di binder.

  • In Java, utilizza android.os.IBinder::linkToDeath.
  • Nel backend CPP, utilizza android::IBinder::linkToDeath.
  • Nel backend NDK, usa 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 è proprietario del callback, devi mantenere attivo l'oggetto per tutto il tempo vuoi ricevere le notifiche.

Informazioni sul chiamante

Quando si riceve una chiamata a binder kernel, le informazioni sul chiamante sono disponibili in diverse API. Il PID (o ID processo) si riferisce all'ID di processo Linux del che invia una transazione. L'UID (o User-ID) si riferisce alla ID utente Linux. Quando ricevi una chiamata unidirezionale, il PID della chiamata è 0. Quando al di fuori di un contesto di transazione binder, queste funzioni restituiscono il PID e l'UID del processo attuale.

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 implementi l'interfaccia, specifica quanto segue (anziché consentire l'impostazione predefinita):

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

Segnalazioni di bug e API di debug per i servizi

Quando vengono eseguite segnalazioni di bug (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 usano il file binario dumpsys su tutti i servizi si è registrato con il gestore del servizio per eseguire il dump delle sue informazioni segnalazione di bug. Puoi anche utilizzare dumpsys nella riga di comando per ottenere informazioni da un servizio con dumpsys SERVICE [ARGS]. Nei backend C++ e Java, può controllare l'ordine in cui viene eseguito il dump dei servizi usando argomenti aggiuntivi a addService. Puoi anche usare dumpsys --pid SERVICE per ottenere il PID di un durante il debug.

Per aggiungere output personalizzato al servizio, puoi eseguire l'override di dump nell'oggetto server allo stesso modo di qualsiasi altro metodo IPC definiti in un file AIDL. Nel farlo, devi limitare il dumping all'app l'autorizzazione android.permission.DUMP o limita 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 implementi l'interfaccia, specifica quanto segue (anziché consentire l'impostazione predefinita):

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

Ottieni un descrittore dell'interfaccia in modo dinamico

Il descrittore dell'interfaccia identifica il tipo di un'interfaccia. È utile durante il debug o quando è presente un binder 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à.

Ottieni descrittore dell'interfaccia in modo statico

A volte, ad esempio quando registri i servizi @VintfStability, devi conoscere in modo statico il descrittore dell'interfaccia. In Java, puoi ottenere descrittore aggiungendo un 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 Rust:

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

Intervallo enum

Nei backend nativi, puoi eseguire l'iterazione dei possibili valori che un'enumerazione può assumere attiva. A causa di considerazioni relative alle dimensioni del codice, questa funzionalità non è supportata in Java.

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

Nel backend CPP:

    ::android::enum_range<MyEnum>()

Nel backend NDK:

   ::ndk::enum_range<MyEnum>()

Nel backend Rust:

    MyEnum::enum_values()

Gestione dei thread

Ogni istanza di libbinder in un processo mantiene un pool di thread. Per la maggior parte dovrebbe essere esattamente un pool di thread, condiviso tra tutti i backend. L'unica eccezione si verifica quando il codice del fornitore potrebbe caricare un'altra copia di libbinder per parlare con /dev/vndbinder. Poiché si trova su un nodo binder separato, il pool di thread non è condiviso.

Per il backend Java, le dimensioni del pool di thread possono solo aumentare (poiché già avviata):

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

Analogamente, nel backend NDK:

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

Nel backend 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 Rust asincrono sono necessari due pool di thread: binder e Tokio. Ciò significa che le app che usano la modalità asincrona di Rust richiedono considerazioni speciali, in particolare per quanto riguarda l'uso di join_thread_pool. Consulta la sezione relativa di registrazione dei dati di prodotto per ulteriori informazioni in merito.

Nomi riservati

C++, Java e Rust riservano alcuni nomi come parole chiave o per termini specifici di linguaggio per gli utilizzi odierni. Sebbene AIDL non applichi restrizioni in base alle regole linguistiche, l'uso nomi di campi o tipi che corrispondono a un nome riservato potrebbero generare una compilazione per C++ o Java. Per Rust, il campo o il tipo viene rinominato utilizzando "identificatore non elaborato" accessibile tramite il prefisso r#.

Ti consigliamo di evitare di utilizzare nomi riservati nelle definizioni AIDL ove possibile per evitare associazioni non ergonomiche o errori di compilazione del tutto.

Se hai già dei nomi riservati nelle definizioni AIDL, puoi tranquillamente rinominare i campi mantenendo la compatibilità con il protocollo; potresti dover aggiornare codice per continuare a creare, ma i programmi già creati continueranno a interagiscono tra loro.

Nomi da evitare: