Backends de AIDL

Un backend de AIDL es un destino para la generación de código de stub. Cuando usas archivos AIDL, úsalas siempre en un lenguaje particular con un tiempo de ejecución específico. Según en el contexto, debes usar distintos backends de AIDL.

En la siguiente tabla, la estabilidad de la plataforma de la API hace referencia a la capacidad de compilar código en esta plataforma de la API de manera que el código se pueda entregar de forma independiente del objeto binario libbinder.so de system.img.

El AIDL tiene los siguientes backends:

Backend Idioma Plataforma de la API Sistemas de compilación
Java Java SDK/SystemApi (estable*) todos
NDK C++ libbinder_ndk (estable*) aidl_interface
CPP C++ libbinder (inestable) todos
Rust Rust libbinder_rs (estable*) aidl_interface
  • Estas plataformas de APIs son estables, pero muchas de las APIs, como las de servicio de configuraciones, se reservan para uso interno de la plataforma y no están disponibles para de Google Chat. Para obtener más información sobre cómo usar AIDL en apps, consulta la documentación para desarrolladores.
  • El backend de Rust se introdujo en Android 12. el El backend del NDK está disponible a partir de Android 10.
  • El contenedor de Rust se compila sobre libbinder_ndk, lo que permite que se pueda estable y portátil. Los APEX usan el contenedor de carpetas de la misma manera que cualquiera del lado del sistema. La parte de Rust se agrupa en un APEX y se envía dentro de él. Depende del libbinder_ndk.so en la partición del sistema.

Sistemas de compilación

Según el backend, hay dos maneras de compilar AIDL en código de stub. Para conocer más detalles sobre los sistemas de compilación, consulta la Referencia del módulo Soong

Sistema de compilación principal

En cualquier módulo de Android.bp cc_ o java_ (o en sus equivalentes de Android.mk), Los archivos .aidl se pueden especificar como archivos de origen. En este caso, el código Java/CPP se usan backends del AIDL (no el backend del 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 de ese módulo se pueden especificar en estos módulos en un aidl: grupo. Ten en cuenta que el backend de Rust solo se usa con Rust. Los módulos rust_ se manejan de manera diferente, ya que los archivos AIDL no se especifican como archivos de origen. En su lugar, el módulo aidl_interface produce un rustlib llamado <aidl_interface name>-rust con la que se puede realizar la vinculación. Para obtener más detalles, consulta el ejemplo de AIDL de Rust.

aidl_interface

Los tipos que se usan con este sistema de compilación deben estar estructurados. Para estar estructuradas, los objetos parcelables deben contener campos directamente y no ser declaraciones de tipos se definen directamente en los idiomas de destino. Para saber cómo se ajusta el AIDL estructurado al AIDL estable, consulta AIDL estructurado versus estable.

Tipos

Puedes considerar el compilador aidl como una implementación de referencia para los tipos. Cuando crees una interfaz, invoca a aidl --lang=<backend> ... para ver el archivo de interfaz resultante. Cuando usas el módulo aidl_interface, puedes ver el resultado en out/soong/.intermediates/<path to module>/.

Tipo de Java/AIDL Tipo de C++ Tipo de NDK Tipo de Rust
boolean booleano booleano bool
byte int8_t int8_t i8
char char16_t char16_t U16
int int32_t int32_t i32
long int64_t int64_t i64
float float float F32
double double double F64
String android::String16 std::string String
android.os.Parcelable android::Parcelable N/A N/A
IBinder android::IBinder ndk::SpAIBinder Binder::SpIBinder,
M std::vector<T> std::vector<T> Entrada: &[T]
Salida: Vec<T>
byte[] std::vector<uint8_t> std::vector<int8_t>1 Entrada: &[u8]
Salida: Vec<u8>
Lista<T> std::vector<T>2 std::vector<T>3 Entrada: &[T]4
Salida: Vec<T>
FileDescriptor android::base::unique_fd N/A binder::parcel::ParcelFileDescriptor
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
tipo de interfaz (T) android::sp<T> std::shared_ptr<T>7 Binder::Sólida
tipo parcelable (T) T T T
tipo de unión (T)5 T T T
V[N] 6 std::matriz<T, N> std::matriz<T, N> [T; N]

1. En Android 12 o versiones posteriores, los arrays de bytes usan uint8_t en lugar de int8_t por razones de compatibilidad.

2. El backend de C++ admite List<T>, en el que T es uno de String, IBinder, ParcelFileDescriptor o parcelable. En Android 13 o versiones posteriores, T puede ser cualquier tipo no primitivo (incluidos los tipos de interfaz), excepto los arrays. AOSP recomienda que uses tipos de array como T[], ya que funcionan en todos los backends.

3. El backend del NDK admite List<T>, en el que T es uno de String, ParcelFileDescriptor o parcelable. En Android 13 o versiones posteriores, T puede ser cualquier tipo no primitivo, excepto los arrays.

4. Los tipos se pasan de manera diferente para el código Rust dependiendo de si sean entrada (un argumento) o una salida (un valor mostrado).

5. Los tipos de unión son compatibles con Android 12 y mayores.

6. En Android 13 o versiones posteriores, los arrays de tamaño fijo son no es compatible. Los arrays de tamaño fijo pueden tener varias dimensiones (p.ej., int[3][4]). En el backend de Java, los arrays de tamaño fijo se representan como tipos de array.

7. Para crear una instancia de un objeto SharedRefBase de Binder, usa SharedRefBase::make\<My\>(... args ...) Esta función crea un std::shared_ptr\<T\> objeto que también se administra de forma interna, en caso de que el Binder pertenezca a otro el proceso de administración de recursos. Crear el objeto de otras maneras provoca la doble propiedad.

Direccionalidad (entrada/salida/entrada)

Cuando especifiques los tipos de argumentos de las funciones, puedes especificarlos como in, out o inout. Esto controla en qué dirección se para una llamada IPC. in es la dirección predeterminada e indica que los datos están pasados del emisor al destinatario. out significa que los datos se pasan del llamado al llamador. inout es la combinación de ambos. Sin embargo, el equipo de Android recomienda que evites usar el especificador de argumentos inout. Si usas inout con una interfaz con versión y un llamador anterior, los campos adicionales que solo están presentes en el llamador se restablecen a sus valores predeterminados. Con respecto a Rust, un tipo inout normal recibe &mut Vec<T>. un tipo de lista inout recibe &mut Vec<T>.

interface IRepeatExamples {
    MyParcelable RepeatParcelable(MyParcelable token); // implicitly 'in'
    MyParcelable RepeatParcelableWithIn(in MyParcelable token);
    void RepeatParcelableWithInAndOut(in MyParcelable param, out MyParcelable result);
    void RepeatParcelableWithInOut(inout MyParcelable param);
}

UTF8/UTF16

Con el backend de la CPP, puedes elegir si las cadenas son utf-8 o utf-16. Declarar como @utf8InCpp String en AIDL para convertirlas automáticamente en utf-8. Los backends de NDK y Rust siempre usan cadenas UTF-8. Para obtener más información sobre la anotación utf8InCpp, consulta Anotaciones en AIDL.

Nulabilidad

Puedes anotar tipos que pueden ser nulos con @nullable. Para obtener más información sobre la anotación de nullable, consulta Anotaciones en AIDL.

Objetos parcelables personalizados

Un parcelable personalizado es aquel que se implementa manualmente en un destino. backend. Usa objetos parcelables personalizados solo cuando intentes agregar compatibilidad a otros idiomas de un objeto parcelable personalizado existente que no se puede cambiar.

Para declarar un elemento parcelable personalizado de modo que el AIDL lo sepa, el AIDL declaración parcelable se ve de la siguiente manera:

    package my.pack.age;
    parcelable Foo;

De forma predeterminada, esto declara un elemento parcelable de Java en el que my.pack.age.Foo es una clase de Java que implementa la interfaz Parcelable.

Para declarar un backend de CPP personalizado parcelable en AIDL, usa 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 de la siguiente manera:

    #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 declarar un NDK personalizado parcelable en AIDL, usa ndk_header:

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

La implementación del NDK en android/pack/age/Foo.h se ve de la siguiente manera:

    #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, para la declaración de un Rust personalizado parcelable en AIDL, usa 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 de la siguiente manera:

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, puedes usar este parcelable como un tipo en los archivos AIDL, pero no será generadas por el AIDL. Proporciona los operadores < y == para el backend de CPP/NDK objetos parcelables personalizados para usarlos en union.

Valores predeterminados

Los elementos parcelables estructurados pueden declarar valores predeterminados por campo para primitivos, String y arreglos 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 backends, los campos se inicializan con valores inicializados de forma predeterminada 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 SO Android proporciona tipos de errores integrados para que los servicios los usen cuando informen errores. Binder los usa y cualquier servicio que implemente una interfaz de Binder puede usarlos. Su uso está bien documentado en la definición del AIDL y no requieren ningún estado definido por el usuario ni tipo de datos que se devuelve.

Parámetros de salida con errores

Cuando una función de AIDL informa un error, es posible que no inicialice ni modifíque los parámetros de salida. Específicamente, los parámetros de salida se pueden modificar si el error ocurre durante el desempaquetado, en lugar de hacerlo durante el procesamiento de la transacción. En general, cuando se recibe un error de un AIDL función, todos los parámetros inout y out, así como el valor que se muestra (que que funciona como un parámetro out en algunos backends) deben considerarse un estado indefinido.

Qué valores de error usar

Muchos de los valores de error integrados se pueden usar en cualquier interfaz de AIDL, pero algunos se tratan de una manera especial. Por ejemplo, EX_UNSUPPORTED_OPERATION y Se pueden usar EX_ILLEGAL_ARGUMENT cuando describen la condición de error, pero No se debe usar EX_TRANSACTION_FAILED, ya que el la infraestructura subyacente. Consulta las definiciones específicas del backend para obtener más información información sobre estos valores integrados.

Si la interfaz del AIDL requiere valores de error adicionales que no están cubiertos por los tipos de errores integrados, es posible que usen la clase que permite incluir un valor de error específico del servicio definidas por el usuario. Por lo general, estos errores específicos del servicio se definen en la interfaz AIDL como un enum respaldado por const int o int, y Binder no los analiza.

En Java, los errores se asignan a excepciones, como android.os.RemoteException. Para excepciones específicas del servicio, Java usa android.os.ServiceSpecificException junto con el error definido por el usuario.

El código nativo de Android no usa excepciones. El backend de CPP usa android::binder::Status. El backend del NDK usa ndk::ScopedAStatus. Cada método que genera AIDL muestra uno de estos, que representa el estado del método. El backend de Rust usa los mismos valores de código de excepción que el NDK, pero los convierte en errores nativos de Rust (StatusCode, ExceptionCode) antes de entregarlos al usuario. En el caso de los errores específicos del servicio, Status o ScopedAStatus usan EX_SERVICE_SPECIFIC junto con 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
Rust android/binder_status.h

Usa varios backends

Estas instrucciones son específicas para el código de la plataforma de Android. En estos ejemplos, se usa un tipo definido, my.package.IFoo. Para obtener instrucciones sobre cómo usar el backend de Rust, consulta el ejemplo del AIDL de Rust en Patrones de Rust en Android .

Tipos de importación

Ya sea que el tipo definido sea una interfaz, un elemento parcelable o una unión, puedes importarlo en Java:

import my.package.IFoo;

O en el backend de CPP:

#include <my/package/IFoo.h>

O en el backend de NDK (observa el espacio de nombres aidl adicional):

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

También puedes hacer lo siguiente 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, cuando importas un tipo anidado Bar definido en my/package/IFoo.aidl (IFoo es el tipo raíz del archivo), debes incluir <my/package/IFoo.h> para el backend de CPP (o <aidl/my/package/IFoo.h> para el backend de NDK).

Implementa servicios

Para implementar un servicio, debes heredar de la clase de stub nativa. Esta clase lee comandos del controlador de Binder y ejecuta los métodos que implementas. Imagina que tienes un archivo AIDL como el siguiente:

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

En Java, debes extender 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 de NDK (observa el espacio de nombres aidl adicional):

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

Regístrate y obtén servicios

Los servicios en la plataforma Android suelen registrarse con el proceso servicemanager. Además de las APIs que se indican a continuación, algunas APIs verifican el servicio (lo que significa que regresan de inmediato si el servicio no está disponible). Consulta la interfaz servicemanager correspondiente para obtener los detalles exactos. Estas operaciones solo se pueden realizar cuando se compila para la plataforma de 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, haz lo siguiente:

    #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 de NDK (observa el espacio de nombres aidl adicional):

    #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 entorno de ejecución de un solo subproceso, haz lo siguiente:

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 respecto de las otras opciones es que no llamamos join_thread_pool cuando se usa Rust asíncrono y un entorno de ejecución de un solo subproceso. Este es porque debes asignarle a Tokio un subproceso donde pueda ejecutar las tareas generadas. En en este ejemplo, el subproceso principal cumplirá ese propósito. Todas las tareas creadas con tokio::spawn se ejecutarán en el subproceso principal.

En el backend asíncrono de Rust con un entorno de ejecución de varios subprocesos, haz lo siguiente:

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 multiproceso de Tokio, las tareas generadas no se ejecutan en el conversación. Por lo tanto, tiene más sentido llamar a join_thread_pool en el objeto principal. para que el principal no quede inactivo. Debes unir la llamada en block_in_place para salir del contexto asíncrono.

Puedes solicitar recibir una notificación cuando un servicio que aloja un Binder falle. Esto puede ayudar a evitar fugas de proxies de devolución de llamada o ayudar en la recuperación de errores. Realiza estas llamadas en objetos del proxy de Binder.

  • En Java, usa android.os.IBinder::linkToDeath.
  • En el backend de la CPP, usa android::IBinder::linkToDeath.
  • En el backend del NDK, usa AIBinder_linkToDeath.
  • En el backend de Rust, crea un objeto DeathRecipient y, luego, llama my_binder.link_to_death(&mut my_death_recipient) Ten en cuenta que, como DeathRecipient es el propietario de la devolución de llamada, debes mantener ese objeto activo mientras quieras recibir notificaciones.

Información de la persona que llama

Cuando se recibe una llamada a Binder de kernel, la información del emisor está disponible en varias APIs. El PID (o ID de proceso) se refiere al ID de proceso de Linux del proceso que envía una transacción. El UID (o User-ID) se refiere al Es el ID de usuario de Linux. Cuando se recibe una llamada unidireccional, el PID de llamada es 0. Cuando están fuera de un contexto de transacción de Binder, estas funciones muestran el PID y el UID del proceso actual.

En el backend de Java, haz lo siguiente:

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

En el backend de CPP:

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

En el backend del NDK:

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

En el backend de Rust, cuando implementes la interfaz, especifica lo siguiente (en lugar de permitir que se establezca de forma predeterminada):

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

Informes de errores y API de depuración para servicios

Cuando se ejecutan los informes de errores (por ejemplo, con adb bugreport), se recopilan de todo el sistema para facilitar la depuración de diversos problemas. Para los servicios de AIDL, los informes de errores usan el objeto binario dumpsys en todos los servicios registrados con el administrador del servicio para volcar su información en la informe de errores. También puedes usar dumpsys en la línea de comandos para obtener información de un servicio con dumpsys SERVICE [ARGS]. En los backends de C++ y Java, puedes controlar el orden en que se vuelcan los servicios con argumentos adicionales a addService. También puedes usar dumpsys --pid SERVICE para obtener el PID de un servicio durante la depuración.

Para agregar un resultado personalizado a tu servicio, puedes anular el método dump en tu objeto de servidor como si estuvieras implementando cualquier otro método de IPC definido en un archivo AIDL. Cuando lo hagas, debes restringir el volcado al permiso de la app android.permission.DUMP o restringir el volcado a UIDs específicos.

En el backend de Java, haz lo siguiente:

    @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 backend del NDK:

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

En el backend de Rust, cuando implementes la interfaz, especifica lo siguiente (en lugar de permitir que se establezca de forma predeterminada):

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

Obtén un descriptor de interfaz de forma dinámica

El descriptor de interfaz identifica el tipo de interfaz. Esto es útil cuando se depura o cuando se tiene un Binder desconocido.

En Java, puedes obtener el descriptor de interfaz con código como el siguiente:

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

En el backend de CPP, haz lo siguiente:

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

Los backends de NDK y Rust no admiten esta función.

Cómo obtener el descriptor de interfaz de forma estática

A veces (por ejemplo, cuando registras los servicios de @VintfStability), debes hacer lo siguiente: sepa qué es el descriptor de interfaz estáticamente. En Java, puedes obtener el descriptor si agregas código como el siguiente:

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

En el backend de CPP, haz lo siguiente:

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

En el backend de NDK (observa el espacio de nombres aidl adicional):

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

En el backend de Rust, haz lo siguiente:

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

Rango de enum

En los backends nativos, puedes iterar sobre los valores posibles que puede tomar una enumeración . Debido a consideraciones de tamaño de código, esto no es compatible con Java.

Para una enum MyEnum definida en AIDL, la iteración se proporciona de la siguiente manera.

En el backend de CPP, haz lo siguiente:

    ::android::enum_range<MyEnum>()

En el backend del NDK:

   ::ndk::enum_range<MyEnum>()

En el backend de Rust:

    MyEnum::enum_values()

Administración de subprocesos

Cada instancia de libbinder en un proceso mantiene un grupo de subprocesos. Para la mayoría de los casos de uso, debe ser exactamente un grupo de subprocesos, compartido en todos los backends. La única excepción es cuando el código del proveedor puede cargar otra copia de libbinder para comunicarse con /dev/vndbinder. Como se encuentra en un nodo de Binder independiente, el Threadpool no se comparte.

Para el backend de Java, el conjunto de subprocesos solo puede aumentar de tamaño (ya que es ya comenzó):

    BinderInternal.setMaxThreads(<new larger value>);

Para el backend de la CPP, se encuentran 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();

Del mismo modo, 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 de Rust asíncrono, necesitas dos grupos de subprocesos: Binder y Tokio. Esto significa que las apps que usan Rust asíncrono requieren consideraciones especiales en especial, cuando se trata del uso de join_thread_pool. Consulta la sección sobre cómo registrar servicios para obtener más información al respecto.

Nombres reservados

C++, Java y Rust reservan algunos nombres como palabras clave o para lenguaje usar. Si bien AIDL no aplica restricciones basadas en reglas de lenguaje, el uso de nombres de campo o tipo que coincidan con un nombre reservado puede provocar un error de compilación en C++ o Java. En Rust, se cambia el nombre del campo o tipo con la sintaxis de “identificador sin procesar”, a la que se puede acceder con el prefijo r#.

Te recomendamos que evites usar nombres reservados en tus definiciones de AIDL siempre que sea posible para evitar vinculaciones poco ergonómicas o fallas de compilación.

Si ya tienes nombres reservados en las definiciones del AIDL, puedes cambiar el nombre de los campos sin dejar de ser compatible con el protocolo; quizás debas actualizar tu código para seguir creando, pero cualquier programa ya compilado seguirá interoperar.

Nombres que debes evitar: