Google se compromete a impulsar la igualdad racial para las comunidades afrodescendientes. Obtén información al respecto.

Backends de AIDL

Un backend AIDL es un objetivo para la generación de códigos de código auxiliar. Al usar archivos AIDL, siempre los usa en un idioma en particular con un tiempo de ejecución específico. Dependiendo del contexto, debe usar diferentes backends AIDL.

AIDL tiene los siguientes backends:

Backend Idioma Superficie API Construir sistemas
Java Java SDK / SystemApi (estable *) todos
NDK C ++ libbinder_ndk (estable *) aidl_interface
CPP C ++ libbinder (inestable) todos
Oxido Oxido libbinder_rs (inestable) aidl_interface
  • Estas superficies de API son estables, pero muchas de las API, como las de administración de servicios, están reservadas para el uso interno de la plataforma y no están disponibles para las aplicaciones. Para obtener más información sobre cómo utilizar AIDL de aplicaciones, consulte la documentación para desarrolladores .
  • El backend de Rust se introdujo en Android 12; el backend del NDK ha estado disponible a partir de Android 10.
  • El cajón de Rust se construye en la parte superior de libbinder_ndk . Los APEX usan la caja de carpetas de la misma manera que cualquier otra persona del lado del sistema. La porción de óxido se empaqueta en un APEX y se envía dentro de él. Depende de la libbinder_ndk.so en la partición del sistema.

Construir sistemas

Dependiendo del backend, hay dos formas de compilar AIDL en código stub. Para más detalles sobre los sistemas de construcción, ver el módulo de referencia Soong .

Sistema de construcción central

En cualquier cc_ o java_ Android.bp módulo (o en sus Android.mk equivalentes), .aidl archivos se pueden especificar como archivos de origen. En este caso, se utilizan los backends Java / CPP de AIDL (no el backend del NDK), y las clases para usar los archivos AIDL correspondientes se agregan al módulo automáticamente. Las opciones tales como local_include_dirs , que indica al sistema de construcción de la ruta raíz de AIDL archivos de ese módulo se pueden especificar en estos módulos bajo un aidl: grupo. Tenga en cuenta que el backend de Rust solo se puede usar con Rust. rust_ módulos se manejan de forma diferente en que los archivos AIDL no se especifican como archivos de origen. En cambio, el aidl_interface módulo produce una rustlib llamada <aidl_interface name>-rust que puede estar unido en contra. Para más detalles, véase el ejemplo Rust AIDL .

aidl_interface

Ver AIDL estable . Los tipos utilizados con este sistema de compilación deben estar estructurados; es decir, expresado directamente en AIDL. Esto significa que no se pueden usar parcelas personalizadas.

Tipos

Se puede considerar la aidl compilador como una implementación de referencia para los tipos. Cuando se crea una interfaz, invoke aidl --lang=<backend> ... para ver el archivo de interfaz resultante. Cuando se utiliza la aidl_interface módulo, se puede ver el resultado en la out/soong/.intermediates/<path to module>/ .

Tipo Java / AIDL Tipo C ++ Tipo de NDK Tipo de óxido
booleano bool bool bool
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
flotador flotador flotador f32
doble doble doble f64
Cuerda android :: String16 std :: cadena Cuerda
android.os.Parcelable android :: Parcelable N / A N / A
IBinder android :: IBinder ndk :: SpAIBinder aglutinante :: SpIBinder
T [] std :: vector <T> std :: vector <T> En t
Fuera: Vec <T>
byte[] std :: vector <uint8_t> std :: vector <int8_t> 1 En: & [u8]
Fuera: Vec <u8>
Lista <T> std :: vector <T> 2 std :: vector <T> 3 En: & [T] 4
Fuera: 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> aglutinante :: Fuerte
tipo parcelable (T) T T T
Tipo de unión (T) 5 T T T

1. En Android 12 o superior, las matrices de bytes usan uint8_t en lugar de int8_t por razones de compatibilidad.

2. backend El C ++ soporta List<T> donde T es uno de String , IBinder , ParcelFileDescriptor o parcelable. En Android T (experimental AOSP) o superior, T puede ser cualquier tipo no primitivo (incluyendo tipos de interfaz), excepto arrays. AOSP recomienda el uso de tipos de matriz como T[] , ya que trabajan en todos los backends.

3. El NDK backend apoya List<T> donde T es uno de String , ParcelFileDescriptor o parcelable. En Android T (experimental AOSP) o superior, T puede ser cualquier tipo no primitivo excepto arrays.

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

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

Direccionalidad (in / out / inout)

