Back-ends de AIDL

Um back-end AIDL é um destino para a geração de códigos stub. Ao usar arquivos AIDL, você sempre use-as em uma linguagem específica com um tempo de execução específico. Dependendo no contexto, use diferentes back-ends da AIDL.

Na tabela abaixo, a estabilidade da superfície da API refere-se à capacidade para compilar o código nessa superfície de API de forma que ele possa ser entregues independentemente do binário system.img libbinder.so.

A AIDL tem os seguintes back-ends:

Back-end Idioma Plataforma da API Sistemas de build
Java Java SDK/SystemApi (estável*) todas
NDK C++ libbinder_ndk (estável*) interface_aidl
CPP C++ libbinder (instável) todas
Rust Rust libbinder_rs (estável*) interface_aidl
  • Essas superfícies de API são estáveis, mas muitas das APIs, como as de serviço do Google Cloud, são reservados para uso interno da plataforma e não estão disponíveis apps. Para mais informações sobre como usar a AIDL em apps, consulte documentação do desenvolvedor.
  • O back-end do Rust foi introduzido no Android 12. as O back-end do NDK está disponível a partir do Android 10.
  • A caixa do Rust é criada com base na libbinder_ndk, o que permite que ela seja estável e portátil. Os APEXs usam a caixa de binder da mesma forma que qualquer um outro no lado do sistema. A parte do Rust é agrupada em um APEX e enviada dentro dele. Depende de libbinder_ndk.so da partição do sistema.

Sistemas de build

Dependendo do back-end, há duas maneiras de compilar a AIDL em stubs o código-fonte. Para saber mais sobre os sistemas de build, consulte a Referência do módulo Soong.

Sistema de build principal

Em qualquer módulo Android.bp cc_ ou java_ (ou nos equivalentes Android.mk), Os arquivos .aidl podem ser especificados como arquivos de origem. Nesse caso, o código Java/CPP são usados back-ends da AIDL (não o back-end do NDK), e as classes para usar arquivos AIDL correspondentes são automaticamente adicionados ao módulo. Opções como local_include_dirs, que informa ao sistema de build o caminho raiz para Os arquivos AIDL nesse módulo podem ser especificados nesses módulos em uma aidl:. grupo. O back-end do Rust serve apenas para uso com o Rust. rust_ módulos estão são processados de maneira diferente, já que os arquivos AIDL não são especificados como arquivos de origem. Em vez disso, o módulo aidl_interface produz um rustlib chamado <aidl_interface name>-rust, que pode ser vinculado. Para mais detalhes, consulte o exemplo de AIDL do Rust.

interface_aidl

Os tipos usados com esse sistema de build precisam ser estruturados. Para ser estruturado, As parcelables precisam conter campos diretamente e não ser declarações de tipos definidos diretamente nos idiomas de destino. Para saber como a AIDL estruturada se encaixa AIDL estável, consulte AIDL estruturada versus estável.

Tipos

Considere o compilador aidl como uma implementação de referência para tipos. Ao criar uma interface, invoque aidl --lang=<backend> ... para ver a do arquivo de interface resultante. Ao usar o módulo aidl_interface, é possível conferir a saída em out/soong/.intermediates/<path to module>/.

Tipo de Java/AIDL Tipo de C++ Tipo de NDK Tipo de ferrugem
booleano booleano booleano booleano
byte int8_t int8_t i8
char char16_t char16_t U16
int int32_t int32_t i32
long int64_t int64_t i64
float float float f32
double double double f64
String android::String16 std::string String
android.os.Parcelable android::Parcelable N/A N/A
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::vetor<T> std::vetor<T> In: &[T]
Saída: Vec<T>
byte[] std::vetor<uint8_t> std::vector<int8_t>1 In: &[u8]
Saída: Vec<u8>
Lista<T> std::vector<T>2 std::vector<T>3 In: &[T]4
Saída: Vec<T>
FileDescriptor android::base::unique_fd N/A binder::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
tipo de interface (T) android::sp<T> std::shared_ptr<T>7 binder::Forte
Tipo parcelable (T) T T T
tipo de união (T)5 T T T
V[N] 6 std::matriz<T, N> std::matriz<T, N> [T; N]

