Backend AIDL

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

Nella tabella seguente, la stabilità della superficie dell'API si riferisce alla capacità di compilare il codice a fronte di questa piattaforma API in modo che il codice possa essere fornito indipendentemente 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 la gestione dei servizi, sono riservate per l'utilizzo interno alla piattaforma 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 è basata su libbinder_ndk, che gli consente di essere stabile e portabile. Gli APEX usano la cassa di raccoglitore come fa chiunque altro sul lato del sistema. La parte relativa alla ruggine è inserita in un APEX e spedita al suo interno. Dipende da libbinder_ndk.so della partizione di sistema.

Crea sistemi

A seconda del backend, esistono due modi per compilare AIDL in codice stub. Per ulteriori dettagli sui sistemi di build, consulta la pagina Riferimento al modulo Soong.

Sistema di build principale

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

interfaccia_aidl

I tipi utilizzati con questo sistema di compilazione devono essere strutturati. Per essere strutturati, i pacchettizzabili devono contenere campi direttamente e non essere dichiarazioni di tipi definiti direttamente nelle lingue di destinazione. Per scoprire come l'AIDL strutturato si adatta all'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 il file dell'interfaccia risultante. 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/A N/A
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/A 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à.

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, ad eccezione degli 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 dimensioni fisse. 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 oggetto std::shared_ptr\<T\> che viene gestito anche internamente, nel caso in cui il binder sia di proprietà di un altro processo. La creazione dell'oggetto in altri modi causa la doppia proprietà.

Direzionalità (in/out/inout)

Quando specifichi i tipi di argomenti per le funzioni, puoi specificarle come in, out o inout. Questo controlla in quale direzione vengono passate le informazioni per una chiamata IPC. in è la direzione predefinita e indica che i dati vengono trasmessi dal chiamante al chiamante. out significa che i dati vengono trasmessi dal chiamato al chiamante. inout è la combinazione di entrambi. Tuttavia, il team 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 meno recente, i campi aggiuntivi presenti solo nel chiamante vengono ripristinati sui valori predefiniti. Per quanto riguarda Rust, un normale tipo inout riceve &mut Vec<T>, mentre un tipo di elenco 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 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, consulta Annotazioni in AIDL.

Nullabilità

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

Lotti personalizzati

Un parcelable personalizzato è un elemento parcelable che viene implementato manualmente in un backend di destinazione. Utilizza i valori parcelable personalizzati solo quando stai tentando di aggiungere il supporto di altre lingue per un parcelable personalizzato esistente che non può essere modificato.

Per dichiarare un elemento Parcelable personalizzato in modo che AIDL ne sia a conoscenza, la dichiarazione AIDL parcelabile ha il seguente aspetto:

    package my.pack.age;
    parcelable Foo;

Per impostazione predefinita, dichiara un codice Java applicabile, dove my.pack.age.Foo è una classe Java 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 (AOSP sperimentale), per la dichiarazione di una Rust personalizzata parcelabile in AIDL, utilizza 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 utilizzare questo partibile come tipo di file AIDL, ma non verrà generato da AIDL. Fornisci gli operatori < e == per gli oggetti parcelable personalizzati del backend CPP/NDK da utilizzare in union.

Valori predefiniti

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

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

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

In altri backend, i campi vengono inizializzati con valori predefiniti inizializzati 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 per la segnalazione degli 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 alcuno stato definito dall'utente o tipo restituito.

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 la separazione dei pacchetti e non durante l'elaborazione della transazione stessa. In generale, quando ricevi un errore da una funzione AIDL, tutti i parametri inout e out e il valore restituito (che agisce come un parametro out in alcuni backend) devono essere considerati in uno stato indefinito.

Quali valori di errore utilizzare

Molti dei valori di errore integrati possono essere utilizzati in qualsiasi interfaccia AIDL, ma alcuni vengono trattati in modo speciale. Ad esempio, i valori EX_UNSUPPORTED_OPERATION e EX_ILLEGAL_ARGUMENT possono essere utilizzati quando descrivono la condizione di errore, ma EX_TRANSACTION_FAILED non devono essere utilizzati perché viene trattato come 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, è possibile che venga usato 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 vengono in genere definiti nell'interfaccia AIDL come const int o enum supportato da int e non vengono analizzati dal raccoglitore.

In Java, gli errori vengono mappati a eccezioni, ad esempio android.os.RemoteException. Per le eccezioni specifiche dei servizi, 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 ne restituisce uno, che rappresenta lo stato del metodo. Il backend Rust utilizza gli stessi valori di codice di eccezione dell'NDK, ma li converte in errori Rust nativi (StatusCode, ExceptionCode) prima di inviarli all'utente. Per gli errori specifici del servizio, il valore Status o ScopedAStatus restituito utilizza EX_SERVICE_SPECIFIC insieme all'errore 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 tipo definito, my.package.IFoo. Per istruzioni su come utilizzare il backend Rust, consulta l'esempio di Rust AIDL nella pagina Modelli di ruggine di Android.

Tipi di importazione

Che il tipo definito sia un'interfaccia, un Parcelable o union, puoi importarlo 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 includere l'intestazione per il tipo root. Ad esempio, quando importi un tipo nidificato Bar definito in my/package/IFoo.aidl (IFoo è il tipo radice del file), 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. Questa classe legge i comandi dal driver binder ed esegue i metodi che implementi. 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 nella piattaforma Android vengono registrati tramite la procedura servicemanager. Oltre alle API riportate di seguito, alcune API controllano il servizio (ossia, tornano immediatamente se il servizio non è disponibile). Per i dettagli esatti, controlla l'interfaccia di servicemanager corrispondente. Queste 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 se utilizzi Rust asincrono e un runtime a thread singolo. Questo perché devi fornire a Tokio un thread in cui possa eseguire le attività generate. In questo esempio, il thread principale servirà a questo scopo. Tutte le attività generate utilizzando tokio::spawn verranno eseguite 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 nel thread principale. Di conseguenza, ha più senso chiamare join_thread_pool sul thread principale, in modo che quest'ultimo non sia solo inattivo. Devi eseguire il wrapping della chiamata in block_in_place per lasciare il 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 finché 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 processo che invia una transazione. L'UID (o User-ID) si riferisce all'ID utente Linux. Quando ricevi una chiamata unidirezionale, il PID della chiamata è 0. All'esterno del contesto di una transazione binder, 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 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 utilizzano il file binario dumpsys su tutti i servizi registrati con il gestore del servizio per scaricare le relative informazioni nella 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, puoi controllare l'ordine in cui i servizi vengono sottoposti a dump 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 servizio, puoi eseguire l'override del metodo dump nell'oggetto server come stai implementando qualsiasi altro metodo IPC definito in un file AIDL. In questo caso, devi limitare il dump all'autorizzazione dell'app android.permission.DUMP o limitarlo 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. Questo è utile durante il debug o quando hai 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 durante la registrazione dei servizi @VintfStability, hai bisogno di sapere qual è il descrittore dell'interfaccia in modo statico. 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 Rust:

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

Intervallo enum

Nei backend nativi, puoi eseguire l'iterazione dei possibili valori che un'enumerazione può assumere. 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. Nella maggior parte dei casi d'uso, 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 comunicare 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à 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();

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 utilizzano la modalità asincrona di Rust richiedono considerazioni specifiche, soprattutto in relazione all'utilizzo di join_thread_pool. Per ulteriori informazioni, consulta la sezione sulla registrazione dei servizi.

Nomi riservati

C++, Java e Rust riservano alcuni nomi come parole chiave o per uso specifico di un linguaggio. Sebbene AIDL non applichi limitazioni basate sulle regole del linguaggio, l'utilizzo di nomi di campi o tipi che corrispondono a un nome riservato potrebbe causare un errore di compilazione per C++ o Java. Per Rust, il campo o il tipo viene rinominato utilizzando la sintassi "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 completi.

Se hai già nomi riservati nelle definizioni AIDL, puoi rinominare in sicurezza i campi mantenendo la compatibilità con il protocollo. Potresti dover aggiornare il codice per continuare a creare, ma tutti i programmi già creati continueranno a interoperare.

Nomi da evitare: