Backends de AIDL

Un backend de AIDL es un destino para la generación de código de stub. Siempre usa archivos AIDL en un lenguaje particular con un tiempo de ejecución específico. Según el contexto, debes usar diferentes back-ends de AIDL.

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

AIDL tiene los siguientes backends:

Backend Idioma Superficie de la API Sistemas de compilación
Java Java SDK o SystemApi (estable*) Todos
NDK C++ libbinder_ndk (estable*) aidl_interface
CPP C++ libbinder (inestable) Todos
Rust Rust libbinder_rs (estable*) aidl_interface
  • Estas superficies de API son estables, pero muchas de las APIs, como las de administración de servicios, están reservadas para el uso interno de la plataforma y no están disponibles para las apps. Si quieres obtener más información para usar AIDL en apps, consulta Android Interface Definition Language (AIDL).
  • El backend de Rust se introdujo en Android 12, mientras que el backend del NDK está disponible desde Android 10.
  • El crate de Rust se basa en libbinder_ndk, lo que le permite ser estable y portátil. Los APEX usan el crate de Binder de la manera estándar en el lado del sistema. La parte de Rust se incluye en un APEX y se envía dentro de él. Esta parte depende de libbinder_ndk.so en la partición del sistema.

Sistemas de compilación

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

Sistema de compilación principal

En cualquier cc_ o java_ Android.bp module (o en sus equivalentes de Android.mk), puedes especificar archivos AIDL (.aidl) como archivos de origen. En este caso, se usan los backends de Java o C++ de AIDL (no el backend del NDK), y las clases para usar los archivos de AIDL correspondientes se agregan al módulo automáticamente. Puedes especificar 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) en estos módulos en un grupo aidl:.

El backend de Rust solo se puede usar con Rust. Los módulos rust_ se controlan de manera diferente, ya que los archivos AIDL no se especifican como archivos fuente. En su lugar, el módulo aidl_interface produce un rustlib llamado aidl_interface_name-rust, que se puede vincular. Para obtener más detalles, consulta el ejemplo de AIDL de Rust.

aidl_interface

Los tipos que se usan con el sistema de compilación aidl_interface deben estar estructurados. Para que sean estructurados, los objetos Parcelable deben contener campos directamente y no ser declaraciones de tipos definidos directamente en los lenguajes de destino. Para saber cómo encaja el AIDL estructurado con el AIDL estable, consulta AIDL estructurado versus estable.

Tipos

Considera el compilador de aidl como una implementación de referencia para los tipos. Cuando creas una interfaz, invoca 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 o AIDL Tipo de C++ Tipo de NDK Tipo de óxido
boolean bool bool bool
byte8 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 Entrada: &str
Salida: String
android.os.Parcelable android::Parcelable N/A N/A
IBinder android::IBinder ndk::SpAIBinder binder::SpIBinder
T[] std::vector<T> std::vector<T> Entrada: &[T]
Salida: Vec<T>
byte[] std::vector std::vector1 Entrada: &[u8]
Salida: Vec<u8>
List<T> std::vector<T>2 std::vector<T>3 In: In: &[T]4
Out: Vec<T>
FileDescriptor android::base::unique_fd N/A N/A
ParcelFileDescriptor android::os::ParcelFileDescriptor ndk::ScopedFileDescriptor binder::parcel::ParcelFileDescriptor
Tipo de interfaz (T) android::sp<T> std::shared_ptr<T>7 binder::Strong
Tipo Parcelable (T) T T T
Tipo de unión (T)5 T T T
T[N]6 std::array<T, N> std::array<T, N> [T; N]

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

2. El backend de C++ admite List<T>, donde T es uno de los siguientes: 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 usar tipos de array como T[], ya que funcionan en todos los backends.

3. El backend del NDK admite List<T>, donde T es uno de los siguientes: 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 de Rust, según si son de entrada (un argumento) o de salida (un valor devuelto).

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

6. En Android 13 y versiones posteriores, se admiten arrays de tamaño fijo. Los arrays de tamaño fijo pueden tener varias dimensiones (por ejemplo, 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 de vinculador SharedRefBase, usa SharedRefBase::make\<My\>(... args ...). Esta función crea un objeto std::shared_ptr\<T\>, que también se administra de forma interna, en caso de que el vinculador sea propiedad de otro proceso. Crear el objeto de otras formas genera una doble propiedad.

8. Consulta también el tipo byte[] de Java o AIDL.

Direccionalidad (in, out y inout)

Cuando especifiques los tipos de los argumentos para las funciones, puedes especificarlos como in, out o inout. Controla la dirección en la que se pasa la información para una llamada de IPC.

  • El especificador de argumentos in indica que los datos se pasan del llamador al llamado. El especificador in es la dirección predeterminada, pero, si los tipos de datos también pueden ser out, debes especificar la dirección.

  • El especificador de argumentos out significa que los datos se pasan del destinatario al llamador.

  • El especificador de argumentos inout es la combinación de ambos. Sin embargo, te recomendamos que evites usar el especificador de argumentos inout. Si usas inout con una interfaz versionada 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 T, y 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);
}

UTF-8 y UTF-16

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

Nulabilidad

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

Parcelables personalizados

Un elemento Parcelable personalizado es un elemento Parcelable que se implementa de forma manual en un backend de destino. Usa parcelables personalizados solo cuando intentes agregar compatibilidad con otros idiomas para un parcelable personalizado existente que no se pueda cambiar.

Este es un ejemplo de una declaración de AIDL parcelable:

    package my.pack.age;
    parcelable Foo;

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

Para declarar un objeto parcelable de backend de C++ personalizado en AIDL, usa cpp_header:

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

La implementación en 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 objeto parcelable personalizado del NDK 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 declarar un objeto parcelable personalizado de Rust 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 objeto Parcelable como un tipo en los archivos AIDL, pero AIDL no lo generará. Proporciona operadores < y == para CPP y NDK backend custom parcelables para usarlos en union.

Valores predeterminados

Los objetos Parcelables estructurados pueden declarar valores predeterminados por campo para tipos primitivos, campos String y arrays 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 los tipos primitivos y null para los tipos no primitivos.

En otros backends, los campos se inicializan con valores inicializados predeterminados cuando no se definen valores predeterminados. 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.

Sindicatos

Las uniones de AIDL están etiquetadas y sus características son similares en todos los backends. Se construyen según el valor predeterminado del primer campo y tienen una forma específica del idioma para interactuar con ellos:

    union Foo {
      int intField;
      long longField;
      String stringField;
      MyParcelable parcelableField;
      ...
    }

Ejemplo de Java

    Foo u = Foo.intField(42);              // construct

    if (u.getTag() == Foo.intField) {      // tag query
      // use u.getIntField()               // getter
    }

    u.setStringField("abc");               // setter

Ejemplo de C++ y NDK

    Foo u;                                            // default constructor

    assert (u.getTag() == Foo::intField);             // tag query
    assert (u.get<Foo::intField>() == 0);             // getter

    u.set<Foo::stringField>("abc");                   // setter

    assert (u == Foo::make<Foo::stringField>("abc")); // make<tag>(value)

Ejemplo de Rust

En Rust, las uniones se implementan como enumeraciones y no tienen métodos get y set explícitos.

    let mut u = Foo::Default();              // default constructor
    match u {                                // tag match + get
      Foo::IntField(x) => assert!(x == 0);
      Foo::LongField(x) => panic!("Default constructed to first field");
      Foo::StringField(x) => panic!("Default constructed to first field");
      Foo::ParcelableField(x) => panic!("Default constructed to first field");
      ...
    }
    u = Foo::StringField("abc".to_string()); // set

Manejo de errores

El SO Android proporciona tipos de errores integrados para que los servicios los usen cuando informen errores. Los enlazadores los usan y cualquier servicio que implemente una interfaz de enlazador puede usarlos. Su uso está bien documentado en la definición de AIDL y no requieren ningún estado ni tipo de devolución definidos por el usuario.

Parámetros de salida con errores

Cuando una función de 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 se pueden modificar si el error se produce durante el desempaquetado, en lugar de ocurrir durante el procesamiento de la transacción en sí. En general, cuando se produce un error en una función de AIDL, todos los parámetros inout y out, así como el valor de retorno (que actúa como un parámetro out en algunos backends), deben considerarse en 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 EX_ILLEGAL_ARGUMENT se pueden usar cuando describen la condición de error, pero no se debe usar EX_TRANSACTION_FAILED porque la infraestructura subyacente lo trata de forma especial. Consulta las definiciones específicas del backend para obtener más información sobre estos valores integrados.

Si la interfaz de AIDL requiere valores de error adicionales que no se incluyen en los tipos de error integrados, puede usar el error integrado especial específico del servicio que permite la inclusión de un valor de error específico del servicio definido por el usuario. Por lo general, estos errores específicos del servicio se definen en la interfaz de 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. En el caso de las excepciones específicas del servicio, Java usa android.os.ServiceSpecificException junto con el error definido por el usuario.

El código nativo en Android no usa excepciones. El backend de C++ usa android::binder::Status. El backend del NDK usa ndk::ScopedAStatus. Cada método generado por AIDL devuelve 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, el Status o el ScopedAStatus devuelto usa 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
Rust android/binder_status.h

Usa varios backends

Estas instrucciones son específicas del 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 de AIDL de Rust en patrones de Rust en Android.

Tipos de importación

Independientemente de si el tipo definido es una interfaz, un objeto parcelable o una unión, puedes importarlo en Java:

import my.package.IFoo;

O en el backend de C++:

#include <my/package/IFoo.h>

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

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

O en el backend de Rust:

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

Si bien puedes importar un tipo anidado en Java, en los backends de C++ y 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 C++ (o <aidl/my/package/IFoo.h> para el backend del NDK).

Cómo implementar una interfaz

Para implementar una interfaz, debes heredar de la clase stub nativa. Una implementación de una interfaz suele denominarse servicio cuando se registra con el administrador de servicios o android.app.ActivityManager, y se denomina devolución de llamada cuando la registra un cliente de un servicio. Sin embargo, se usan varios nombres para describir las implementaciones de interfaces según el uso exacto. La clase de código auxiliar 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 extenderte desde la clase Stub generada:

    import my.package.IFoo;
    public class MyFoo extends IFoo.Stub {
        @Override
        int doFoo() { ... }
    }

En el backend de CPP, haz lo siguiente:

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

En el backend del 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, haz lo siguiente:

    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

Por lo general, los servicios en la plataforma Android se registran con el proceso servicemanager. Además de las siguientes APIs, algunas APIs verifican el servicio (es decir, muestran un resultado de inmediato si el servicio no está disponible). Consulta la interfaz servicemanager correspondiente para obtener detalles exactos. Solo puedes realizar estas operaciones cuando compilas 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, 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 del 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, haz lo siguiente:

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 de Rust asíncrono, con un tiempo de ejecución de un solo subproceso, sucede 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 run on this thread.
    std::future::pending().await
}

Una diferencia importante con respecto a las otras opciones es que no llamas a join_thread_pool cuando usas Rust asíncrono y un tiempo de ejecución de un solo subproceso. Esto se debe a que debes proporcionarle a Tokio un subproceso en el que pueda ejecutar las tareas secundarias. En el siguiente ejemplo, el subproceso principal cumple ese propósito. Todas las tareas secundarias creadas con tokio::spawn se ejecutan en el subproceso principal.

En el backend de Rust asíncrono, con un entorno de ejecución de subprocesos múltiples, 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 de Tokio de subprocesos múltiples, las tareas secundarias no se ejecutan en el subproceso principal. Por lo tanto, tiene más sentido llamar a join_thread_pool en el subproceso principal para que este no esté inactivo. Debes unir la llamada en block_in_place para salir del contexto asíncrono.

Puedes solicitar que se te notifique cuando falle un servicio que aloja un binder. Esto puede ayudar a evitar la filtración de proxies de devolución de llamada o ayudar en la recuperación de errores. Realiza estas llamadas en objetos proxy de Binder.

  • En Java, usa android.os.IBinder::linkToDeath.
  • En el backend de C++, usa android::IBinder::linkToDeath.
  • En el backend del NDK, usa AIBinder_linkToDeath.
  • En el backend de Rust, crea un objeto DeathRecipient y, luego, llama a my_binder.link_to_death(&mut my_death_recipient). Ten en cuenta que, como DeathRecipient posee 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 del binder del kernel, la información del llamador está disponible en varias APIs. El ID de proceso (PID) hace referencia al ID de proceso de Linux del proceso que envía una transacción. El ID de usuario (UI) hace referencia al ID de usuario de Linux. Cuando se recibe una llamada unidireccional, el PID de la llamada es 0. Fuera de un contexto de transacción de Binder, estas funciones devuelven 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, haz lo siguiente:

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