1. No Android 12 ou versões mais recentes, as matrizes de bytes usam uint8_t em vez de int8_t por motivos de compatibilidade. .

2. O back-end em C++ é compatível com List<T>, em que T é um de String. IBinder, ParcelFileDescriptor ou parcelable. No Android 13 ou mais recente, T pode ser qualquer tipo não primitivo incluindo tipos de interface, exceto matrizes. O AOSP recomenda que você use tipos de matriz como T[], já que elas funcionam em todos os back-ends.

3. O back-end do NDK é compatível com List<T>, em que T é um de String. ParcelFileDescriptor ou parcelable. No Android 13 ou superior, T pode ser qualquer tipo não primitivo, exceto matrizes.

4. Os tipos são transmitidos de forma diferente para códigos Rust, dependendo se eles são uma entrada (um argumento) ou uma saída (um valor retornado).

5. Tipos de união são compatíveis com o Android 12 e mais alto. .

6. No Android 13 ou versões mais recentes, matrizes de tamanho fixo são suporte. As matrizes de tamanho fixo podem ter várias dimensões (por exemplo, int[3][4]). No back-end Java, as matrizes de tamanho fixo são representadas como tipos de matriz.

7. Para instanciar um objeto SharedRefBase de vinculação, use SharedRefBase::make\<My\>(... args ...). Essa função cria uma std::shared_ptr\<T\> objeto que também é gerenciado internamente, caso o binder seja de propriedade de outra de desenvolvimento de software. Criar o objeto de outras maneiras causa propriedade dupla. .

Direção (entrada/saída/saída)

Ao especificar os tipos de argumentos para as funções, você pode especificar como in, out ou inout. Isso controla em que direção as informações são passados para uma chamada IPC. in é a direção padrão e indica que os dados estão transmitido do autor da chamada para o recebedor da chamada. out significa que os dados são transmitidos da recebedor da chamada. inout é a combinação dos dois. No entanto, A equipe do Android recomenda evitar o uso do especificador de argumentos inout. Se você usar inout com uma interface com controle de versão e um recebedor da chamada mais antigo, o Os campos adicionais presentes apenas no autor da chamada são redefinidos para o padrão e a distribuição dos valores dos dados. Com relação ao Rust, um tipo inout normal recebe &mut Vec<T>. um tipo inout de lista recebe &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

Com o back-end de CPP, é possível escolher se as strings são utf-8 ou utf-16. Declarar strings como @utf8InCpp String na AIDL para convertê-las automaticamente em utf-8. Os back-ends do NDK e do Rust sempre usam strings utf-8. Para mais informações sobre a anotação utf8InCpp, consulte Anotações na AIDL.

Nulidade

É possível anotar tipos que podem ser nulos no back-end do Java com @nullable. para expor valores nulos aos back-ends de CPP e NDK. No back-end do Rust, Os tipos @nullable são expostos como Option<T>. Os servidores nativos rejeitam valores nulos por padrão. As únicas exceções são os tipos interface e IBinder, que sempre pode ser nulo para leituras do NDK e gravações do CPP/NDK. Para mais informações sobre a anotação nullable, consulte Anotações na AIDL.

Parcelables personalizados

Um parcelável personalizado é um parcelable implementado manualmente em um destino. back-end. Use parcelables personalizados somente quando você estiver tentando adicionar suporte a outros idiomas para um parcelable personalizado existente, que não pode ser alterado.

Para declarar um parcelable personalizado para que a AIDL saiba sobre ele, a AIDL A declaração parcelable tem esta aparência:

    package my.pack.age;
    parcelable Foo;

Por padrão, isso declara um parcelable Java em que my.pack.age.Foo é um Java. que implementa a interface Parcelable.

Para uma declaração de um back-end de CPP personalizado e parcelable na AIDL, use cpp_header:

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

A implementação do C++ no my/pack/age/Foo.h vai ficar assim:

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

Para uma declaração de um NDK personalizado em AIDL, use ndk_header:

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

A implementação do NDK em android/pack/age/Foo.h vai ficar assim:

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

No Android 15 (AOSP experimental), para declaração de um Rust personalizado. Parcelable na AIDL, use rust_type:

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

A implementação do Rust no rust_crate/src/lib.rs vai ficar assim:

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

Você poderá usar esse parcelable como um tipo em arquivos AIDL, mas não será gerados pela AIDL. Fornecer os operadores < e == para o back-end de CPP/NDK parcelables personalizados para usar em union.

Valores padrão

Parcelables estruturados podem declarar valores padrão por campo para primitivos. Strings e matrizes desses tipos.

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

No back-end Java, quando os valores padrão estão ausentes, os campos são inicializados como valores zero para tipos primitivos e null para tipos não primitivos.

Em outros back-ends, os campos são inicializados com valores inicializados padrão quando valores padrão não estão definidos. Por exemplo, no back-end C++, os campos String são inicializados como uma string vazia e os campos List<T> são inicializados como uma vector<T> vazio. Os campos @nullable são inicializados como campos de valor nulo.

Tratamento de erros

O SO Android fornece tipos de erros integrados para que os serviços usem ao gerar relatórios erros. Elas são usadas por binder e podem ser usadas por qualquer serviço que implemente uma interface binder. Seu uso está bem documentado na definição da AIDL e não exigem status ou tipo de retorno definido pelo usuário.

Parâmetros de saída com erros

Quando uma função AIDL relata um erro, ela pode não inicializar ou e modificar os parâmetros de saída. Especificamente, os parâmetros de saída podem ser modificados se o ocorre durante a separação em vez de acontecer durante o processamento da transação em si. Em geral, ao receber um erro de uma AIDL, todos os parâmetros inout e out, bem como o valor de retorno (que atua como um parâmetro out em alguns back-ends) deve ser considerado como estando em um estado indefinido.

Quais valores de erro usar

Muitos dos valores de erro integrados podem ser usados em qualquer interface AIDL, mas alguns são tratados de maneira especial. Por exemplo, EX_UNSUPPORTED_OPERATION e É possível usar EX_ILLEGAL_ARGUMENT quando eles descrevem a condição de erro, mas EX_TRANSACTION_FAILED não deve ser usado porque é tratado de forma especial pelo do Google Cloud. Verifique as definições específicas do back-end para saber mais informações sobre esses valores integrados.

Se a interface AIDL exigir outros valores de erro que não são cobertos pela os tipos de erro integrados, eles poderão usar a função especial que permite a inclusão de um valor de erro específico do serviço que está definidos pelo usuário. Esses erros específicos de serviços são normalmente definidos no interface AIDL como uma enum com suporte de const int ou int e não é analisada pela fichário.

Em Java, os erros são mapeados para exceções, como android.os.RemoteException. Para exceções específicas de serviços, o Java usa android.os.ServiceSpecificException além do erro definido pelo usuário.

O código nativo no Android não usa exceções. O back-end do CPP usa android::binder::Status: O back-end do NDK usa ndk::ScopedAStatus. Todas gerado pela AIDL retorna um desses valores, representando o status da . O back-end do Rust usa os mesmos valores de código de exceção que o NDK, mas os converter em erros nativos do Rust (StatusCode, ExceptionCode) antes entregá-los ao usuário. Para erros específicos de serviços, o valor Status ou ScopedAStatus usam EX_SERVICE_SPECIFIC com o definido pelo usuário.

Os tipos de erros integrados podem ser encontrados nos seguintes arquivos:

Back-end Definição
Java android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
Rust android/binder_status.h

Usar vários back-ends

Estas instruções são específicas para o código da plataforma Android. Esses exemplos usam uma tipo definido, my.package.IFoo. Para instruções sobre como usar o back-end do Rust, confira o exemplo da AIDL do Rust em Padrões do Rust no Android página.

Tipos de importação

Independentemente de o tipo definido ser uma interface, parcelable ou union, é possível importar em Java:

import my.package.IFoo;

Ou no back-end de CPP:

#include <my/package/IFoo.h>

Ou no back-end do NDK (observe o namespace extra aidl):

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

Ou no back-end do Rust:

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

Embora seja possível importar um tipo aninhado em Java, nos back-ends de CPP/NDK é preciso inclua o cabeçalho para seu tipo de raiz. Por exemplo, ao importar um tipo aninhado Bar definido em my/package/IFoo.aidl (IFoo é o tipo raiz do é preciso incluir <my/package/IFoo.h> para o back-end de CPP (ou <aidl/my/package/IFoo.h> para o back-end do NDK).

Implementar serviços

Para implementar um serviço, você precisa herdar da classe de stub nativa. Esta turma lê comandos do driver de vinculação e executa os métodos implementar. Imagine que você tenha um arquivo AIDL como este:

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

No Java, você precisa estender esta classe:

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

No back-end do CPP:

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

No back-end do NDK, observe o namespace extra aidl:

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

No back-end do 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(())
        }
    }

Ou com o Rust assíncrono:

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

Registrar e receber serviços

Os serviços na plataforma Android geralmente são registrados no servicemanager de desenvolvimento de software. Além das APIs abaixo, algumas verificam a serviço (o que significa que elas retornam imediatamente se o serviço não estiver disponível). Verifique a interface servicemanager correspondente para ver os detalhes exatos. Esses operações só podem ser feitas ao compilar com a plataforma Android.

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

No back-end do 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"));

No back-end do NDK, observe o namespace extra aidl:

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

No back-end do 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()
}

No back-end assíncrono do Rust, com um ambiente de execução de linha de execução única:

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
}

Uma diferença importante em relação às outras opções é que não chamamos join_thread_pool ao usar o Rust assíncrono e um ambiente de execução com linha de execução única. Isso é porque é necessário fornecer ao Tokio uma linha de execução em que ele possa executar tarefas geradas. Em neste exemplo, a linha de execução principal terá esse propósito. Todas as tarefas geradas usando tokio::spawn será executado na linha de execução principal.

No back-end assíncrono do Rust, com um ambiente de execução de várias linhas de execução:

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

Com o tempo de execução multithread Tokio, as tarefas geradas não são executadas na principal fio Portanto, faz mais sentido chamar join_thread_pool na instância principal para que a linha de execução principal não fique apenas ociosa. Você deve encapsular a chamada block_in_place para sair do contexto assíncrono.

Você pode solicitar uma notificação para quando um serviço que hospeda um binder é desativado. Isso pode ajudar a evitar o vazamento de proxies de callback ou auxiliar na recuperação de erros. Faça essas chamadas em objetos de proxy de vinculação.

  • Em Java, use android.os.IBinder::linkToDeath.
  • No back-end do CPP, use android::IBinder::linkToDeath.
  • No back-end do NDK, use AIBinder_linkToDeath.
  • No back-end do Rust, crie um objeto DeathRecipient e chame my_binder.link_to_death(&mut my_death_recipient). Observe que, como o DeathRecipient é proprietário do callback. Você precisa manter esse objeto ativo pelo tempo que de receber notificações.

Informações do autor da chamada

Ao receber uma chamada de vinculação do kernel, as informações do autor da chamada ficam disponíveis no várias APIs. O PID (ou ID de processo) se refere ao ID de processo Linux do que está enviando uma transação. O UID (ou User-ID) se refere à ID do usuário do Linux. Ao receber uma chamada unidirecional, o PID da chamada é 0. Quando fora de um contexto de transação de binder, essas funções retornam o PID e o UID. do processo atual.

No back-end do Java:

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

No back-end do CPP:

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

No back-end do NDK:

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

No back-end do Rust, ao implementar a interface, especifique o seguinte: (em vez de permitir a configuração padrão):

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

Relatórios de bugs e API de depuração para serviços

Quando os relatórios de bugs são gerados (por exemplo, com adb bugreport), eles coletam informações de todo o sistema para ajudar na depuração de vários problemas. Para serviços AIDL, os relatórios de bugs usam o dumpsys binário em todos os serviços registrados no gerente de serviço para despejarem as informações no relatório do bug. Também é possível usar dumpsys na linha de comando para receber informações de um serviço com dumpsys SERVICE [ARGS]. Nos back-ends C++ e Java, pode controlar a ordem em que os serviços são despejados usando argumentos adicionais. para addService. Também é possível usar dumpsys --pid SERVICE para receber o PID de um durante a depuração.

Para adicionar uma saída personalizada ao serviço, modifique dump em seu objeto de servidor como se estivesse implementando qualquer outro método IPC definidos em um arquivo AIDL. Ao fazer isso, você deve restringir o despejo ao aplicativo a permissão android.permission.DUMP ou restringir o despejo a UIDs específicos.

No back-end do Java:

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

No back-end do CPP:

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

No back-end do NDK:

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

No back-end do Rust, ao implementar a interface, especifique o seguinte: (em vez de permitir a configuração padrão):

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

Acessar dinamicamente o descritor da interface

O descritor de interface identifica o tipo de uma interface. Isso é útil ao depurar ou ter um binder desconhecido.

Em Java, você pode conseguir o descritor de interface com um código como:

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

No back-end do CPP:

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

Os back-ends do NDK e do Rust não oferecem suporte a esse recurso.

Receber estaticamente o descritor da interface

Às vezes, como ao registrar os serviços do @VintfStability, você precisa saber o que é o descritor de interface estaticamente. No Java, é possível receber descritor adicionando um código como:

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

No back-end do CPP:

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

No back-end do NDK, observe o namespace extra aidl:

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

No back-end do Rust:

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

Intervalo de tipo enumerado

Em back-ends nativos, você pode iterar nos valores possíveis que um tipo enumerado pode usar. Devido a considerações de tamanho de código, isso não é compatível com Java.

Para um tipo enumerado MyEnum definido na AIDL, a iteração é fornecida da seguinte maneira:

No back-end do CPP:

    ::android::enum_range<MyEnum>()

No back-end do NDK:

   ::ndk::enum_range<MyEnum>()

No back-end do Rust:

    MyEnum::enum_values()

Gerenciamento de linhas de execução

Cada instância de libbinder em um processo mantém um pool de linhas de execução. Para a maioria de uso geral, precisa ser exatamente um conjunto de linhas de execução, compartilhado entre todos os back-ends. A única exceção é quando o código do fornecedor pode carregar outra cópia de libbinder para falar com /dev/vndbinder. Como está em um nó de vinculação separado, o o pool de conversas não é compartilhado.

Para o back-end em Java, o pool de linhas de execução só pode aumentar de tamanho (já que ele já iniciado):

    BinderInternal.setMaxThreads(<new larger value>);

Para o back-end de CPP, as seguintes operações estão disponíveis:

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

Da mesma forma, no back-end do NDK:

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

No back-end do Rust:

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

Com o back-end assíncrono do Rust, você precisa de dois conjuntos de linhas de execução: binder e Tokio. Isso significa que os apps que usam o Rust assíncrono precisam de considerações especiais, especialmente quando se trata do uso de join_thread_pool. Consulte a seção sobre como registrar serviços para mais informações.

Nomes reservados

C++, Java e Rust reservam alguns nomes como palavras-chave ou para linguagens usar. Embora a AIDL não aplique restrições com base em regras de linguagem, usar nomes de campo ou tipo que correspondam a um nome reservado podem resultar em uma compilação em C++ ou Java. No Rust, o campo ou tipo é renomeado usando o "identificador bruto" sintaxe, que pode ser acessada usando o prefixo r#.

Evite o uso de nomes reservados nas definições de AIDL sempre que possível para evitar vinculações ergonômicas ou falha total de compilação.

Se você já tiver nomes reservados nas definições de AIDL, poderá renomear campos enquanto permanecem compatíveis com o protocolo; talvez seja necessário atualizar para continuar criando, mas todos os programas já criados continuarão interoperem.

Nomes que devem ser evitados: