AIDL estable

Android 10 agrega soporte para el lenguaje de definición de interfaz de Android (AIDL) estable, una nueva forma de realizar un seguimiento de la interfaz del programa de aplicación (API)/interfaz binaria de aplicación (ABI) proporcionada por las interfaces AIDL. AIDL estable tiene las siguientes diferencias clave con AIDL:

  • Las interfaces se definen en el sistema de compilación con aidl_interfaces .
  • Las interfaces solo pueden contener datos estructurados. Los parcelables que representan los tipos deseados se crean automáticamente en función de su definición AIDL y se ordenan y desordenan automáticamente.
  • Las interfaces se pueden declarar como estables (compatibles con versiones anteriores). Cuando esto sucede, se realiza un seguimiento de su API y se versiona en un archivo junto a la interfaz AIDL.

AIDL estructurada versus estable

AIDL estructurado se refiere a tipos definidos exclusivamente en AIDL. Por ejemplo, una declaración parcelable (un paquete personalizado) no está estructurada AIDL. Los parcelables con sus campos definidos en AIDL se denominan parcelables estructurados .

AIDL estable requiere AIDL estructurado para que el sistema de compilación y el compilador puedan comprender si los cambios realizados en los parcelables son compatibles con versiones anteriores. Sin embargo, no todas las interfaces estructuradas son estables. Para ser estable, una interfaz debe usar solo tipos estructurados y también debe usar las siguientes funciones de control de versiones. Por el contrario, una interfaz no es estable si se utiliza el sistema de compilación central para construirla o si se establece unstable:true .

Definición de una interfaz AIDL

Una definición de aidl_interface se ve así:

aidl_interface {
    name: "my-aidl",
    srcs: ["srcs/aidl/**/*.aidl"],
    local_include_dir: "srcs/aidl",
    imports: ["other-aidl"],
    versions_with_info: [
        {
            version: "1",
            imports: ["other-aidl-V1"],
        },
        {
            version: "2",
            imports: ["other-aidl-V3"],
        }
    ],
    stability: "vintf",
    backend: {
        java: {
            enabled: true,
            platform_apis: true,
        },
        cpp: {
            enabled: true,
        },
        ndk: {
            enabled: true,
        },
        rust: {
            enabled: true,
        },
    },

}
  • name : El nombre del módulo de interfaz AIDL que identifica de forma única una interfaz AIDL.
  • srcs : la lista de archivos fuente AIDL que componen la interfaz. La ruta para un Foo tipo AIDL definido en un paquete com.acme debe estar en <base_path>/com/acme/Foo.aidl , donde <base_path> podría ser cualquier directorio relacionado con el directorio donde está Android.bp . En el ejemplo anterior, <base_path> es srcs/aidl .
  • local_include_dir : la ruta desde donde comienza el nombre del paquete. Corresponde a <base_path> explicado anteriormente.
  • imports : una lista de módulos aidl_interface que utiliza. Si una de sus interfaces AIDL utiliza una interfaz o un paquete de otra aidl_interface , escriba su nombre aquí. Puede ser el nombre en sí, para hacer referencia a la última versión, o el nombre con el sufijo de la versión (como -V1 ) para hacer referencia a una versión específica. La especificación de una versión es compatible desde Android 12
  • versions : las versiones anteriores de la interfaz que están congeladas en api_dir . A partir de Android 11, las versions están congeladas en aidl_api/ name . Si no hay versiones congeladas de una interfaz, esto no se debe especificar y no habrá comprobaciones de compatibilidad. Este campo ha sido reemplazado por versions_with_info para 13 y superiores.
  • versions_with_info : Lista de tuplas, cada una de las cuales contiene el nombre de una versión congelada y una lista con importaciones de versiones de otros módulos Aidl_interface que esta versión de Aidl_interface importó. La definición de la versión V de una interfaz AIDL IFACE se encuentra en aidl_api/ IFACE / V . Este campo se introdujo en Android 13 y no debe modificarse directamente en Android.bp. El campo se agrega o actualiza invocando *-update-api o *-freeze-api . Además, los campos versions se migran automáticamente a versions_with_info cuando un usuario invoca *-update-api o *-freeze-api .
  • stability : el indicador opcional para la promesa de estabilidad de esta interfaz. Actualmente sólo es compatible con "vintf" . Si no está configurado, corresponde a una interfaz con estabilidad dentro de este contexto de compilación (por lo que una interfaz cargada aquí solo se puede usar con cosas compiladas juntas, por ejemplo en system.img). Si se establece en "vintf" , corresponde a una promesa de estabilidad: la interfaz debe mantenerse estable mientras se utilice.
  • gen_trace : el indicador opcional para activar o desactivar el seguimiento. A partir de Android 14, el valor predeterminado es true para los backends cpp y java .
  • host_supported : el indicador opcional que, cuando se establece en true , hace que las bibliotecas generadas estén disponibles para el entorno host.
  • unstable : el indicador opcional utilizado para marcar que esta interfaz no necesita ser estable. Cuando se establece en true , el sistema de compilación no crea el volcado de API para la interfaz ni requiere que se actualice.
  • frozen : el indicador opcional que, cuando se establece en true , significa que la interfaz no tiene cambios desde la versión anterior de la interfaz. Esto permite realizar más comprobaciones en tiempo de construcción. Cuando se establece en false esto significa que la interfaz está en desarrollo y tiene nuevos cambios, por lo que ejecutar foo-freeze-api generará una nueva versión y cambiará automáticamente el valor a true . Introducido en Android 14.
  • backend.<type>.enabled : estos indicadores alternan cada uno de los backends para los que el compilador AIDL genera código. Actualmente, se admiten cuatro backends: Java, C++, NDK y Rust. Los backends de Java, C++ y NDK están habilitados de forma predeterminada. Si alguno de estos tres backends no es necesario, debe desactivarse explícitamente. Rust está deshabilitado de forma predeterminada.
  • backend.<type>.apex_available : la lista de nombres APEX para los que está disponible la biblioteca de código auxiliar generada.
  • backend.[cpp|java].gen_log : el indicador opcional que controla si se genera código adicional para recopilar información sobre la transacción.
  • backend.[cpp|java].vndk.enabled : el indicador opcional para hacer que esta interfaz forme parte de VNDK. El valor predeterminado es false .
  • backend.[cpp|ndk].additional_shared_libraries : Introducido en Android 14, este indicador agrega dependencias a las bibliotecas nativas. Esta bandera es útil con ndk_header y cpp_header .
  • backend.java.sdk_version : el indicador opcional para especificar la versión del SDK con la que se construye la biblioteca auxiliar de Java. El valor predeterminado es "system_current" . Esto no debería configurarse cuando backend.java.platform_apis es verdadero.
  • backend.java.platform_apis : el indicador opcional que debe establecerse en true cuando las bibliotecas generadas deben compilarse en la API de la plataforma en lugar del SDK.

Para cada combinación de versiones y backends habilitados, se crea una biblioteca auxiliar. Para saber cómo hacer referencia a la versión específica de la biblioteca auxiliar para un backend específico, consulte Reglas de nomenclatura de módulos .

Escribir archivos AIDL

Las interfaces en AIDL estable son similares a las interfaces tradicionales, con la excepción de que no se les permite usar parcelables no estructurados (¡porque no son estables! consulte AIDL estructurado versus estable ). La principal diferencia en AIDL estable es cómo se definen los parcelables. Anteriormente, los parcelables se declaraban anticipadamente ; En AIDL estable (y por lo tanto estructurado), los campos y variables de parcelables se definen explícitamente.

// in a file like 'some/package/Thing.aidl'
package some.package;

parcelable SubThing {
    String a = "foo";
    int b;
}

Actualmente se admite un valor predeterminado (pero no es obligatorio) para boolean , char , float , double , byte , int , long y String . En Android 12, también se admiten los valores predeterminados para enumeraciones definidas por el usuario. Cuando no se especifica un valor predeterminado, se utiliza un valor vacío o similar a 0. Las enumeraciones sin un valor predeterminado se inicializan en 0 incluso si no hay un enumerador cero.

Usando bibliotecas auxiliares

Después de agregar bibliotecas auxiliares como dependencia de su módulo, puede incluirlas en sus archivos. A continuación se muestran ejemplos de bibliotecas auxiliares en el sistema de compilación ( Android.mk también se puede utilizar para definiciones de módulos heredados):

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if desire is to load a library and share
    // it among multiple users or if you only need access to constants
    static_libs: ["my-module-name-java"],
    ...
}
# or
rust_... {
    name: ...,
    rustlibs: ["my-module-name-rust"],
    ...
}

Ejemplo en C++:

#include "some/package/IFoo.h"
#include "some/package/Thing.h"
...
    // use just like traditional AIDL

Ejemplo en Java:

import some.package.IFoo;
import some.package.Thing;
...
    // use just like traditional AIDL

Ejemplo en Rust:

use aidl_interface_name::aidl::some::package::{IFoo, Thing};
...
    // use just like traditional AIDL

Interfaces de versiones

Declarar un módulo con nombre foo también crea un destino en el sistema de compilación que puede usar para administrar la API del módulo. Cuando se compila, foo-freeze-api agrega una nueva definición de API en api_dir o aidl_api/ name , dependiendo de la versión de Android, y agrega un archivo .hash , ambos representan la versión recién congelada de la interfaz. foo-freeze-api también actualiza la propiedad versions_with_info para reflejar la versión adicional y imports de la versión. Básicamente, imports en versions_with_info se copian del campo imports . Pero la última versión estable se especifica en imports en versions_with_info para la importación que no tiene una versión explícita. Una vez que se especifica la propiedad versions_with_info , el sistema de compilación ejecuta comprobaciones de compatibilidad entre las versiones congeladas y también entre Top of Tree (ToT) y la última versión congelada.

Además, debe administrar la definición de API de la versión ToT. Siempre que se actualice una API, ejecute foo-update-api para actualizar aidl_api/ name /current que contiene la definición de API de la versión ToT.

Para mantener la estabilidad de una interfaz, los propietarios pueden agregar nuevos:

  • Métodos hasta el final de una interfaz (o métodos con nuevos seriales definidos explícitamente)
  • Elementos al final de un parcelable (requiere agregar un valor predeterminado para cada elemento)
  • Valores constantes
  • En Android 11, enumeradores
  • En Android 12, campos al final de una unión

No se permiten otras acciones y nadie más puede modificar una interfaz (de lo contrario, corren el riesgo de colisionar con los cambios que realiza un propietario).

Para probar que todas las interfaces están congeladas para su lanzamiento, puede compilar con las siguientes variables ambientales configuradas:

  • AIDL_FROZEN_REL=true m ... - la compilación requiere que se congelen todas las interfaces AIDL estables que no tienen owner: campo especificado.
  • AIDL_FROZEN_OWNERS="aosp test" - la compilación requiere que todas las interfaces AIDL estables estén congeladas con el owner: campo especificado como "aosp" o "test".

Estabilidad de las importaciones

La actualización de las versiones de importaciones para versiones congeladas de una interfaz es compatible con versiones anteriores en la capa AIDL estable. Sin embargo, actualizarlos requiere actualizar todos los servidores y clientes que usan la versión anterior de la interfaz, y algunas aplicaciones pueden confundirse al mezclar diferentes versiones de tipos. Generalmente, para paquetes comunes o de solo tipos, esto es seguro porque el código ya debe estar escrito para manejar tipos desconocidos de transacciones IPC.

En el código de la plataforma Android android.hardware.graphics.common es el mayor ejemplo de este tipo de actualización de versión.

Usando interfaces versionadas

Métodos de interfaz

En tiempo de ejecución, al intentar llamar a nuevos métodos en un servidor antiguo, los nuevos clientes obtienen un error o una excepción, según el backend.

  • El backend cpp obtiene ::android::UNKNOWN_TRANSACTION .
  • El backend ndk obtiene STATUS_UNKNOWN_TRANSACTION .
  • El backend java obtiene android.os.RemoteException con un mensaje que indica que la API no está implementada.

Para conocer estrategias para manejar esto, consulte Consulta de versiones y uso de valores predeterminados .

Parcelables

Cuando se agregan nuevos campos a los parcelables, los clientes y servidores antiguos los descartan. Cuando nuevos clientes y servidores reciben parcelables antiguos, los valores predeterminados para los nuevos campos se completan automáticamente. Esto significa que se deben especificar los valores predeterminados para todos los campos nuevos en un parcelable.

Los clientes no deben esperar que los servidores utilicen los nuevos campos a menos que sepan que el servidor está implementando la versión que tiene el campo definido (consulte consultas de versiones ).

Enumeraciones y constantes

De manera similar, los clientes y servidores deben rechazar o ignorar los valores constantes y enumeradores no reconocidos según corresponda, ya que es posible que se agreguen más en el futuro. Por ejemplo, un servidor no debería abortar cuando recibe un enumerador que no conoce. Debería ignorarlo o devolver algo para que el cliente sepa que no es compatible con esta implementación.

Sindicatos

Intentar enviar una unión con un campo nuevo falla si el receptor es antiguo y no conoce el campo. La implementación nunca verá la unión con el nuevo campo. El error se ignora si se trata de una transacción unidireccional; de lo contrario, el error es BAD_VALUE (para el backend de C++ o NDK) o IllegalArgumentException (para el backend de Java). El error se recibe si el cliente envía un conjunto de unión al nuevo campo a un servidor antiguo, o cuando es un cliente antiguo que recibe la unión de un servidor nuevo.

Desarrollo basado en banderas

Las interfaces en desarrollo (no congeladas) no se pueden usar en dispositivos de lanzamiento porque no se garantiza que sean compatibles con versiones anteriores.

AIDL admite el tiempo de ejecución alternativo para estas bibliotecas de interfaz no congeladas para que el código se escriba en la última versión no congelada y aún se pueda usar en los dispositivos de lanzamiento. El comportamiento compatible con versiones anteriores de los clientes es similar al comportamiento existente y, con el respaldo, las implementaciones también deben seguir esos comportamientos. Consulte Uso de interfaces versionadas .

Bandera de compilación AIDL

El indicador que controla este comportamiento es RELEASE_AIDL_USE_UNFROZEN definido en build/release/build_flags.bzl . true significa que la versión no congelada de la interfaz se usa en tiempo de ejecución y false significa que todas las bibliotecas de las versiones no congeladas se comportan como su última versión congelada. Puede anular el indicador a true para el desarrollo local, pero debe revertirlo a false antes del lanzamiento. Normalmente el desarrollo se realiza con una configuración que tiene el indicador establecido en true .

Matriz de compatibilidad y manifiestos.

Los objetos de la interfaz del proveedor (objetos VINTF) definen qué versiones se esperan y qué versiones se proporcionan en cada lado de la interfaz del proveedor.

La mayoría de los dispositivos que no son Cuttlefish apuntan a la matriz de compatibilidad más reciente solo después de que las interfaces se congelan, por lo que no hay diferencia en las bibliotecas AIDL basadas en RELEASE_AIDL_USE_UNFROZEN .

matrices