Al especificar los tipos de los argumentos de funciones, se pueden especificar 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 por defecto, e indica los datos se transmiten de la persona que llama al abonado llamado. out significa que los datos se pasa desde el destinatario de la llamada a la persona que llama. inout es la combinación de ambas cosas. Sin embargo, el equipo de Android recomienda que evite utilizar el argumento especificador inout . Si utiliza inout con una interfaz versionado y un destinatario de la llamada más antigua, los campos adicionales que están presentes sólo en la persona que llama reinicio llegar a sus valores por defecto. Con respecto a Rust, a la normalidad inout tipo recibe &mut Vec<T> , y una lista inout tipo recibe &mut Vec<T> .

UTF8 / UTF16

Con el backend de C ++, puede elegir si las cadenas son utf-8 o utf-16. Declarar como cuerdas @utf8InCpp String en AIDL que automáticamente los convierte a UTF-8. Los backends NDK y Rust siempre usan cadenas utf-8. Para obtener más información acerca de la utf8InCpp anotación, consulte Anotaciones en AIDL .

Nulabilidad

Puede anotar los tipos que pueden ser nula en Java con @nullable para exponer los valores nulos en C ++ y NDK. En estos Rust @nullable tipos se exponen como Option<T> . Los servidores nativos rechazan los valores nulos de forma predeterminada. Las únicas excepciones a esto son interface y IBinder tipos, que siempre puede ser nulo. Para obtener más información acerca de la nullable anotación, consulte Anotaciones en AIDL .

Parcelables personalizados

En los backend de C ++ y Java en el sistema de compilación principal, puede declarar un parcelable que se implementa manualmente en un backend de destino (en C ++ o en Java).

    package my.package;
    parcelable Foo;

o con declaración de encabezado C ++:

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

Entonces puede usar este parcelable como un tipo en archivos AIDL, pero no lo generará AIDL.

Rust no admite parcelables personalizados.

Valores predeterminados

Parcelables estructurados pueden declarar valores predeterminados por el campo de las primitivas, String s, y las matrices de este tipo.

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

En el back-end de Java cuando los valores por defecto faltan, 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 los valores predeterminados no están definidos. Por ejemplo, en el backend de C ++, String campos se inicializan como una cadena vacía y List<T> campos se inicializan como un vacío vector<T> . @nullable campos se inicializan como campos nulo valor.

Manejo de errores

El sistema operativo Android proporciona tipos de error 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 de AIDL y no requieren ningún estado o tipo de retorno definido por el usuario.

Si la interfaz AIDL requiere valores de error adicionales que no están cubiertos por los tipos de error incorporados, entonces pueden usar el error incorporado 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 se definen típicamente en la interfaz AIDL como un const int o int -backed enum y no se analizan por aglutinante.

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

El código nativo en Android no usa excepciones. Los usos de back-end CPP android::binder::Status . Los usos de back-end NDK ndk::ScopedAStatus . Cada método generado por AIDL devuelve uno de estos, que representa el estado del método. El backend Rust utiliza los mismos valores de código de excepción como el NDK, pero los convierte en errores de óxido nativo ( StatusCode , ExceptionCode ) antes de entregarlos al usuario. Para errores específicos del servicio, el devuelto Status o ScopedAStatus utiliza EX_SERVICE_SPECIFIC junto con el error definido por el usuario.

Los tipos de error incorporados se pueden encontrar en los siguientes archivos:

Backend Definición
Java android/os/Parcel.java
CPP binder/Status.h
NDK android/binder_status.h
Oxido 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 Rust, ver el ejemplo Rust AIDL en la Patrones Android Rust página.

Importación de tipos

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 back-end NDK (nótese el extra aidl espacio de nombres):

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

O en el backend de Rust:

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

Aunque puede importar un tipo anidado en Java, en los backends de CPP / NDK debe incluir el encabezado para su tipo raíz. Por ejemplo, al importar un tipo anidado Bar define en my/package/IFoo.aidl ( IFoo es el tipo raíz del archivo), debe incluir <my/package/IFoo.h> para el backend CPP (o <aidl/my/package/IFoo.h> para el backend NDK).

Implementación de servicios

Para implementar un servicio, debe heredar de la clase de código auxiliar nativo. Esta clase lee los comandos del controlador de enlace y ejecuta los métodos que implementa. Imagina que tienes un archivo AIDL como este:

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

En Java, debe extenderse 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 back-end NDK (nótese el extra aidl espacio de nombres):

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

Registrarse y obtener servicios

Servicios en la plataforma Android suelen ser registrados en el servicemanager proceso. Además de las API a continuación, algunas API verifican el servicio (lo que significa que regresan inmediatamente si el servicio no está disponible). Compruebe el correspondiente servicemanager interfaz para detalles exactos. Estas operaciones solo se pueden realizar al compilar contra la plataforma Android.