En el backend del NDK, haz lo siguiente:

    ... = 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();

API de depuración y de informes de errores para servicios

Cuando se ejecutan informes de errores (por ejemplo, con adb bugreport), se recopila información de todo el sistema para ayudar a depurar varios problemas. En el caso de los servicios de AIDL, los informes de errores usan el archivo binario dumpsys 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 comandos 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 vuelcan los servicios con argumentos adicionales para addService. También puedes usar dumpsys --pid SERVICE para obtener el PID de un servicio durante la depuración.

Para agregar una salida personalizada a tu servicio, anula el método dump en el objeto del servidor como si estuvieras implementando cualquier otro método de IPC definido en un archivo AIDL. Cuando lo hagas, restringe el volcado al permiso de la app android.permission.DUMP o 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, haz lo siguiente:

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

En el backend del NDK, haz lo siguiente:

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

Usa punteros débiles

Puedes mantener una referencia débil a un objeto de vinculador.

Si bien Java admite WeakReference, no admite referencias de vinculador débiles en la capa nativa.

En el backend de C++, el tipo débil es wp<IFoo>.

En el backend del NDK, usa ScopedAIBinder_Weak:

#include <android/binder_auto_utils.h>

AIBinder* binder = ...;
ScopedAIBinder_Weak myWeakReference = ScopedAIBinder_Weak(AIBinder_Weak_new(binder));

En el backend de Rust, usa WpIBinder o Weak<IFoo>:

let weak_interface = myIface.downgrade();
let weak_binder = myIface.as_binder().downgrade();

Obtén de forma dinámica el descriptor de la interfaz

El descriptor de interfaz identifica el tipo de interfaz. Esto es útil cuando depuras o cuando tienes un vinculador desconocido.

En Java, puedes obtener el descriptor de la 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 back-ends de NDK y Rust no admiten esta capacidad.

Obtiene de forma estática el descriptor de la interfaz

A veces (por ejemplo, cuando registras servicios de @VintfStability), necesitas saber cuál es el descriptor de la interfaz de forma estática. En Java, puedes obtener el descriptor agregando 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 del 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 enumeración

En los backends nativos, puedes iterar sobre los posibles valores que puede tomar una enumeración. Debido a consideraciones sobre el tamaño del código, esto no se admite en Java.

Para una enumeración 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, haz lo siguiente:

   ::ndk::enum_range<MyEnum>()

En el backend de Rust, haz lo siguiente:

    MyEnum::enum_values()

Administración de subprocesos

Cada instancia de libbinder en un proceso mantiene un grupo de subprocesos. En la mayoría de los casos de uso, debería haber exactamente un grupo de subprocesos, compartido en todos los backends. La única excepción es si el código del proveedor carga otra copia de libbinder para comunicarse con /dev/vndbinder. Esto se encuentra en un nodo de vinculador independiente, por lo que no se comparte el grupo de subprocesos.

En el caso del backend de Java, el grupo de subprocesos solo puede aumentar de tamaño (porque ya se inició):

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

Del mismo modo, en el backend del NDK:

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

En el backend de Rust, haz lo siguiente:

    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 el registro de servicios para obtener más información.

Nombres reservados

C++, Java y Rust reservan algunos nombres como palabras clave o para uso específico del lenguaje. Si bien AIDL no aplica restricciones basadas en reglas de lenguaje, usar nombres de campos o tipos que coincidan con un nombre reservado puede provocar un error de compilación para C++ o Java. En Rust, el campo o el tipo se cambian de nombre con la sintaxis del 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 no ergonómicas o fallas de compilación directas.

Si ya tienes nombres reservados en tus definiciones de AIDL, puedes cambiar el nombre de los campos de forma segura y seguir siendo compatible con el protocolo. Es posible que debas actualizar tu código para seguir compilando, pero los programas ya compilados seguirán interoperando.

Nombres que debes evitar: