O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Back-ends AIDL

Um back-end AIDL é um destino para geração de código stub. Ao usar arquivos AIDL, você sempre os usa em um idioma específico com um tempo de execução específico. Dependendo do contexto, você deve usar diferentes back-ends AIDL.

AIDL tem os seguintes back-ends:

Processo interno Linguagem superfície da API Construir sistemas
Java Java SDK/SystemApi (estável*) todos
NDK C++ libbinder_ndk (estável*) aidl_interface
CPP C++ libidinoso (instável) todos
Ferrugem Ferrugem libbinder_rs (instável) aidl_interface
  • Essas superfícies de API são estáveis, mas muitas das APIs, como as de gerenciamento de serviços, são reservadas para uso interno da plataforma e não estão disponíveis para aplicativos. Para obter mais informações sobre como usar AIDL em aplicativos, consulte a documentação do desenvolvedor .
  • O back-end Rust foi introduzido no Android 12; o back-end do NDK está disponível a partir do Android 10.
  • A caixa Rust é construída sobre libbinder_ndk . Os APEXs usam a caixa do fichário da mesma forma que qualquer outra pessoa no lado do sistema. A parte Rust é agrupada em um APEX e enviada dentro dele. Depende do libbinder_ndk.so na partição do sistema.

Construir sistemas

Dependendo do back-end, há duas maneiras de compilar AIDL em código stub. Para obter mais detalhes sobre os sistemas de compilação, consulte a referência do módulo Soong .

Sistema de compilação principal

Em qualquer módulo Android.bp cc_ ou java_ (ou em seus equivalentes Android.mk ), os arquivos .aidl podem ser especificados como arquivos de origem. Nesse caso, os back-ends Java/CPP do AIDL são usados ​​(não o back-end NDK) e as classes para usar os arquivos AIDL correspondentes são adicionadas ao módulo automaticamente. Opções como local_include_dirs , que informa ao sistema de compilação o caminho raiz para os arquivos AIDL nesse módulo podem ser especificadas nesses módulos em um grupo aidl: Observe que o back-end Rust é apenas para uso com Rust. Os módulos rust_ são tratados de maneira diferente, pois 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 obter mais detalhes, consulte o exemplo Rust AIDL .

aidl_interface

Consulte AIDL estável . Os tipos usados ​​com este sistema de compilação devem ser estruturados; ou seja, expresso em AIDL diretamente. Isso significa que os parcelables personalizados não podem ser usados.

tipos

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

Tipo Java/AIDL Tipo C++ Tipo NDK Tipo de ferrugem
boleano bool bool bool
byte int8_t int8_t i8
Caracteres char16_t char16_t u16
int int32_t int32_t i32
grande int64_t int64_t i64
flutuador flutuador flutuador f32
Duplo Duplo Duplo f64
Corda android::String16 padrão::string Corda
android.os.Parcelable android::Parcelável N / D N / D
IBinder android::IBinder ndk::SpAIBinder fichário::SpIBinder
T[] padrão::vetor<T> padrão::vetor<T> Em: &[T]
Fora: Vec<T>
byte[] padrão::vetor<uint8_t> std::vector<int8_t> 1 Em: &[u8]
Fora: Vec<u8>
Lista<T> std::vetor<T> 2 std::vetor<T> 3 Em: &[T] 4
Fora: Vec<T>
FileDescriptor android::base::unique_fd N / D fichário::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor fichário::parcel::ParcelFileDescriptor
tipo de interface (T) android::sp<T> std::shared_ptr<T> aglutinante::Forte
tipo parcelavel (T) T T T
tipo de união (T) 5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T; N]

1. No Android 12 ou superior, as matrizes de bytes usam uint8_t em vez de int8_t por motivos de compatibilidade.

2. O back-end C++ oferece suporte a List<T> onde T é um de String , IBinder , ParcelFileDescriptor ou parcelable. No Android 13 ou superior, T pode ser qualquer tipo não primitivo (incluindo tipos de interface), exceto arrays. O AOSP recomenda que você use tipos de matriz como T[] , pois eles funcionam em todos os back-ends.