En Java:

    import android.os.ServiceManager;
    // registering
    ServiceManager.addService("service-name", myService);
    // getting
    myService = IFoo.Stub.asInterface(ServiceManager.getService("service-name"));
    // waiting until service comes up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForService("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = IFoo.Stub.asInterface(ServiceManager.waitForDeclaredService("service-name"));

En el backend de CPP:

    #include <binder/IServiceManager.h>
    // registering
    defaultServiceManager()->addService(String16("service-name"), myService);
    // getting
    status_t err = getService<IFoo>(String16("service-name"), &myService);
    // waiting until service comes up (new in Android 11)
    myService = waitForService<IFoo>(String16("service-name"));
    // waiting for declared (VINTF) service to come up (new in Android 11)
    myService = waitForDeclaredService<IFoo>(String16("service-name"));

En el back-end NDK (nótese el extra aidl espacio de nombres):

    #include <android/binder_manager.h>
    // registering
    status_t err = AServiceManager_addService(myService->asBinder().get(), "service-name");
    // getting
    myService = IFoo::fromBinder(SpAIBinder(AServiceManager_getService("service-name")));
    // is a service declared in the VINTF manifest
    // VINTF services have the type in the interface instance name.
    bool isDeclared = AServiceManager_isDeclared("android.hardware.light.ILights/default");
    // wait until a service is available (if isDeclared or you know it's available)
    myService = IFoo::fromBinder(SpAIBinder(AServiceManager_waitForService("service-name")));

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

Puede solicitar recibir una notificación cuando falle un servicio que aloja una carpeta. Esto puede ayudar a evitar fugas de servidores proxy de devolución de llamada o ayudar en la recuperación de errores. Realice estas llamadas en objetos de proxy de vinculador.

  • En Java, utilice android.os.IBinder::linkToDeath .
  • En el back-end CPP, el uso android::IBinder::linkToDeath .
  • En el back-end NDK, el uso AIBinder_linkToDeath .
  • En el back-end Rust, crear un DeathRecipient objeto, a continuación, llamar my_binder.link_to_death(&mut my_death_recipient) . Tenga en cuenta que debido a que el DeathRecipient posee la devolución de llamada, debe mantener ese objeto vivo el mayor tiempo que desea recibir notificaciones.

Informes de errores y API de depuración para servicios

Cuando se ejecutan informes de errores (por ejemplo, con el adb bugreport ), que recogen la información de todo el sistema para la ayuda con la depuración de varios temas. Para los servicios AIDL, informes de errores utilizan los binarios dumpsys en todos los servicios registrados con el administrador de servicios para volcar su información en el informe de error. También puede utilizar dumpsys en la línea de comandos para obtener información de un servicio con dumpsys SERVICE [ARGS] . En las ++ y Java backends C, se puede controlar el orden en que los servicios consigue descargado mediante el uso de argumentos adicionales para addService . También puede utilizar dumpsys --pid SERVICE para obtener el PID de un servicio durante la depuración.

Para agregar salida personalizado a su servicio, puede anular el dump método en el objeto de servidor, como va a implementar cualquier otro método IPC definido en un archivo AIDL. Al hacer esto, se debe restringir vertido al permiso a la aplicación android.permission.DUMP o restringir el vertido a los UID específicos.

En el backend de Java:

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

En el backend de C ++:

    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, 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 descriptor de interfaz de forma dinámica

El descriptor de interfaz identifica el tipo de interfaz. Esto es útil al depurar o cuando tiene un archivador desconocido.

En Java, puede obtener el descriptor de la interfaz con un 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 NDK y Rust no admiten esta funcionalidad.

Obteniendo estáticamente el descriptor de la interfaz

A veces (como cuando se registra @VintfStability servicios), lo que necesita saber lo que el descriptor de interfaz es estáticamente. En Java, puede 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 back-end NDK (nótese el extra aidl espacio de nombres):

    #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 asumir una enumeración. Debido a consideraciones sobre el tamaño del código, esto no es compatible con Java actualmente.

Para una enumeración MyEnum definido en AIDL, iteración se proporciona como sigue.

En el backend de CPP:

    ::android::enum_range<MyEnum>()

En el backend del NDK:

   ::ndk::enum_range<MyEnum>()

En el backend de Rust:

    MyEnum::enum_range()
````

### Thread management {:#thread-management}

Every instance of `libbinder` in a process maintains one threadpool. For most
use cases, this should be exactly one threadpool, shared across all backends. The
only exception to this is when vendor code might load another copy of `libbinder`
to talk to `/dev/vndbinder`.  Since this is on a separate binder node, the
threadpool isn't shared.

For the Java backend, the threadpool can only increase in size (since it is
already started):

BinderInternal.setMaxThreads(<new larger value>);

For the C++ backend, the following operations are available:

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

Similarly, in the NDK backend:

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

In the Rust backend:

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