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

Backends AIDL

Um back-end AIDL é um alvo 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 back-ends AIDL diferentes.

AIDL tem os seguintes back-ends:

Processo interno Língua Superfície API Sistemas de construção
Java Java SDK / SystemApi (estável *) tudo
NDK C ++ libbinder_ndk (estável *) aidl_interface
CPP C ++ libbinder (instável) tudo
Ferrugem Ferrugem libbinder_rs (instável) aidl_interface
  • Essas superfícies de API são estáveis, mas muitas das APIs, como aquelas para gerenciamento de serviço, são reservadas para uso de plataforma interna e não estão disponíveis para aplicativos. Para 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 NDK está disponível a partir do Android 10.
  • A caixa Rust é construído em cima de libbinder_ndk . Os APEXs usam a caixa de encadernação da mesma maneira que qualquer outra pessoa no sistema. A porção Rust é agrupada em um APEX e enviada dentro dele. Depende do libbinder_ndk.so na partição do sistema.

Sistemas de construção

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

Sistema de construção central

Em qualquer cc_ ou java_ Android.bp módulo (ou em suas Android.mk equivalentes), .aidl arquivos podem ser especificados como arquivos de origem. Nesse caso, os back-ends Java / CPP de 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 conta o sistema de construção a caminho da raiz para AIDL arquivos nesse módulo pode ser especificado nestes módulos sob um aidl: grupo. Observe que o back-end do Rust deve ser usado apenas com o Rust. rust_ módulos são tratados de forma diferente em que os arquivos AIDL não são especificados como arquivos de origem. Em vez disso, o aidl_interface módulo produz uma rustlib chamado <aidl_interface name>-rust que pode ser ligado contra. Para mais detalhes, veja o exemplo Rust AIDL .

aidl_interface

Veja AIDL Estável . Os tipos usados ​​com este sistema de compilação devem ser estruturados; isto é, expresso em AIDL diretamente. Isso significa que pacotes personalizados não podem ser usados.

Tipos

Você pode considerar o aidl compilador como uma implementação de referência para os tipos. Quando você cria uma interface, chame aidl --lang=<backend> ... para ver o arquivo de interface resultante. Quando você usa o aidl_interface módulo, você pode ver 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
Fragmento android :: String16 std :: string Fragmento
android.os.Parcelable android :: Parcelable N / D N / D
IBinder android :: IBinder ndk :: SpAIBinder binder :: SpIBinder
T [] std :: vector <T> std :: vector <T> In: & T
Fora: Vec <T>
byte[] std :: vector <uint8_t> std :: vector <int8_t> 1 Em: & [u8]
Fora: Vec <u8>
Lista <T> std :: vector <T> 2 std :: vector <T> 3 In: & [T] 4
Fora: Vec <T>
FileDescriptor android :: base :: unique_fd N / D binder :: parcel :: ParcelFileDescriptor
ParcelFileDescriptor android :: os :: ParcelFileDescriptor ndk :: ScopedFileDescriptor binder :: parcel :: ParcelFileDescriptor
tipo de interface (T) android :: sp <T> std :: shared_ptr <T> aglutinante :: Forte
tipo parcelável (T) T T T
tipo de união (T) 5 T T T

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

2. backend O C ++ suporta List<T> , onde T é uma das String , IBinder , ParcelFileDescriptor ou parcelable. Em t Android (AOSP experimental) ou superior, T pode ser qualquer tipo não primitivo (incluindo tipos de interface), excepto matrizes. AOSP recomenda que você use tipos de matriz como T[] , uma vez que eles trabalham em todos os backends.

3. O NDK infra-estrutura suporta List<T> , onde T é uma das String , ParcelFileDescriptor ou parcelable. Em t Android (AOSP experimental) ou superior, T pode ser qualquer tipo não primitivo excepto matrizes.

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.

Direcionalidade (dentro / fora / dentro / fora)

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

UTF8 / UTF16

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

Nulidade

Você pode anotar tipos que pode ser nulo em Java com @nullable para expor valores nulos para C ++ e NDK. Em Rust estes @nullable tipos são expostos como Option<T> . Os servidores nativos rejeitam valores nulos por padrão. As únicas excepções a este são interface e IBinder tipos, que sempre pode ser nulo. Para mais informações sobre o nullable anotação, consulte Anotações em AIDL .

Pacotes Personalizados

Nos back-ends C ++ e Java no sistema de construçã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 parcelável como um tipo em arquivos AIDL, mas não será gerado por AIDL.

Rust não oferece suporte para pacotes personalizados.

Valores padrão

Parcelables estruturados pode declarar valores padrão per-campo para primitivos, String s, e matrizes desses tipos.

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

No backend Java quando os valores padrão estiver ausente, 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 padrão inicializados quando os valores padrão não são definidos. Por exemplo, no backend C ++, String campos são inicializados como uma string vazia e List<T> campos são inicializados como um vazio vector<T> . @nullable campos são inicializados como campos nulo de valor.

Manipulação de erros

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

Se a interface AIDL exigir valores de erro adicionais que não são cobertos pelos tipos de erro integrados, eles podem usar o erro integrado 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 . Estes erros de serviço específicos são tipicamente definidos na interface AIDL como um const int ou int -backed enum e não são analisados por ligante.

Em Java, erros mapear para exceções, como android.os.RemoteException . Para exceções específicas do serviço, Java usa android.os.ServiceSpecificException juntamente com o erro definido pelo usuário.

O código nativo no Android não usa exceções. Os usos de back-end CPP android::binder::Status . Os usos de back-end NDK ndk::ScopedAStatus . Todo método gerado por AIDL retorna um destes, representando o status do método. O backend Rust usa os mesmos valores de código de exceção como o NDK, mas converte-os em erros Rust nativas ( StatusCode , ExceptionCode ) antes de entregá-los para o usuário. Para erros específicos do serviço, o retornou Status ou ScopedAStatus usa EX_SERVICE_SPECIFIC juntamente com o erro definido pelo usuário.

Os tipos de erros 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

Essas instruções são específicas para o código da plataforma Android. Estes exemplos utilizam um tipo definido, my.package.IFoo . Para obter instruções sobre como usar o backend Rust, veja o exemplo Rust AIDL na Patterns Rust Android página.

Tipos de importação

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

    import my.package.IFoo;

Ou no backend CPP:

    #include <my/package/IFoo.h>

Ou no backend NDK (aviso extra aidl namespace):

    #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 de raiz. Por exemplo, quando a importação de um tipo aninhado Bar definido na my/package/IFoo.aidl ( IFoo é o tipo de raiz do arquivo) você deve incluir <my/package/IFoo.h> para o backend CPP (ou <aidl/my/package/IFoo.h> para o backend NDK).

Serviços de implementação

Para implementar um serviço, você deve herdar da classe stub nativa. Esta 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 backend CPP:

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

No backend NDK (aviso extra aidl namespace):

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

No backend 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

Serviços de plataforma Android são geralmente registrados com o servicemanager processo. 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 o correspondente servicemanager interface para detalhes exatos. Essas operações só podem ser feitas durante a compilação na plataforma Android.

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

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

No backend NDK (aviso extra aidl namespace):

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

No backend 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 o recebimento de uma notificação quando um serviço de hospedagem de um fichário morre. 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 de fichário.

  • Em Java, utilize android.os.IBinder::linkToDeath .
  • No backend CPP, o uso android::IBinder::linkToDeath .
  • No backend NDK, o uso AIBinder_linkToDeath .
  • No backend Rust, criar um DeathRecipient objeto, em seguida, chamar my_binder.link_to_death(&mut my_death_recipient) . Note-se que porque o DeathRecipient possui o retorno de chamada, você deve manter esse objeto vivo, enquanto você deseja receber notificações.

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

Quando os relatórios de bug executado (por exemplo, com adb bugreport ), eles coletam informações de todo o sistema de ajuda com a depuração de várias questões. Para serviços AIDL, relatos de erros usar os binários dumpsys em todos os serviços registrados com o gerente de serviço para despejar suas informações para o bugreport. Você também pode usar dumpsys na linha de comando para obter informações de um serviço com dumpsys SERVICE [ARGS] . Nos backends C ++ e Java, você pode controlar a ordem em que os serviços levar um fora, 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 para o seu serviço, você pode substituir o dump método no seu objeto de servidor, como você está implementando qualquer outro método IPC definido em um arquivo AIDL. Ao fazer isso, você deve restringir o despejo à permissão aplicativo android.permission.DUMP ou restringir despejo para UIDs específicos.

No back-end Java:

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

No back-end C ++:

    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 o padrão):

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

Obtendo dinamicamente o descritor da 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 da interface com códigos como:

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

No backend 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 descritor de interface estaticamente

Às vezes (como quando registrar @VintfStability serviços), você precisa saber o que o descritor de interface é estaticamente. Em Java, você pode obter o descritor adicionando códigos como:

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

No backend CPP:

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

No backend NDK (aviso extra aidl namespace):

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

No backend do Rust:

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

Intervalo Enum

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

Para uma enumeração MyEnum definido em AIDL, iteração é fornecido como se segue.

No backend CPP:

    ::android::enum_range<MyEnum>()

No back-end do NDK:

   ::ndk::enum_range<MyEnum>()

No backend do Rust:

    MyEnum::enum_range()

Gerenciamento de tópicos

Cada instância de libbinder em um processo mantém um pool de threads. Para a maioria dos casos de uso, deve ser exatamente um threadpool, compartilhado entre todos os back-ends. A única exceção a isso é quando o código fornecedor pode carregar outra cópia do libbinder falar com /dev/vndbinder . Uma vez que este 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 (uma vez que já foi iniciado):

    BinderInternal.setMaxThreads(<new larger value>);

Para o back-end C ++, 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 NDK:

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

No backend do Rust:

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