3. O back-end do NDK oferece suporte a List<T> em que T é String , ParcelFileDescriptor ou parcelable. No Android 13 ou superior, T pode ser qualquer tipo não primitivo, exceto arrays.

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

5. Os tipos de união são suportados no Android 12 e superior.

6. No Android 13 ou superior, as matrizes de tamanho fixo são suportadas. Arrays de tamanho fixo podem ter várias dimensões (por exemplo int[3][4] ). No back-end Java, os arrays de tamanho fixo são representados como tipos de array.

Direcionalidade (entrada/saída/entrada)

Ao especificar os tipos de argumentos para funções, você pode especificá-los como in , out ou inout . Isso controla em qual direção as informações são passadas para uma chamada IPC. in é a direção padrão e indica que os dados são passados ​​do chamador para o chamado. out significa que os dados são passados ​​do receptor para o chamador. inout é a combinação de ambos. No entanto, a equipe do Android recomenda que você evite usar o especificador de argumento inout . Se você usar inout com uma interface com versão e um receptor mais antigo, os campos adicionais que estão presentes apenas no chamador serão redefinidos para seus valores padrão. Com relação a Rust, um tipo inout normal recebe &mut Vec<T> , e um tipo inout list recebe &mut Vec<T> .

UTF8/UTF16

Com o back-end CPP, você pode escolher se as strings são utf-8 ou utf-16. Declare strings como @utf8InCpp String em AIDL para convertê-las automaticamente em utf-8. Os back-ends NDK e Rust sempre usam strings utf-8. Para obter mais informações sobre a anotação utf8InCpp , consulte Anotações em AIDL .

Nulidade

Você pode anotar tipos que podem ser nulos no back-end Java com @nullable para expor valores nulos aos back-ends CPP e NDK. No back-end do Rust, esses 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 de interface e IBinder , que sempre podem ser nulos para leituras de NDK e gravações de CPP/NDK. Para obter mais informações sobre a anotação nullable , consulte Anotações em AIDL .

Parceláveis ​​personalizados

Nos back-ends C++ e Java no sistema de compilação principal, você pode declarar um parcelable que é implementado manualmente em um back-end de destino (em C++ ou em Java).

    package my.package;
    parcelable Foo;

ou com declaração de cabeçalho C++:

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

Então você pode usar este parcelable como um tipo em arquivos AIDL, mas não será gerado pelo AIDL.

Rust não suporta parcelables personalizados.

Valores padrão

Os parcelables estruturados podem declarar valores padrão por campo para primitivos, String s e arrays desses tipos.

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

No backend 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 os valores padrão não sã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 um vector<T> vazio. Os campos @nullable são inicializados como campos de valor nulo.

Manipulação de erros

O sistema operacional Android fornece tipos de erro integrados para os serviços usarem ao relatar erros. Eles são usados ​​pelo fichário e podem ser usados ​​por qualquer serviço que implemente uma interface de fichário. Seu uso é bem documentado na definição AIDL e não requer nenhum status ou tipo de retorno definido pelo usuário.

Parâmetros de saída com erros

Quando uma função AIDL relata um erro, a função pode não inicializar ou modificar os parâmetros de saída. Especificamente, os parâmetros de saída podem ser modificados se o erro ocorrer durante o desempacotamento, em vez de ocorrer durante o processamento da própria transação. Em geral, ao obter um erro de uma função inout , todos out , bem como o valor de retorno (que age como um parâmetro de out em alguns back-ends) devem ser considerados 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 EX_ILLEGAL_ARGUMENT podem ser usados ​​quando descrevem a condição de erro, mas EX_TRANSACTION_FAILED não deve ser usado porque é tratado de forma especial pela infraestrutura subjacente. Verifique as definições específicas de back-end para obter mais informações sobre esses valores integrados.

Se a interface AIDL exigir valores de erro adicionais que não sejam cobertos pelos tipos de erro integrados, eles poderão usar o erro interno específico do serviço especial que permite a inclusão de um valor de erro específico do serviço definido pelo usuário . Esses erros específicos de serviço são geralmente definidos na interface AIDL como um const int ou uma enum baseada em int e não são analisados ​​pelo fichário.

Em Java, os erros são mapeados para exceções, como android.os.RemoteException . Para exceções específicas do serviço, o Java usa android.os.ServiceSpecificException junto com o 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 . Todo método gerado pelo AIDL retorna um deles, representando o status do método. O back-end Rust usa os mesmos valores de código de exceção que o NDK, mas os converte em erros Rust nativos ( StatusCode , ExceptionCode ) antes de entregá-los ao usuário. Para erros específicos do serviço, o Status ou ScopedAStatus usa EX_SERVICE_SPECIFIC junto com o erro definido pelo usuário.

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

Processo interno Definição
Java android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
Ferrugem android/binder_status.h

Usando vários back-ends

Estas instruções são específicas para o código da plataforma Android. Esses exemplos usam um tipo definido, my.package.IFoo . Para obter instruções sobre como usar o back-end Rust, consulte o exemplo Rust AIDL na página Android Rust Patterns .

Tipos de importação

Se o tipo definido é uma interface, parcelable ou union, você pode importá-lo em Java:

import my.package.IFoo;

Ou no back-end do CPP:

#include <my/package/IFoo.h>

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

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

Ou no back-end do Rust:

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

Embora você possa importar um tipo aninhado em Java, nos back-ends CPP/NDK você deve incluir o cabeçalho para seu tipo raiz. Por exemplo, ao importar um tipo aninhado Bar definido em my/package/IFoo.aidl ( IFoo é o tipo raiz do arquivo) você deve incluir <my/package/IFoo.h> para o backend CPP (ou <aidl/my/package/IFoo.h> para o back-end do NDK).

Implementação de serviços

Para implementar um serviço, você deve herdar da classe stub nativa. Essa classe lê comandos do driver do fichário e executa os métodos que você implementa. Imagine que você tenha um arquivo AIDL como este:

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

Em Java, você deve estender a partir desta 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 aidl extra):

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

Registrando e obtendo serviços

Os serviços na plataforma Android geralmente são registrados com o processo servicemanager . Além das APIs abaixo, algumas APIs verificam o serviço (o que significa que retornam imediatamente se o serviço não estiver disponível). Verifique a interface do servicemanager de serviço correspondente para obter detalhes exatos. Essas operações só podem ser feitas ao compilar na 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 aidl extra):

    #include <android/binder_manager.h>
    // registering
    status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // return if service is started now
    myService = IFoo::fromBinder(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(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()
}

Você pode solicitar uma notificação para quando um serviço que hospeda um fichário for encerrado. Isso pode ajudar a evitar o vazamento de proxies de retorno de chamada ou auxiliar na recuperação de erros. Faça essas chamadas em objetos de proxy do fichário.

  • 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 DeathRecipient possui o retorno de chamada, você deve manter esse objeto ativo enquanto desejar receber notificações.

Informações do chamador

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

No back-end 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 que seja padrão):

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

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

Quando bugreports são executados (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 os dumpsys binários em todos os serviços registrados no gerenciador de serviços para descarregar suas informações no relatório de bugs. Você também pode usar dumpsys na linha de comando para obter informações de um serviço com dumpsys SERVICE [ARGS] . Nos back-ends C++ e Java, você pode controlar a ordem na qual os serviços são despejados usando argumentos adicionais para addService . Você também pode usar dumpsys --pid SERVICE para obter o PID de um serviço durante a depuração.

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

No back-end 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 que seja padrão):

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

Obtendo dinamicamente o descritor de interface

O descritor de interface identifica o tipo de uma interface. Isso é útil ao depurar ou quando você tem um fichário desconhecido.

Em Java, você pode obter o descritor de interface com códigos 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 NDK e Rust não oferecem suporte a essa funcionalidade.

Obtendo estaticamente o descritor de interface

Às vezes (como ao registrar os serviços @VintfStability ), você precisa saber qual é o descritor de interface estaticamente. Em Java, você pode obter o descritor adicionando 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 aidl extra):

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

No back-end do Rust:

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

Intervalo de enumeração

Em back-ends nativos, você pode iterar sobre os possíveis valores que uma enumeração pode assumir. Devido a considerações de tamanho de código, isso não é suportado em Java atualmente.

Para um enum MyEnum definido em AIDL, a iteração é fornecida da seguinte forma.

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

Gerenciamento de tópicos

Cada instância de libbinder em um processo mantém um threadpool. Para a maioria dos casos de uso, isso deve ser exatamente um conjunto de threads, compartilhado por todos os back-ends. A única exceção a isso é quando o código do fornecedor pode carregar outra cópia do libbinder para falar com /dev/vndbinder . Como isso está em um nó de fichário separado, o threadpool não é compartilhado.

Para o back-end Java, o threadpool só pode aumentar de tamanho (já que já foi iniciado):

    BinderInternal.setMaxThreads(<new larger value>);

Para o back-end 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();

Nomes reservados

C++, Java e Rust reservam alguns nomes como palavras-chave ou para uso específico da linguagem. Embora o AIDL não imponha restrições baseadas em regras de linguagem, usar nomes de campo ou tipo que correspondam a um nome reservado pode resultar em uma falha de compilação para C++ ou Java. Para Rust, o campo ou tipo é renomeado usando a sintaxe "identificador bruto", acessível usando o prefixo r# .

Recomendamos que você evite usar nomes reservados em suas definições AIDL sempre que possível para evitar ligações não ergonômicas ou falhas de compilação definitivas.

Se você já tiver nomes reservados em suas definições de AIDL, poderá renomear os campos com segurança enquanto permanece compatível com o protocolo; pode ser necessário atualizar seu código para continuar compilando, mas todos os programas já compilados continuarão a interoperar.

Nomes a evitar: * Palavras- chave C++ * Palavras - chave Java * Palavras -chave Rust

,

Um back-end AIDL é um destino para geração de código stub. Ao usar arquivos AIDL, você sempre os usa em um idioma específico com um tempo de execução específico. Dependendo do contexto, você deve usar diferentes back-ends AIDL.

AIDL tem os seguintes back-ends:

Processo interno Linguagem superfície da API Construir sistemas
Java Java SDK/SystemApi (estável*) todos
NDK C++ libbinder_ndk (estável*) aidl_interface
CPP C++ libidinoso (instável) todos
Ferrugem Ferrugem libbinder_rs (instável) aidl_interface
  • Essas superfícies de API são estáveis, mas muitas das APIs, como as de gerenciamento de serviços, são reservadas para uso interno da plataforma e não estão disponíveis para aplicativos. Para obter mais informações sobre como usar AIDL em aplicativos, consulte a documentação do desenvolvedor .
  • O back-end Rust foi introduzido no Android 12; o back-end do NDK está disponível a partir do Android 10.
  • A caixa Rust é construída sobre libbinder_ndk . Os APEXs usam a caixa do fichário da mesma forma que qualquer outra pessoa no lado do sistema. A parte Rust é agrupada em um APEX e enviada dentro dele. Depende do libbinder_ndk.so na partição do sistema.

Construir sistemas

Dependendo do back-end, há duas maneiras de compilar AIDL em código stub. Para obter mais detalhes sobre os sistemas de compilação, consulte a referência do módulo Soong .

Sistema de compilação principal

Em qualquer módulo Android.bp cc_ ou java_ (ou em seus equivalentes Android.mk ), os arquivos .aidl podem ser especificados como arquivos de origem. Nesse caso, os back-ends Java/CPP do AIDL são usados ​​(não o back-end NDK) e as classes para usar os arquivos AIDL correspondentes são adicionadas ao módulo automaticamente. Opções como local_include_dirs , que informa ao sistema de compilação o caminho raiz para os arquivos AIDL nesse módulo podem ser especificadas nesses módulos em um grupo aidl: Observe que o back-end Rust é apenas para uso com Rust. Os módulos rust_ são tratados de maneira diferente, pois 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 obter mais detalhes, consulte o exemplo Rust AIDL .

aidl_interface

Consulte AIDL estável . Os tipos usados ​​com este sistema de compilação devem ser estruturados; ou seja, expresso em AIDL diretamente. Isso significa que os parcelables personalizados não podem ser usados.

tipos

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

Tipo Java/AIDL Tipo C++ Tipo NDK Tipo de ferrugem
boleano bool bool bool
byte int8_t int8_t i8
Caracteres char16_t char16_t u16
int int32_t int32_t i32
grande int64_t int64_t i64
flutuador flutuador flutuador f32
Duplo Duplo Duplo f64
Corda android::String16 padrão::string Corda
android.os.Parcelable android::Parcelável N / D N / D
IBinder android::IBinder ndk::SpAIBinder fichário::SpIBinder
T[] padrão::vetor<T> padrão::vetor<T> Em: &[T]
Fora: Vec<T>
byte[] padrão::vetor<uint8_t> std::vector<int8_t> 1 Em: &[u8]
Fora: Vec<u8>
Lista<T> std::vetor<T> 2 std::vetor<T> 3 Em: &[T] 4
Fora: Vec<T>
FileDescriptor android::base::unique_fd N / D fichário::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor fichário::parcel::ParcelFileDescriptor
tipo de interface (T) android::sp<T> std::shared_ptr<T> aglutinante::Forte
tipo parcelavel (T) T T T
tipo de união (T) 5 T T T
T[N] 6 std::array<T, N> std::array<T, N> [T; N]

1. No Android 12 ou superior, as matrizes de bytes usam uint8_t em vez de int8_t por motivos de compatibilidade.

2. O back-end C++ oferece suporte a List<T> onde T é um de String , IBinder , ParcelFileDescriptor ou parcelable. No Android 13 ou superior, T pode ser qualquer tipo não primitivo (incluindo tipos de interface), exceto arrays. O AOSP recomenda que você use tipos de matriz como T[] , pois eles funcionam em todos os back-ends.

3. O back-end do NDK oferece suporte a List<T> em que T é String , ParcelFileDescriptor ou parcelable. No Android 13 ou superior, T pode ser qualquer tipo não primitivo, exceto arrays.

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

5. Os tipos de união são suportados no Android 12 e superior.

6. No Android 13 ou superior, as matrizes de tamanho fixo são suportadas. Arrays de tamanho fixo podem ter várias dimensões (por exemplo int[3][4] ). No back-end Java, os arrays de tamanho fixo são representados como tipos de array.

Direcionalidade (entrada/saída/entrada)

Ao especificar os tipos de argumentos para funções, você pode especificá-los como in , out ou inout . Isso controla em qual direção as informações são passadas para uma chamada IPC. in é a direção padrão e indica que os dados são passados ​​do chamador para o chamado. out significa que os dados são passados ​​do receptor para o chamador. inout é a combinação de ambos. No entanto, a equipe do Android recomenda que você evite usar o especificador de argumento inout . Se você usar inout com uma interface com versão e um receptor mais antigo, os campos adicionais que estão presentes apenas no chamador serão redefinidos para seus valores padrão. Com relação a Rust, um tipo inout normal recebe &mut Vec<T> , e um tipo inout list recebe &mut Vec<T> .

UTF8/UTF16

Com o back-end CPP, você pode escolher se as strings são utf-8 ou utf-16. Declare strings como @utf8InCpp String em AIDL para convertê-las automaticamente em utf-8. Os back-ends NDK e Rust sempre usam strings utf-8. Para obter mais informações sobre a anotação utf8InCpp , consulte Anotações em AIDL .

Nulidade

Você pode anotar tipos que podem ser nulos no back-end Java com @nullable para expor valores nulos aos back-ends CPP e NDK. No back-end do Rust, esses 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 de interface e IBinder , que sempre podem ser nulos para leituras de NDK e gravações de CPP/NDK. Para obter mais informações sobre a anotação nullable , consulte Anotações em AIDL .

Parceláveis ​​personalizados

Nos back-ends C++ e Java no sistema de compilação principal, você pode declarar um parcelable que é implementado manualmente em um back-end de destino (em C++ ou em Java).

    package my.package;
    parcelable Foo;

ou com declaração de cabeçalho C++:

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

Então você pode usar este parcelable como um tipo em arquivos AIDL, mas não será gerado pelo AIDL.

Rust não suporta parcelables personalizados.

Valores padrão

Os parcelables estruturados podem declarar valores padrão por campo para primitivos, String s e arrays desses tipos.

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

No backend 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 os valores padrão não sã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 um vector<T> vazio. Os campos @nullable são inicializados como campos de valor nulo.

Manipulação de erros

O sistema operacional Android fornece tipos de erro integrados para os serviços usarem ao relatar erros. Eles são usados ​​pelo fichário e podem ser usados ​​por qualquer serviço que implemente uma interface de fichário. Seu uso é bem documentado na definição AIDL e não requer nenhum status ou tipo de retorno definido pelo usuário.

Parâmetros de saída com erros

Quando uma função AIDL relata um erro, a função pode não inicializar ou modificar os parâmetros de saída. Especificamente, os parâmetros de saída podem ser modificados se o erro ocorrer durante o desempacotamento, em vez de ocorrer durante o processamento da própria transação. Em geral, ao obter um erro de uma função inout , todos out , bem como o valor de retorno (que age como um parâmetro de out em alguns back-ends) devem ser considerados 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 EX_ILLEGAL_ARGUMENT podem ser usados ​​quando descrevem a condição de erro, mas EX_TRANSACTION_FAILED não deve ser usado porque é tratado de forma especial pela infraestrutura subjacente. Verifique as definições específicas de back-end para obter mais informações sobre esses valores integrados.

Se a interface AIDL exigir valores de erro adicionais que não sejam cobertos pelos tipos de erro integrados, eles poderão usar o erro interno específico do serviço especial que permite a inclusão de um valor de erro específico do serviço definido pelo usuário . Esses erros específicos de serviço são geralmente definidos na interface AIDL como um const int ou uma enum baseada em int e não são analisados ​​pelo fichário.

Em Java, os erros são mapeados para exceções, como android.os.RemoteException . Para exceções específicas do serviço, o Java usa android.os.ServiceSpecificException junto com o 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 . Todo método gerado pelo AIDL retorna um deles, representando o status do método. O back-end Rust usa os mesmos valores de código de exceção que o NDK, mas os converte em erros Rust nativos ( StatusCode , ExceptionCode ) antes de entregá-los ao usuário. Para erros específicos do serviço, o Status ou ScopedAStatus usa EX_SERVICE_SPECIFIC junto com o erro definido pelo usuário.

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

Processo interno Definição
Java android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
Ferrugem android/binder_status.h

Usando vários back-ends

Estas instruções são específicas para o código da plataforma Android. Esses exemplos usam um tipo definido, my.package.IFoo . Para obter instruções sobre como usar o back-end Rust, consulte o exemplo Rust AIDL na página Android Rust Patterns .

Tipos de importação

Se o tipo definido é uma interface, parcelable ou union, você pode importá-lo em Java:

import my.package.IFoo;

Ou no back-end do CPP:

#include <my/package/IFoo.h>

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

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

Ou no back-end do Rust:

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

Embora você possa importar um tipo aninhado em Java, nos back-ends CPP/NDK você deve incluir o cabeçalho para seu tipo raiz. Por exemplo, ao importar um tipo aninhado Bar definido em my/package/IFoo.aidl ( IFoo é o tipo raiz do arquivo) você deve incluir <my/package/IFoo.h> para o backend CPP (ou <aidl/my/package/IFoo.h> para o back-end do NDK).

Implementação de serviços

Para implementar um serviço, você deve herdar da classe stub nativa. Essa classe lê comandos do driver do fichário e executa os métodos que você implementa. Imagine que você tenha um arquivo AIDL como este:

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

Em Java, você deve estender a partir desta 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 aidl extra):

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

Registrando e obtendo serviços

Os serviços na plataforma Android geralmente são registrados com o processo servicemanager . Além das APIs abaixo, algumas APIs verificam o serviço (o que significa que retornam imediatamente se o serviço não estiver disponível). Verifique a interface do servicemanager de serviço correspondente para obter detalhes exatos. Essas operações só podem ser feitas ao compilar na 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 aidl extra):

    #include <android/binder_manager.h>
    // registering
    status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // return if service is started now
    myService = IFoo::fromBinder(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(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()
}

Você pode solicitar uma notificação para quando um serviço que hospeda um fichário for encerrado. Isso pode ajudar a evitar o vazamento de proxies de retorno de chamada ou auxiliar na recuperação de erros. Faça essas chamadas em objetos de proxy do fichário.

  • 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 DeathRecipient possui o retorno de chamada, você deve manter esse objeto ativo enquanto desejar receber notificações.

Informações do chamador

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

No back-end 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 que seja padrão):

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

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

Quando bugreports são executados (por exemplo, com adb bugreport ), eles coletam informações de todo o sistema para ajudar na depuração de vários problemas. For AIDL services, bugreports use the binary dumpsys on all services registered with the service manager to dump their information into the bugreport. You can also use dumpsys on the commandline to get information from a service with dumpsys SERVICE [ARGS] . In the C++ and Java backends, you can control the order in which services get dumped by using additional arguments to addService . You can also use dumpsys --pid SERVICE to get the PID of a service while debugging.

To add custom output to your service, you can override the dump method in your server object like you are implementing any other IPC method defined in an AIDL file. When doing this, you should restrict dumping to the app permission android.permission.DUMP or restrict dumping to specific UIDs.

In the Java backend:

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

In the CPP backend:

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

In the NDK backend:

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

In the Rust backend, when implementing the interface, specify the following (instead of allowing it to default):

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

Dynamically getting interface descriptor

The interface descriptor identifies the type of an interface. This is useful when debugging or when you have an unknown binder.

In Java, you can get the interface descriptor with code such as:

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

In the CPP backend:

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

The NDK and Rust backends don't support this functionality.

Statically getting interface descriptor

Sometimes (such as when registering @VintfStability services), you need to know what the interface descriptor is statically. In Java, you can get the descriptor by adding code such as:

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

In the CPP backend:

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

In the NDK backend (notice the extra aidl namespace):

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

In the Rust backend:

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

Enum Range

In native backends, you can iterate over the possible values an enum can take on. Due to code size considerations, this isn't supported in Java currently.

For an enum MyEnum defined in AIDL, iteration is provided as follows.

In the CPP backend:

    ::android::enum_range<MyEnum>()

In the NDK backend:

   ::ndk::enum_range<MyEnum>()

In the Rust backend:

    MyEnum::enum_range()

Thread management

Every instance of libbinder in a process maintains one threadpool. For most use cases, this should be exactly one threadpool, shared across all backends. The only exception to this is when vendor code might load another copy of libbinder to talk to /dev/vndbinder . Since this is on a separate binder node, the threadpool isn't shared.

For the Java backend, the threadpool can only increase in size (since it is already started):

    BinderInternal.setMaxThreads(<new larger value>);

For the CPP backend, the following operations are available:

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

Similarly, in the NDK backend:

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

In the Rust backend:

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

Reserved Names

C++, Java, and Rust reserve some names as keywords or for language-specific use. While AIDL doesn't enforce restrictions based on language rules, using field or type names that matching a reserved name might result in a compilation failure for C++ or Java. For Rust, the field or type is renamed using the "raw identifier" syntax, accessible using the r# prefix.

We recommend that you avoid using reserved names in your AIDL definitions where possible to avoid unergonomic bindings or outright compilation failure.

If you already have reserved names in your AIDL definitions, you can safely rename fields while remaining protocol compatible; you may need to update your code to continue building, but any already built programs will continue to interoperate.

Names to avoid: * C++ Keywords * Java Keywords * Rust Keywords