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 backend AIDL diversi.

AIDL ha i seguenti backend:

Backend Lingua Superficie API Costruisci sistemi
Giava Giava SDK/SystemApi (stabile*) Tutto
NDK C++ libbinder_ndk (stabile*) aid_interface
CPP C++ libbinder (instabile) Tutto
Ruggine Ruggine libbinder_rs (instabile) aid_interface
  • Queste superfici API sono stabili, ma molte delle API, come quelle per la gestione dei servizi, sono riservate all'uso interno della piattaforma e non sono disponibili per le app. Per ulteriori informazioni su come utilizzare AIDL nelle app, vedere 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 del raccoglitore 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 back-end, ci sono due modi per compilare AIDL in codice stub. Per maggiori dettagli sui sistemi di compilazione, vedere il Riferimento del modulo Soong .

Sistema di costruzione di base

In qualsiasi cc_ o java_ Android.bp (o nei loro equivalenti Android.mk ), 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 per utilizzare i file AIDL corrispondenti vengono aggiunte automaticamente al modulo. Opzioni come local_include_dirs , che indica al sistema di compilazione il percorso radice dei file AIDL in quel modulo possono essere specificate in questi moduli in un gruppo aidl: Si noti 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. Invece, il modulo aidl_interface produce una rustlib chiamata <aidl_interface name>-rust cui può essere collegato. Per maggiori dettagli, vedere l' esempio Rust AIDL .

aid_interface

Vedere AIDL stabile . I tipi utilizzati con questo sistema di compilazione devono essere strutturati; cioè espresso direttamente in AIDL. Ciò significa che i pacchettizzabili personalizzati non possono essere utilizzati.

Tipi

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

Tipo Java/AIDL Tipo C++ Tipo NDK Tipo ruggine
booleano bollo bollo bollo
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::Piazzabile N / A N / A
IBinder android::IBnder ndk::SpAIBinder raccoglitore::SpIBinder
T[] std::vettore<T> std::vettore<T> In: &T
Fuori: 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
Fuori: Vec<T>
FileDescriptor android::base::unique_fd N / A raccoglitore::pacco::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopeFileDescriptor raccoglitore::pacco::ParcelFileDescriptor
tipo di interfaccia (T) android::sp<T> std::ptr_condiviso<T> raccoglitore::Forte
tipo parcellare (T) T T T
tipo di raccordo (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 invece di int8_t per motivi di compatibilità.

2. Il backend C++ supporta List<T> dove T è uno di String , IBinder , ParcelFileDescriptor o parcelable. In Android T (AOSP sperimentale) 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 back-end NDK supporta List<T> dove T è uno di String , ParcelFileDescriptor o parcelable. In Android T (AOSP sperimentale) 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 si tratti di input (un argomento) o di un output (un valore restituito).

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

6. In Android T (AOSP sperimentale) o versioni successive, sono supportati array di 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.

Direzionalità (in/out/inout)

Quando si specificano i tipi degli argomenti per le funzioni, è possibile specificarli 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 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 argomenti inout . Se utilizzi inout con un'interfaccia con versione e un chiamato meno recente, i campi aggiuntivi che sono presenti solo nel chiamante vengono reimpostati sui valori predefiniti. Per quanto riguarda Rust, un normale tipo inout riceve &mut Vec<T> e un tipo list inout 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 usano sempre stringhe utf-8. Per ulteriori informazioni sull'annotazione utf8InCpp , vedere Annotazioni in AIDL .

Nullabilità

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

Parcellabili personalizzati

Nei backend C++ e Java nel sistema di build di base, puoi dichiarare un parcelable implementato manualmente in un backend di destinazione (in C++ o in Java).

    package my.package;
    parcelable Foo;

o con la dichiarazione di intestazione C++:

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

Quindi puoi utilizzare questo parcelable come tipo nei file AIDL, ma non verrà generato da AIDL.

Rust non supporta i pacchettizzabili personalizzati.

Valori standard

I parcelable strutturati possono dichiarare valori predefiniti per campo per primitive, String e array 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 back-end 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 nullo.

Gestione degli errori

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

Quando una funzione AIDL segnala un errore, la funzione potrebbe non inizializzare o modificare i parametri di uscita. In particolare, i parametri di output possono essere modificati se l'errore si verifica durante l'annullamento della parcellizzazione anziché 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 back-end) dovrebbero essere considerati in uno stato indefinito.