Las interfaces propiedad de los socios se agregan a matrices de compatibilidad específicas del dispositivo o del producto a las que apunta el dispositivo durante el desarrollo. Entonces, cuando se agrega una versión nueva no congelada de una interfaz a una matriz de compatibilidad, las versiones congeladas anteriores deben permanecer durante RELEASE_AIDL_USE_UNFROZEN=false . Puede manejar esto usando diferentes archivos de matriz de compatibilidad para diferentes configuraciones RELEASE_AIDL_USE_UNFROZEN o permitiendo ambas versiones en un único archivo de matriz de compatibilidad que se usa en todas las configuraciones.

Por ejemplo, al agregar una versión 4 descongelada, use <version>3-4</version> .

Cuando la versión 4 está congelada, puede eliminar la versión 3 de la matriz de compatibilidad porque la versión 4 congelada se usa cuando RELEASE_AIDL_USE_UNFROZEN es false .

Manifiestos

En Android 15 (AOSP experimental), se introduce un cambio en libvintf para modificar los archivos de manifiesto en el momento de la compilación según el valor de RELEASE_AIDL_USE_UNFROZEN .

Los manifiestos y los fragmentos de manifiesto declaran qué versión de una interfaz implementa un servicio. Cuando se utiliza la última versión no congelada de una interfaz, el manifiesto debe actualizarse para reflejar esta nueva versión. Cuando RELEASE_AIDL_USE_UNFROZEN=false libvintf ajusta las entradas del manifiesto para reflejar el cambio en la biblioteca AIDL generada. La versión se modifica desde la versión no congelada, N , a la última versión congelada N - 1 . Por lo tanto, los usuarios no necesitan administrar múltiples manifiestos o fragmentos de manifiesto para cada uno de sus servicios.

Cambios en el cliente HAL

El código de cliente HAL debe ser compatible con cada versión congelada anterior admitida. Cuando RELEASE_AIDL_USE_UNFROZEN es false , los servicios siempre se ven como la última versión congelada o anterior (por ejemplo, llamar a nuevos métodos no congelados devuelve UNKNOWN_TRANSACTION , o los nuevos campos parcelable tienen sus valores predeterminados). Los clientes del marco de Android deben ser compatibles con versiones anteriores adicionales, pero este es un detalle nuevo para los clientes de proveedores y clientes de interfaces propiedad de socios.

Cambios en la implementación de HAL

La mayor diferencia entre el desarrollo HAL y el desarrollo basado en banderas es el requisito de que las implementaciones HAL sean compatibles con la última versión congelada para poder funcionar cuando RELEASE_AIDL_USE_UNFROZEN es false . Considerar la compatibilidad con versiones anteriores en implementaciones y código de dispositivo es un ejercicio nuevo. Consulte Uso de interfaces versionadas .

Las consideraciones de compatibilidad con versiones anteriores son generalmente las mismas para los clientes y servidores, y para el código del marco y el código del proveedor, pero existen diferencias sutiles que debe tener en cuenta, ya que ahora está implementando efectivamente dos versiones que usan el mismo código fuente. (la versión actual, no congelada).

Ejemplo: una interfaz tiene tres versiones congeladas. La interfaz se actualiza con un nuevo método. Tanto el cliente como el servicio se actualizan para utilizar la nueva biblioteca de la versión 4. Debido a que la biblioteca V4 se basa en una versión no congelada de la interfaz, se comporta como la última versión congelada, la versión 3, cuando RELEASE_AIDL_USE_UNFROZEN es false e impide el uso del nuevo método.

Cuando la interfaz está congelada, todos los valores de RELEASE_AIDL_USE_UNFROZEN usan esa versión congelada y el código que maneja la compatibilidad con versiones anteriores se puede eliminar.

Al llamar a métodos en devoluciones de llamada, debe manejar con elegancia el caso en el que se devuelve UNKNOWN_TRANSACTION . Es posible que los clientes estén implementando dos versiones diferentes de una devolución de llamada según la configuración de la versión, por lo que no puede asumir que el cliente enviará la versión más reciente y que los nuevos métodos pueden devolver esto. Esto es similar a cómo los clientes AIDL estables mantienen la compatibilidad con los servidores que se describe en Uso de interfaces versionadas .

// Get the callback along with the version of the callback
ScopedAStatus RegisterMyCallback(const std::shared_ptr<IMyCallback>& cb) override {
    mMyCallback = cb;
    // Get the version of the callback for later when we call methods on it
    auto status = mMyCallback->getInterfaceVersion(&mMyCallbackVersion);
    return status;
}

// Example of using the callback later
void NotifyCallbackLater() {
  // From the latest frozen version (V2)
  mMyCallback->foo();
  // Call this method from the unfrozen V3 only if the callback is at least V3
  if (mMyCallbackVersion >= 3) {
    mMyCallback->bar();
  }
}

Es posible que los campos nuevos en tipos existentes ( parcelable , enum , union ) no existan o contengan sus valores predeterminados cuando RELEASE_AIDL_USE_UNFROZEN es false y los valores de los campos nuevos que un servicio intenta enviar se eliminan al salir del proceso.

Los nuevos tipos agregados en esta versión descongelada no se pueden enviar ni recibir a través de la interfaz.

La implementación nunca recibe una llamada para nuevos métodos de ningún cliente cuando RELEASE_AIDL_USE_UNFROZEN es false .

Tenga cuidado de utilizar nuevos enumeradores solo con la versión en la que se introdujeron y no con la versión anterior.

Normalmente, usas foo->getInterfaceVersion() para ver qué versión está usando la interfaz remota. Sin embargo, con el soporte de versiones basado en indicadores, está implementando dos versiones diferentes, por lo que es posible que desee obtener la versión de la interfaz actual. Puede hacer esto obteniendo la versión de la interfaz del objeto actual, por ejemplo, this->getInterfaceVersion() u otros métodos para my_ver . Consulte Consultar la versión de la interfaz del objeto remoto para obtener más información.

Nuevas interfaces estables VINTF

Cuando se agrega un nuevo paquete de interfaz AIDL, no hay una última versión congelada, por lo que no hay ningún comportamiento al que recurrir cuando RELEASE_AIDL_USE_UNFROZEN es false . No utilice estas interfaces. Cuando RELEASE_AIDL_USE_UNFROZEN es false , Service Manager no permitirá que el servicio registre la interfaz y los clientes no la encontrarán.

Puede agregar los servicios condicionalmente según el valor del indicador RELEASE_AIDL_USE_UNFROZEN en el archivo MAKE del dispositivo:

ifeq ($(RELEASE_AIDL_USE_UNFROZEN),true)
PRODUCT_PACKAGES += \
    android.hardware.health.storage-service
endif

Si el servicio es parte de un proceso más grande y no puede agregarlo al dispositivo de manera condicional, puede verificar si el servicio está declarado con IServiceManager::isDeclared() . Si se declara y no se registra, cancele el proceso. Si no se declara, se espera que no se registre.

La sepia como herramienta de desarrollo

Cada año, después de que se congela el VINTF, ajustamos el target-level de la matriz de compatibilidad del marco (FCM) y el PRODUCT_SHIPPING_API_LEVEL de Cuttlefish para que reflejen los dispositivos que se lanzarán con el lanzamiento del próximo año. Ajustamos target-level y PRODUCT_SHIPPING_API_LEVEL para asegurarnos de que haya algún dispositivo de lanzamiento que esté probado y cumpla con los nuevos requisitos para el lanzamiento del próximo año.

Cuando RELEASE_AIDL_USE_UNFROZEN es true , Cuttlefish se utiliza para el desarrollo de futuras versiones de Android. Tiene como objetivo el nivel FCM de la versión de Android del próximo año y PRODUCT_SHIPPING_API_LEVEL , lo que requiere que cumpla con los requisitos de software del proveedor (VSR) de la próxima versión.

Cuando RELEASE_AIDL_USE_UNFROZEN es false , Cuttlefish tiene el target-level anterior y PRODUCT_SHIPPING_API_LEVEL para reflejar un dispositivo de liberación. En Android 14 y versiones anteriores, esta diferenciación se lograría con diferentes ramas de Git que no recogen el cambio al target-level de FCM, al nivel de API de envío ni a ningún otro código destinado a la próxima versión.

Reglas de nomenclatura de módulos

En Android 11, para cada combinación de versiones y backends habilitados, se crea automáticamente un módulo de biblioteca auxiliar. Para hacer referencia a un módulo de biblioteca auxiliar específico para vincular, no use el nombre del módulo aidl_interface , sino el nombre del módulo de biblioteca auxiliar, que es ifacename - version - backend , donde

  • ifacename : nombre del módulo aidl_interface
  • version es cualquiera de
    • V version-number para las versiones congeladas
    • V latest-frozen-version-number + 1 para la versión de la punta del árbol (aún por congelar)
  • backend es cualquiera de
    • java para el backend de Java,
    • cpp para el backend de C++,
    • ndk o ndk_platform para el backend de NDK. El primero es para aplicaciones y el segundo es para uso de plataforma.
    • rust para el backend de Rust.

Supongamos que hay un módulo con nombre foo y su última versión es 2 y es compatible con NDK y C++. En este caso, AIDL genera estos módulos:

  • Basado en la versión 1
    • foo-V1-(java|cpp|ndk|ndk_platform|rust)
  • Basado en la versión 2 (la última versión estable)
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • Basado en la versión ToT
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

En comparación con Android 11,

  • foo- backend , que hacía referencia a la última versión estable se convierte en foo- V2 - backend
  • foo-unstable- backend , que se refería a la versión ToT se convierte en foo- V3 - backend

Los nombres de los archivos de salida son siempre los mismos que los nombres de los módulos.

  • Basado en la versión 1: foo-V1-(cpp|ndk|ndk_platform|rust).so
  • Basado en la versión 2: foo-V2-(cpp|ndk|ndk_platform|rust).so
  • Basado en la versión ToT: foo-V3-(cpp|ndk|ndk_platform|rust).so

Tenga en cuenta que el compilador AIDL no crea ni un módulo de versión unstable ni un módulo sin versión para una interfaz AIDL estable. A partir de Android 12, el nombre del módulo generado desde una interfaz AIDL estable siempre incluye su versión.

Nuevos métodos de metainterfaz

Android 10 agrega varios métodos de metainterfaz para el AIDL estable.

Consultar la versión de la interfaz del objeto remoto.

Los clientes pueden consultar la versión y el hash de la interfaz que está implementando el objeto remoto y comparar los valores devueltos con los valores de la interfaz que está utilizando el cliente.

Ejemplo con el backend cpp :

