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.
Na tabela a seguir, a estabilidade da superfície da API refere-se à capacidade de compilar código nessa superfície da API de forma que o código possa ser entregue independentemente do binário system.img
libbinder.so
.
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*) | interface_aidl |
PCP | C++ | libbinder (instável) | todos |
Ferrugem | Ferrugem | libbinder_rs (estável*) | interface_aidl |
- Estas superfícies de API são estáveis, mas muitas das APIs, como as de gestão de serviços, são reservadas para uso interno da plataforma e não estão disponíveis para aplicações. Para obter mais informações sobre como usar AIDL em aplicativos, consulte a documentação do desenvolvedor .
- O backend 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
, o que permite que ela seja estável e portátil. APEXes usam a caixa do fichário da mesma forma que qualquer outra pessoa no lado do sistema. A parte Rust é empacotada em um APEX e enviada dentro dele. Depende dolibbinder_ndk.so
na partição do sistema.
Construir sistemas
Dependendo do back-end, existem duas maneiras de compilar AIDL em código stub. Para obter mais detalhes sobre os sistemas de construção, consulte a Referência do Módulo Soong .
Sistema de construçã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 backends Java/CPP do AIDL são usados (não o backend 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 do Rust deve ser usado apenas com o 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 .
interface_aidl
Os tipos usados com este sistema de compilação devem ser estruturados. Para serem estruturados, os parcelables devem conter campos diretamente e não ser declarações de tipos definidos diretamente nos idiomas de destino. Para saber como o AIDL estruturado se encaixa no AIDL estável, consulte AIDL estruturado versus AIDL estável .
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 de NDK | Tipo de ferrugem |
---|---|---|---|
boleano | bool | bool | bool |
byte | int8_t | int8_t | i8 |
Caracteres | char16_t | char16_t | u16 |
interno | int32_t | int32_t | i32 |
longo | int64_t | int64_t | i64 |
flutuador | flutuador | flutuador | f32 |
dobro | dobro | dobro | f64 |
Corda | andróide::String16 | std::string | Corda |
android.os.Parcelável | android::Parcelável | N / D | N / D |
IBinder | android::IBinder | ndk::SpAIBinder | fichário::SpIBinder |
T[] | std::vetor<T> | std::vetor<T> | Em: &[T] Fora: Vec<T> |
byte[] | std::vector<uint8_t> | std::vetor<int8_t> 1 | Em: &[u8] Fora: Vec<u8> |
Lista<T> | std::vetor<T> 2 | std::vetor<T> 3 | Em: &[T] 4 Fora: Vec<T> |
Descritor de arquivo | android::base::unique_fd | N / D | binder::parcel::ParcelFileDescriptor |
ParcelFileDescriptor | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | fichário::parcel::ParcelFileDescriptor |
tipo de interface (T) | android::sp<T> | std::shared_ptr<T> | fichário:: 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 backend C++ suporta List<T>
onde T
é String
, IBinder
, ParcelFileDescriptor
ou parcelable. No Android 13 ou superior, T
pode ser qualquer tipo não primitivo (incluindo tipos de interface), exceto arrays. AOSP recomenda que você use tipos de array como T[]
, pois eles funcionam em todos os back-ends.
3. O back-end do NDK oferece suporte List<T>
onde T
é String
, ParcelFileDescriptor
ou parcelable. No Android 13 ou superior, T
pode ser de 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, há suporte para matrizes de tamanho fixo. Matrizes de tamanho fixo podem ter múltiplas dimensões (por exemplo, int[3][4]
). No backend Java, 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
. Isto 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 receptor. 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 evitar usar o especificador de argumento inout
. Se você usar inout
com uma interface versionada 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 ao Rust, um tipo inout
normal recebe &mut Vec<T>
, e um tipo inout
lista recebe &mut Vec<T>
.
UTF8/UTF16
Com o backend 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 para 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 interface
e IBinder
, que sempre podem ser nulos para leituras NDK e gravações CPP/NDK. Para obter mais informações sobre a anotação nullable
, consulte Anotações em AIDL .
Parceláveis personalizados
Um parcelable personalizado é um parcelable implementado manualmente em um back-end de destino. Use parcelables personalizados somente quando 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 o AIDL saiba sobre ele, a declaração parcelable do AIDL fica assim:
package my.pack.age;
parcelable Foo;
Por padrão, isso declara um Java parcelable onde my.pack.age.Foo
é uma classe Java que implementa a interface Parcelable
.
Para uma declaração de um backend CPP customizado parcelavel em AIDL, use cpp_header
:
package my.pack.age;
parcelable Foo cpp_header "my/pack/age/Foo.h";
A implementação C++ em my/pack/age/Foo.h
se parece com isto:
#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 parcelavel 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
é 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 customizado parcelavel em AIDL, use rust_type
:
package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";
A implementação do Rust em rust_crate/src/lib.rs
é 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);
Então você pode usar este parcelable como um tipo em arquivos AIDL, mas não será gerado pelo AIDL. Forneça os operadores <
e ==
para parcelables personalizados de back-end CPP/NDK para usá-los em union
.
Valores padrão
Parceláveis estruturados podem declarar valores padrão por campo para primitivos, String
s 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 faltando, 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 backend 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 erros 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 do fichário. Seu uso está 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 não pode 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 acontecer durante o processamento da transação em si. Em geral, ao obter um erro de uma função 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) devem ser considerados em 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 são 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 do serviço são normalmente definidos na interface AIDL como um const int
ou enum
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 de serviç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 backend CPP usa android::binder::Status
. O back-end do NDK usa ndk::ScopedAStatus
. Cada método gerado pelo AIDL retorna um destes, representando o status do método. O back-end do Rust usa os mesmos valores de código de exceção que o NDK, mas os converte em erros nativos do Rust ( StatusCode
, ExceptionCode
) antes de entregá-los ao usuário. Para erros específicos do serviço, o Status
ou ScopedAStatus
retornado 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 |
PCP | 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. Estes exemplos usam um tipo definido, my.package.IFoo
. Para obter instruções sobre como usar o backend Rust, consulte o exemplo Rust AIDL na página Android Rust Patterns .
Importando tipos
Quer o tipo definido seja uma interface, parcelavel ou união, 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).
Implementando serviços
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ê tem um arquivo AIDL como este:
package my.package;
interface IFoo {
int doFoo();
}
Em Java, você deve 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 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(())
}
}
Ou com 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(())
}
}
Registrando e obtendo serviços
Os serviços na plataforma Android geralmente são registrados no processo servicemanager
. Além das APIs abaixo, algumas APIs verificam o serviço (ou seja, retornam imediatamente se o serviço não estiver disponível). Verifique a interface servicemanager
correspondente para obter 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);
// 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
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 Rust assíncrono, com um tempo de execução de thread único:
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 das outras opções é que não chamamos join_thread_pool
ao usar Rust assíncrono e um tempo de execução de thread único. Isso ocorre porque você precisa fornecer ao Tokio um thread onde ele possa executar tarefas geradas. Neste exemplo, o thread principal servirá para esse propósito. Quaisquer tarefas geradas usando tokio::spawn
serão executadas no thread principal.
No back-end Rust assíncrono, com um tempo de execução multithread:
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 do Tokio, as tarefas geradas não são executadas no thread principal. Portanto, faz mais sentido chamar join_thread_pool
no thread principal para que o thread principal não fique apenas ocioso. Você deve encerrar a chamada em block_in_place
para sair do contexto assíncrono.
Ligando à morte
Você pode solicitar uma notificação quando um serviço que hospeda um fichário morrer. 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 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 chamemy_binder.link_to_death(&mut my_death_recipient)
. Observe que, comoDeathRecipient
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 kernel binder, as informações do chamador ficam disponíveis em diversas 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 Linux. Ao receber uma chamada unidirecional, o PID de chamada é 0. Quando fora de um contexto de transação do binder, 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 o padrão):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
Relatórios de bugs e API de depuração para serviços
Quando relatórios de bugs 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 dumpsys
binários em todos os serviços registrados no gerenciador de serviços para despejar 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 em que 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 despejo à permissão do aplicativo android.permission.DUMP
ou restringir o despejo 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 o 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 durante a depuração ou quando você tem um fichário desconhecido.
Em Java, você pode obter o descritor de interface com 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 NDK e Rust não oferecem suporte a essa funcionalidade.
Obtendo estaticamente o descritor de interface
Às vezes (como ao registrar 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 Enum
Em back-ends nativos, você pode iterar sobre os valores possíveis que um enum pode assumir. Devido a considerações de tamanho do código, isso não é compatível com 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_values()
Gerenciamento de threads
Cada instância do libbinder
em um processo mantém um threadpool. Para a maioria dos casos de uso, deve ser exatamente um threadpool, 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 conversar com /dev/vndbinder
. Como isso está em um nó fichário separado, o conjunto de threads não é compartilhado.
Para o backend Java, o threadpool só pode aumentar de tamanho (já que já foi iniciado):
BinderInternal.setMaxThreads(<new larger value>);
Para o backend 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 Rust assíncrono, você precisa de dois threadpools: binder e Tokio. Isso significa que os aplicativos que usam Rust assíncrono precisam de considerações especiais, especialmente quando se trata do uso de join_thread_pool
. Consulte a seção sobre registro de serviços para obter mais informações sobre isso.
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 com base em regras de linguagem, o uso de nomes de campos ou tipos 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 de "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 vinculações não ergonômicas ou falha total na compilação.
Se você já reservou nomes em suas definições AIDL, poderá renomear 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á criados continuarão a interoperar.
Nomes a serem evitados: * Palavras-chave C++ * Palavras-chave Java * Palavras-chave Rust