Se l'interfaccia AIDL richiede valori di errore aggiuntivi che non sono coperti dai tipi di errore integrati, possono 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 enum const int o int -backed 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 usa eccezioni. Il backend CPP utilizza android::binder::Status . Il back-end NDK utilizza ndk::ScopedAStatus . Ogni metodo generato da AIDL ne restituisce uno, che rappresenta lo stato del metodo. Il back-end Rust usa gli stessi valori di codice di eccezione dell'NDK, ma li converte in errori Rust nativi ( StatusCode , ExceptionCode ) prima di consegnarli all'utente. Per gli errori specifici del servizio, lo Status restituito o ScopedAStatus utilizza EX_SERVICE_SPECIFIC insieme all'errore definito dall'utente.

I tipi di errore integrati 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

Utilizzo di vari backend

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

Tipi di importazione

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 back-end NDK (notare lo spazio dei nomi di aidl aggiuntivo):

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

O nel backend di Rust:

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

Sebbene sia possibile importare un tipo nidificato in Java, nei backend CPP/NDK è necessario includere l'intestazione per il relativo tipo radice. Ad esempio, quando si importa una Bar di tipo nidificata definita 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 back-end NDK).

Servizi di implementazione

Per implementare un servizio, devi 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 (notare lo spazio dei nomi di 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(())
        }
    }

Registrazione e ottenere servizi

I servizi nella piattaforma Android sono generalmente registrati con il processo servicemanager . Oltre alle API seguenti, alcune API controllano il servizio (il che significa che ritornano immediatamente se il servizio non è disponibile). Controllare l'interfaccia servicemanager corrispondente per i dettagli esatti. Queste operazioni possono essere eseguite solo durante la compilazione sulla piattaforma Android.

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

Nel backend CPP:

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

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

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

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

Puoi richiedere di ricevere una notifica per quando un servizio che ospita un raccoglitore muore. Questo può aiutare a evitare la perdita di proxy di callback o aiutare nel ripristino degli errori. Effettuare queste chiamate su oggetti proxy raccoglitore.

  • In Java, usa android.os.IBinder::linkToDeath .
  • Nel backend CPP, usa 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 possiede la richiamata, devi mantenere vivo quell'oggetto finché desideri ricevere le notifiche.

Segnalazioni di bug e API di debug per i servizi

Quando i bugreport vengono eseguiti (ad esempio, con adb bugreport ), raccolgono informazioni da tutto il sistema per aiutare con 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 dei servizi per scaricare le loro informazioni nella segnalazione di bug. Puoi anche 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 usando argomenti aggiuntivi per addService . Puoi anche usare dumpsys --pid SERVICE per ottenere il PID di un servizio durante il debug.

Per aggiungere output personalizzato al tuo servizio, puoi sovrascrivere il metodo dump nel tuo oggetto server come se stessi implementando qualsiasi altro metodo IPC definito in un file AIDL. Quando si esegue questa operazione, è necessario limitare il dump all'autorizzazione dell'app android.permission.DUMP o limitare il dump 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 back-end 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<()>

Ottenere dinamicamente il 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à.

Descrittore di interfaccia che ottiene staticamente

A volte (come quando si registrano i servizi @VintfStability ), è necessario conoscere 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 (notare lo spazio dei nomi di aidl aggiuntivo):

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

Nel backend di Rust:

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

Intervallo enum

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 enum MyEnum definito 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_range()

Gestione dei fili

Ogni istanza di libbinder in un processo mantiene un threadpool. Per la maggior parte dei casi d'uso, questo dovrebbe essere esattamente un pool di thread, condiviso su tutti i back-end. L'unica eccezione è quando il codice del fornitore potrebbe caricare un'altra copia di libbinder per parlare con /dev/vndbinder . Poiché si trova su un nodo raccoglitore separato, il threadpool 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();