Un backend AIDL es un objetivo para la generación de códigos auxiliares. Cuando utiliza archivos AIDL, siempre los utiliza en un idioma particular con un tiempo de ejecución específico. Dependiendo del contexto, debes usar diferentes backends AIDL.
En la siguiente tabla, la estabilidad de la superficie API se refiere a la capacidad de compilar código en esta superficie API de manera que el código pueda entregarse independientemente del binario system.img
libbinder.so
.
AIDL tiene los siguientes backends:
backend | Idioma | Superficie API | Construir sistemas |
---|---|---|---|
Java | Java | SDK/SystemApi (estable*) | todo |
NDK | C++ | libbinder_ndk (estable*) | interfaz_aidl |
CPP | C++ | libbinder (inestable) | todo |
Óxido | Óxido | libbinder_rs (estable*) | interfaz_aidl |
- Estas superficies de API son estables, pero muchas de las API, como las de administración de servicios, están reservadas para uso interno de la plataforma y no están disponibles para las aplicaciones. Para obtener más información sobre cómo usar AIDL en aplicaciones, consulte la documentación para desarrolladores .
- El backend de Rust se introdujo en Android 12; El backend NDK ha estado disponible a partir de Android 10.
- La caja Rust está construida sobre
libbinder_ndk
, lo que le permite ser estable y portátil. Los APEX utilizan la caja de carpetas de la misma manera que cualquier otra persona del lado del sistema. La porción de Rust se incluye en un APEX y se envía dentro de él. Depende delibbinder_ndk.so
en la partición del sistema.
Construir sistemas
Dependiendo del backend, hay dos formas de compilar AIDL en código auxiliar. Para obtener más detalles sobre los sistemas de compilación, consulte la Referencia del módulo Soong .
Sistema de construcción central
En cualquier módulo cc_
o java_
Android.bp (o en sus equivalentes Android.mk
), los archivos .aidl
se pueden especificar como archivos fuente. En este caso, se utilizan los backends Java/CPP de AIDL (no el backend NDK), y las clases para usar los archivos AIDL correspondientes se agregan al módulo automáticamente. Opciones como local_include_dirs
, que le indica al sistema de compilación la ruta raíz a los archivos AIDL en ese módulo, se pueden especificar en estos módulos bajo un grupo aidl:
:. Tenga en cuenta que el backend de Rust solo se usa con Rust. Los módulos rust_
se manejan de manera diferente en el sentido de que los archivos AIDL no se especifican como archivos fuente. En cambio, el módulo aidl_interface
produce un rustlib
llamado <aidl_interface name>-rust
al que se puede vincular. Para obtener más detalles, consulte el ejemplo de Rust AIDL .
interfaz_aidl
Los tipos utilizados con este sistema de compilación deben estar estructurados. Para estar estructurados, los parcelables deben contener campos directamente y no ser declaraciones de tipos definidos directamente en los idiomas de destino. Para saber cómo encaja el AIDL estructurado con el AIDL estable, consulte AIDL estructurado versus estable .
Tipos
Puede considerar el compilador aidl
como una implementación de referencia para tipos. Cuando crea una interfaz, invoque aidl --lang=<backend> ...
para ver el archivo de interfaz resultante. Cuando utiliza el módulo aidl_interface
, puede ver el resultado en out/soong/.intermediates/<path to module>/
.
Tipo Java/AIDL | Tipo C++ | Tipo NDK | Tipo de óxido |
---|---|---|---|
booleano | booleano | booleano | booleano |
byte | int8_t | int8_t | i8 |
carbonizarse | char16_t | char16_t | u16 |
En t | int32_t | int32_t | i32 |
largo | int64_t | int64_t | i64 |
flotar | flotar | flotar | f32 |
doble | doble | doble | f64 |
Cadena | android::Cadena16 | std::cadena | Cadena |
android.os.Parcelable | android::Parcelable | N / A | N / A |
Carpeta | android::IBinder | ndk::SpAIBinder | carpeta::SpIBinder |
T[] | estándar::vector<T> | estándar::vector<T> | En t] Fuera: Vec<T> |
byte[] | std::vector<uint8_t> | std::vector<int8_t> 1 | En: &[u8] Fuera: Vec<u8> |
Lista<T> | estándar::vector<T> 2 | estándar::vector<T> 3 | En: &[T] 4 Fuera: Vec<T> |
Descriptor de archivo | android::base::unique_fd | N / A | carpeta::parcel::ParcelFileDescriptor |
Descriptor de archivo de parcela | android::os::ParcelFileDescriptor | ndk::ScopedFileDescriptor | carpeta::parcel::ParcelFileDescriptor |
tipo de interfaz (T) | android::sp<T> | std::shared_ptr<T> | aglutinante::Fuerte |
tipo parcelable (T) | t | t | t |
tipo de unión (T) 5 | t | t | t |
T[N] 6 | estándar::matriz<T, N> | estándar::matriz<T, N> | [T; NORTE] |
1. En Android 12 o superior, las matrices de bytes usan uint8_t en lugar de int8_t por razones de compatibilidad.
2. El backend de C++ admite List<T>
donde T
es uno de String
, IBinder
, ParcelFileDescriptor
o Parcelable. En Android 13 o superior, T
puede ser cualquier tipo no primitivo (incluidos los tipos de interfaz), excepto matrices. AOSP recomienda utilizar tipos de matrices como T[]
, ya que funcionan en todos los backends.
3. El backend del NDK admite List<T>
donde T
es uno de String
, ParcelFileDescriptor
o Parcelable. En Android 13 o superior, T
puede ser cualquier tipo no primitivo, excepto matrices.
4. Los tipos se pasan de manera diferente para el código Rust dependiendo de si son entrada (un argumento) o salida (un valor devuelto).
5. Los tipos de unión son compatibles con Android 12 y versiones posteriores.
6. En Android 13 o superior, se admiten matrices de tamaño fijo. Las matrices de tamaño fijo pueden tener múltiples dimensiones (por ejemplo, int[3][4]
). En el backend de Java, las matrices de tamaño fijo se representan como tipos de matrices.
Direccionalidad (dentro/fuera/infuera)
Al especificar los tipos de argumentos para funciones, puede especificarlos como in
, out
o inout
. Esto controla en qué dirección se pasa la información para una llamada IPC. in
es la dirección predeterminada e indica que los datos se pasan de la persona que llama al destinatario. out
significa que los datos se pasan del destinatario a la persona que llama. inout
es la combinación de ambos. Sin embargo, el equipo de Android recomienda evitar el uso del especificador de argumentos inout
. Si usa inout
con una interfaz versionada y una persona que llama más antigua, los campos adicionales que están presentes solo en la persona que llama se restablecen a sus valores predeterminados. Con respecto a Rust, un tipo inout
normal recibe &mut Vec<T>
y un tipo inout
lista recibe &mut Vec<T>
.
UTF8/UTF16
Con el backend de CPP puedes elegir si las cadenas son utf-8 o utf-16. Declare cadenas como @utf8InCpp String
en AIDL para convertirlas automáticamente a utf-8. Los backends de NDK y Rust siempre usan cadenas utf-8. Para obtener más información sobre la anotación utf8InCpp
, consulte Anotaciones en AIDL .
Anulabilidad
Puede anotar tipos que pueden ser nulos en el backend de Java con @nullable
para exponer valores nulos a los backends de CPP y NDK. En el backend de Rust, estos tipos @nullable
se exponen como Option<T>
. Los servidores nativos rechazan valores nulos de forma predeterminada. Las únicas excepciones a esto son los tipos interface
e IBinder
, que siempre pueden ser nulos para lecturas de NDK y escrituras de CPP/NDK. Para obtener más información sobre la anotación nullable
, consulte Anotaciones en AIDL .
Parcelables personalizados
Un paquete personalizado es un paquete que se implementa manualmente en un backend de destino. Utilice elementos parcelables personalizados solo cuando intente agregar compatibilidad con otros idiomas para un elemento parcelable personalizado existente que no se puede cambiar.
Para declarar un paquete personalizado para que AIDL lo sepa, la declaración de paquete AIDL se ve así:
package my.pack.age;
parcelable Foo;
De forma predeterminada, esto declara un paquete Java donde my.pack.age.Foo
es una clase Java que implementa la interfaz Parcelable
.
Para una declaración de un backend CPP personalizado parcelable en AIDL, utilice cpp_header
:
package my.pack.age;
parcelable Foo cpp_header "my/pack/age/Foo.h";
La implementación de C++ en my/pack/age/Foo.h
se ve así:
#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 una declaración de un paquete NDK personalizado en AIDL, use ndk_header
:
package my.pack.age;
parcelable Foo ndk_header "android/pack/age/Foo.h";
La implementación de NDK en android/pack/age/Foo.h
tiene este aspecto:
#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);
};
En Android 15 (AOSP experimental), para la declaración de un paquete Rust personalizado en AIDL, use rust_type
:
package my.pack.age;
@RustOnlyStableParcelable parcelable Foo rust_type "rust_crate::Foo";
La implementación de Rust en rust_crate/src/lib.rs
se ve así:
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);
Luego puede usar este paquete como tipo en archivos AIDL, pero AIDL no lo generará. Proporcione operadores <
y ==
para parcelables personalizados de backend de CPP/NDK para usarlos en union
.
Valores predeterminados
Parcelables estructurados pueden declarar valores predeterminados por campo para primitivas, String
y matrices de estos tipos.
parcelable Foo {
int numField = 42;
String stringField = "string value";
char charValue = 'a';
...
}
En el backend de Java, cuando faltan valores predeterminados, los campos se inicializan como valores cero para tipos primitivos y null
para tipos no primitivos.
En otros servidores, los campos se inicializan con valores inicializados predeterminados cuando los valores predeterminados no están definidos. Por ejemplo, en el backend de C++, los campos String
se inicializan como una cadena vacía y los campos List<T>
se inicializan como un vector<T>
vacío. Los campos @nullable
se inicializan como campos de valor nulo.
Manejo de errores
El sistema operativo Android proporciona tipos de errores integrados para que los servicios los utilicen al informar errores. Estos son utilizados por Binder y pueden ser utilizados por cualquier servicio que implemente una interfaz de Binder. Su uso está bien documentado en la definición AIDL y no requieren ningún estado ni tipo de devolución definido por el usuario.
Parámetros de salida con errores
Cuando una función AIDL informa un error, es posible que la función no inicialice ni modifique los parámetros de salida. Específicamente, los parámetros de salida pueden modificarse si el error ocurre durante el despaquete en lugar de ocurrir durante el procesamiento de la transacción en sí. En general, cuando se obtiene un error de una función AIDL, todos los parámetros inout
y out
, así como el valor de retorno (que actúa como un parámetro out
en algunos servidores), deben considerarse en un estado indefinido.
Qué valores de error usar
Muchos de los valores de error integrados se pueden usar en cualquier interfaz AIDL, pero algunos se tratan de una manera especial. Por ejemplo, EX_UNSUPPORTED_OPERATION
y EX_ILLEGAL_ARGUMENT
se pueden usar cuando describen la condición de error, pero EX_TRANSACTION_FAILED
no se debe usar porque la infraestructura subyacente lo trata de manera especial. Consulte las definiciones específicas del backend para obtener más información sobre estos valores integrados.
Si la interfaz AIDL requiere valores de error adicionales que no están cubiertos por los tipos de error integrados, entonces pueden usar el error integrado específico del servicio especial que permite la inclusión de un valor de error específico del servicio definido por el usuario. . Estos errores específicos del servicio generalmente se definen en la interfaz AIDL como una enum
const int
o int
y no son analizados por el enlazador.
En Java, los errores se asignan a excepciones, como android.os.RemoteException
. Para excepciones específicas de servicios, Java usa android.os.ServiceSpecificException
junto con el error definido por el usuario.
El código nativo en Android no utiliza excepciones. El backend de CPP utiliza android::binder::Status
. El backend de NDK utiliza ndk::ScopedAStatus
. Cada método generado por AIDL devuelve uno de estos, que representa el estado del método. El backend de Rust utiliza los mismos valores de código de excepción que el NDK, pero los convierte en errores nativos de Rust ( StatusCode
, ExceptionCode
) antes de entregárselos al usuario. Para errores específicos del servicio, el Status
o ScopedAStatus
devuelto utiliza EX_SERVICE_SPECIFIC
junto con el error definido por el usuario.
Los tipos de errores integrados se pueden encontrar en los siguientes archivos:
backend | Definición |
---|---|
Java | android/os/Parcel.java |
CPP | binder/Status.h |
NDK | android/binder_status.h |
Óxido | android/binder_status.h |
Usando varios backends
Estas instrucciones son específicas del código de la plataforma Android. Estos ejemplos utilizan un tipo definido, my.package.IFoo
. Para obtener instrucciones sobre cómo utilizar el backend de Rust, consulte el ejemplo de Rust AIDL en la página de Patrones de Rust de Android .
Tipos de importación
Ya sea que el tipo definido sea una interfaz, parcelable o unión, puede importarlo en Java:
import my.package.IFoo;
O en el backend de CPP:
#include <my/package/IFoo.h>
O en el backend del NDK (observe el espacio de nombres adicional aidl
):
#include <aidl/my/package/IFoo.h>
O en el backend de Rust:
use my_package::aidl::my::package::IFoo;
Aunque puedes importar un tipo anidado en Java, en los backends de CPP/NDK debes incluir el encabezado de su tipo raíz. Por ejemplo, al importar una Bar
de tipo anidada definida en my/package/IFoo.aidl
( IFoo
es el tipo raíz del archivo), debe incluir <my/package/IFoo.h>
para el backend de CPP (o <aidl/my/package/IFoo.h>
para el backend de NDK).
Servicios de implementación
Para implementar un servicio, debe heredar de la clase stub nativa. Esta clase lee comandos del controlador de carpeta y ejecuta los métodos que implementa. Imagina que tienes un archivo AIDL como este:
package my.package;
interface IFoo {
int doFoo();
}
En Java, debes extender desde esta clase:
import my.package.IFoo;
public class MyFoo extends IFoo.Stub {
@Override
int doFoo() { ... }
}
En el backend de CPP:
#include <my/package/BnFoo.h>
class MyFoo : public my::package::BnFoo {
android::binder::Status doFoo(int32_t* out) override;
}
En el backend del NDK (observe el espacio de nombres adicional aidl
):
#include <aidl/my/package/BnFoo.h>
class MyFoo : public aidl::my::package::BnFoo {
ndk::ScopedAStatus doFoo(int32_t* out) override;
}
En el backend de 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(())
}
}
O con Rust así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(())
}
}
Registrarse y obtener servicios
Los servicios en la plataforma Android generalmente se registran con el proceso servicemanager
. Además de las API siguientes, algunas API verifican el servicio (lo que significa que regresan inmediatamente si el servicio no está disponible). Consulte la interfaz servicemanager
correspondiente para obtener detalles exactos. Estas operaciones solo se pueden realizar al compilar en la plataforma Android.
En 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"));
En el backend de 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"));
En el backend del NDK (observe el espacio de nombres adicional 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")));
En el backend de 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()
}
En el backend asíncrono de Rust, con un tiempo de ejecución de un solo subproceso:
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
}
Una diferencia importante con las otras opciones es que no llamamos join_thread_pool
cuando usamos Rust asíncrono y un tiempo de ejecución de un solo subproceso. Esto se debe a que necesitas darle a Tokio un hilo donde pueda ejecutar las tareas generadas. En este ejemplo, el hilo principal cumplirá ese propósito. Cualquier tarea generada usando tokio::spawn
se ejecutará en el hilo principal.
En el backend asíncrono de Rust, con un tiempo de ejecución multiproceso:
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();
});
}
Con el tiempo de ejecución de Tokio multiproceso, las tareas generadas no se ejecutan en el hilo principal. Por lo tanto, tiene más sentido llamar join_thread_pool
en el hilo principal para que el hilo principal no esté simplemente inactivo. Debes ajustar la llamada en block_in_place
para salir del contexto asíncrono.
Vinculando a la muerte
Puede solicitar recibir una notificación cuando un servicio que aloja una carpeta falle. Esto puede ayudar a evitar la filtración de servidores proxy de devolución de llamadas o ayudar en la recuperación de errores. Realice estas llamadas en objetos proxy de carpeta.
- En Java, utilice
android.os.IBinder::linkToDeath
. - En el backend de CPP, use
android::IBinder::linkToDeath
. - En el backend del NDK, use
AIBinder_linkToDeath
. - En el backend de Rust, cree un objeto
DeathRecipient
, luego llame amy_binder.link_to_death(&mut my_death_recipient)
. Tenga en cuenta que debido a queDeathRecipient
es propietario de la devolución de llamada, debe mantener vivo ese objeto mientras desee recibir notificaciones.
Información de la llamada
Al recibir una llamada de kernel Binder, la información de la persona que llama está disponible en varias API. El PID (o ID de proceso) se refiere al ID del proceso de Linux que envía una transacción. El UID (o ID de usuario) se refiere al ID de usuario de Linux. Al recibir una llamada unidireccional, el PID de llamada es 0. Cuando están fuera del contexto de una transacción de carpeta, estas funciones devuelven el PID y el UID del proceso actual.
En el servidor de Java:
... = Binder.getCallingPid();
... = Binder.getCallingUid();
En el backend de CPP:
... = IPCThreadState::self()->getCallingPid();
... = IPCThreadState::self()->getCallingUid();
En el servidor del NDK:
... = AIBinder_getCallingPid();
... = AIBinder_getCallingUid();
En el backend de Rust, al implementar la interfaz, especifique lo siguiente (en lugar de permitir que sea predeterminado):
... = ThreadState::get_calling_pid();
... = ThreadState::get_calling_uid();
Informes de errores y API de depuración para servicios
Cuando se ejecutan informes de errores (por ejemplo, con adb bugreport
), recopilan información de todo el sistema para ayudar a depurar diversos problemas. Para los servicios AIDL, los informes de errores utilizan dumpsys
binarios en todos los servicios registrados con el administrador de servicios para volcar su información en el informe de errores. También puedes usar dumpsys
en la línea de comando para obtener información de un servicio con dumpsys SERVICE [ARGS]
. En los backends de C++ y Java, puedes controlar el orden en el que se descargan los servicios utilizando argumentos adicionales para addService
. También puede utilizar dumpsys --pid SERVICE
para obtener el PID de un servicio durante la depuración.
Para agregar resultados personalizados a su servicio, puede anular el método dump
en su objeto de servidor como si estuviera implementando cualquier otro método IPC definido en un archivo AIDL. Al hacer esto, debe restringir el volcado al permiso de la aplicación android.permission.DUMP
o restringir el volcado a UID específicos.
En el servidor de Java:
@Override
protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter fout,
@Nullable String[] args) {...}
En el backend de CPP:
status_t dump(int, const android::android::Vector<android::String16>&) override;
En el servidor del NDK:
binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
En el backend de Rust, al implementar la interfaz, especifique lo siguiente (en lugar de permitir que sea predeterminado):
fn dump(&self, mut file: &File, args: &[&CStr]) -> binder::Result<()>
Obtener dinámicamente el descriptor de interfaz
El descriptor de interfaz identifica el tipo de interfaz. Esto es útil al depurar o cuando tiene una carpeta desconocida.
En Java, puedes obtener el descriptor de interfaz con código como:
service = /* get ahold of service object */
... = service.asBinder().getInterfaceDescriptor();
En el backend de CPP:
service = /* get ahold of service object */
... = IInterface::asBinder(service)->getInterfaceDescriptor();
Los backends de NDK y Rust no admiten esta funcionalidad.
Obteniendo estáticamente el descriptor de interfaz
A veces (como al registrar servicios @VintfStability
), necesita saber cuál es el descriptor de interfaz estáticamente. En Java, puedes obtener el descriptor agregando código como:
import my.package.IFoo;
... IFoo.DESCRIPTOR
En el backend de CPP:
#include <my/package/BnFoo.h>
... my::package::BnFoo::descriptor
En el backend del NDK (observe el espacio de nombres adicional aidl
):
#include <aidl/my/package/BnFoo.h>
... aidl::my::package::BnFoo::descriptor
En el backend de Rust:
aidl::my::package::BnFoo::get_descriptor()
Rango de enumeración
En backends nativos, puede iterar sobre los posibles valores que puede adoptar una enumeración. Debido a consideraciones de tamaño del código, esto no es compatible actualmente con Java.
Para una enumeración MyEnum
definida en AIDL, la iteración se proporciona de la siguiente manera.
En el backend de CPP:
::android::enum_range<MyEnum>()
En el servidor del NDK:
::ndk::enum_range<MyEnum>()
En el backend de Rust:
MyEnum::enum_values()
Gestión de hilos
Cada instancia de libbinder
en un proceso mantiene un grupo de subprocesos. Para la mayoría de los casos de uso, este debería ser exactamente un grupo de subprocesos, compartido entre todos los backends. La única excepción a esto es cuando el código del proveedor puede cargar otra copia de libbinder
para comunicarse con /dev/vndbinder
. Dado que se encuentra en un nodo de carpeta separado, el grupo de subprocesos no se comparte.
Para el backend de Java, el grupo de subprocesos solo puede aumentar de tamaño (dado que ya está iniciado):
BinderInternal.setMaxThreads(<new larger value>);
Para el backend de CPP, están disponibles las siguientes operaciones:
// 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();
De manera similar, en el backend del NDK:
bool success = ABinderProcess_setThreadPoolMaxThreadCount(numThreads);
ABinderProcess_startThreadPool();
ABinderProcess_joinThreadPool();
En el backend de Rust:
binder::ProcessState::start_thread_pool();
binder::add_service(“myservice”, my_service_binder).expect(“Failed to register service?”);
binder::ProcessState::join_thread_pool();
Con el backend asíncrono de Rust, necesitas dos grupos de subprocesos: binder y Tokio. Esto significa que las aplicaciones que utilizan Rust asíncrono necesitan consideraciones especiales, especialmente cuando se trata del uso de join_thread_pool
. Consulte la sección sobre registro de servicios para obtener más información al respecto.
Nombres reservados
C++, Java y Rust reservan algunos nombres como palabras clave o para uso específico del lenguaje. Si bien AIDL no impone restricciones basadas en reglas del lenguaje, el uso de nombres de campo o tipo que coincidan con un nombre reservado puede provocar un error de compilación para C++ o Java. Para Rust, se cambia el nombre del campo o tipo utilizando la sintaxis de "identificador sin formato", a la que se puede acceder mediante el prefijo r#
.
Le recomendamos que evite el uso de nombres reservados en sus definiciones AIDL siempre que sea posible para evitar enlaces poco ergonómicos o fallas de compilación.
Si ya tiene nombres reservados en sus definiciones AIDL, puede cambiar el nombre de los campos de forma segura sin dejar de ser compatible con el protocolo; Es posible que necesites actualizar tu código para continuar con la construcción, pero cualquier programa ya creado continuará interoperando.
Nombres que se deben evitar: * Palabras clave C++ * Palabras clave Java * Palabras clave Rust