sp<IFoo> foo = ... // the remote object
int32_t my_ver = IFoo::VERSION;
int32_t remote_ver = foo->getInterfaceVersion();
if (remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::HASH;
std::string remote_hash = foo->getInterfaceHash();

Ejemplo con el backend ndk (y ndk_platform ):

IFoo* foo = ... // the remote object
int32_t my_ver = IFoo::version;
int32_t remote_ver = 0;
if (foo->getInterfaceVersion(&remote_ver).isOk() && remote_ver < my_ver) {
  // the remote side is using an older interface
}

std::string my_hash = IFoo::hash;
std::string remote_hash;
foo->getInterfaceHash(&remote_hash);

Ejemplo con el backend java :

IFoo foo = ... // the remote object
int myVer = IFoo.VERSION;
int remoteVer = foo.getInterfaceVersion();
if (remoteVer < myVer) {
  // the remote side is using an older interface
}

String myHash = IFoo.HASH;
String remoteHash = foo.getInterfaceHash();

Para el lenguaje Java, el lado remoto DEBE implementar getInterfaceVersion() y getInterfaceHash() de la siguiente manera (se usa super en lugar de IFoo para evitar errores de copiar y pegar. La anotación @SuppressWarnings("static") puede ser necesaria para deshabilitar las advertencias, dependiendo de la configuración javac ):

class MyFoo extends IFoo.Stub {
    @Override
    public final int getInterfaceVersion() { return super.VERSION; }

    @Override
    public final String getInterfaceHash() { return super.HASH; }
}

Esto se debe a que las clases generadas ( IFoo , IFoo.Stub , etc.) se comparten entre el cliente y el servidor (por ejemplo, las clases pueden estar en el classpath de arranque). Cuando se comparten clases, el servidor también está vinculado a la versión más reciente de las clases, aunque se haya creado con una versión anterior de la interfaz. Si esta metainterfaz se implementa en la clase compartida, siempre devuelve la versión más reciente. Sin embargo, al implementar el método como se indicó anteriormente, el número de versión de la interfaz está incrustado en el código del servidor (porque IFoo.VERSION es un static final int que está en línea cuando se hace referencia) y, por lo tanto, el método puede devolver la versión exacta en la que se creó el servidor. con.

Tratar con interfaces más antiguas

Es posible que un cliente esté actualizado con la versión más reciente de una interfaz AIDL pero el servidor esté usando la interfaz AIDL anterior. En tales casos, llamar a un método en una interfaz antigua devuelve UNKNOWN_TRANSACTION .

Con AIDL estable, los clientes tienen más control. En el lado del cliente, puede configurar una implementación predeterminada para una interfaz AIDL. Un método en la implementación predeterminada se invoca solo cuando el método no está implementado en el lado remoto (porque se creó con una versión anterior de la interfaz). Dado que los valores predeterminados se establecen globalmente, no deben usarse desde contextos potencialmente compartidos.

Ejemplo en C++ en Android 13 y posteriores:

class MyDefault : public IFooDefault {
  Status anAddedMethod(...) {
   // do something default
  }
};

// once per an interface in a process
IFoo::setDefaultImpl(::android::sp<MyDefault>::make());

foo->anAddedMethod(...); // MyDefault::anAddedMethod() will be called if the
                         // remote side is not implementing it

Ejemplo en Java:

IFoo.Stub.setDefaultImpl(new IFoo.Default() {
    @Override
    public xxx anAddedMethod(...)  throws RemoteException {
        // do something default
    }
}); // once per an interface in a process


foo.anAddedMethod(...);

No es necesario proporcionar la implementación predeterminada de todos los métodos en una interfaz AIDL. Los métodos que se garantiza que se implementarán en el lado remoto (porque está seguro de que el control remoto se creó cuando los métodos estaban en la descripción de la interfaz AIDL) no necesitan ser anulados en la clase impl predeterminada.

Conversión de AIDL existente a AIDL estructurado/estable

Si tiene una interfaz AIDL existente y un código que la utiliza, siga los siguientes pasos para convertir la interfaz en una interfaz AIDL estable.

  1. Identifique todas las dependencias de su interfaz. Para cada paquete del que depende la interfaz, determine si el paquete está definido en AIDL estable. Si no está definido, el paquete debe convertirse.

  2. Convierta todos los elementos en su interfaz en elementos estables (los archivos de la interfaz pueden permanecer sin cambios). Haga esto expresando su estructura directamente en archivos AIDL. Las clases de gestión deben reescribirse para utilizar estos nuevos tipos. Esto se puede hacer antes de crear un paquete aidl_interface (a continuación).

  3. Cree un paquete aidl_interface (como se describe arriba) que contenga el nombre de su módulo, sus dependencias y cualquier otra información que necesite. Para estabilizarlo (no sólo estructurarlo), también es necesario versionarlo. Para obtener más información, consulte Control de versiones de interfaces .