AIDL estable

Android 10 agrega compatibilidad con el lenguaje de definición de la interfaz de Android (AIDL) estable, una nueva forma de realizar un seguimiento de la interfaz de programa de aplicaciones (API)/interfaz binaria de la aplicación (ABI) que proporcionan las interfaces del AIDL. El AIDL estable tiene las siguientes diferencias clave respecto del AIDL:

  • Las interfaces se definen en el sistema de compilación con aidl_interfaces.
  • Las interfaces solo pueden contener datos estructurados. Los objetos parcelables que representan los tipos deseados se crean automáticamente según su definición del AIDL y se ordenan y no se agrupan 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 controla su versión en un archivo junto a la interfaz del AIDL.

AIDL estructurado versus estable

El AIDL estructurado se refiere a los tipos definidos completamente en AIDL. Por ejemplo, una declaración parcelable (un parcelable personalizado) no es un AIDL estructurado. Los objetos parcelables con sus campos definidos en el AIDL se denominan parcelables estructurados.

El AIDL estable requiere un AIDL estructurado para que el sistema de compilación y el compilador puedan comprender si los cambios realizados en objetos parcelables son retrocompatibles. Sin embargo, no todas las interfaces estructuradas son estables. Para ser estable, una interfaz debe usar solo tipos estructurados y, también, las siguientes funciones de control de versiones. Por el contrario, una interfaz no es estable si se usa el sistema de compilación principal para compilarla o si se configura unstable:true.

Cómo definir una interfaz de AIDL

Una definición de aidl_interface se ve de la siguiente manera:

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: Es el nombre del módulo de la interfaz del AIDL que identifica de forma única una interfaz del AIDL.
  • srcs: Es la lista de archivos de origen del AIDL que componen la interfaz. La ruta de acceso para un tipo de AIDL Foo 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 en el que se encuentra Android.bp. En el ejemplo anterior, <base_path> es srcs/aidl.
  • local_include_dir: Es la ruta desde donde comienza el nombre del paquete. Corresponde a <base_path> que se explicó anteriormente.
  • imports: Es una lista de los módulos aidl_interface que usa. Si una de tus interfaces del AIDL usa una interfaz o un elemento parcelable de otro aidl_interface, escribe su nombre aquí. Puede ser el nombre en sí mismo, para hacer referencia a la versión más reciente, o el nombre con el sufijo de la versión (como -V1) para hacer referencia a una versión específica. Se admite la especificación de una versión desde Android 12
  • versions: Las versiones anteriores de la interfaz que se inmovilizaron en api_dir. A partir de Android 11, el versions se bloquea en aidl_api/name. Si no hay versiones congeladas de una interfaz, esto no se debe especificar, y no habrá verificaciones de compatibilidad. Este campo se reemplazó por versions_with_info para 13 y versiones posteriores.
  • 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 de aidl_interface que importó esta versión de aidl_interface. La definición de la versión V de una IFACE de la interfaz del AIDL se encuentra en aidl_api/IFACE/V. Este campo se introdujo en Android 13 y se supone que no se debe modificar directamente en Android.bp. El campo se agrega o actualiza mediante la invocación de *-update-api o *-freeze-api. Además, los campos versions se migran de forma automática a versions_with_info cuando un usuario invoca a *-update-api o *-freeze-api.
  • stability: Es la marca opcional de la promesa de estabilidad de esta interfaz. Por el momento, esta opción solo admite "vintf". Si no se configura stability, el sistema de compilación verifica que la interfaz sea retrocompatible, a menos que se especifique unstable. Si no se configura, corresponde a una interfaz con estabilidad dentro de este contexto de compilación (por lo tanto, todos los elementos del sistema, por ejemplo, los de system.img y las particiones relacionadas, o todos los elementos del proveedor, por ejemplo, los de vendor.img y las particiones relacionadas). Si stability se configura como "vintf", esto corresponde a una promesa de estabilidad: la interfaz se debe mantener estable mientras se usa.
  • gen_trace: La marca 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: La marca opcional que, cuando se establece en true, pone a disposición las bibliotecas generadas para el entorno de host.
  • unstable: Es la marca opcional que se usa 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: La marca opcional que indica que cuando se establece en true significa que la interfaz no presenta cambios desde la versión anterior. Esto permite más verificaciones durante el tiempo de compilación. Cuando se establece en false, significa que la interfaz está en desarrollo y tiene cambios nuevos, por lo que ejecutar foo-freeze-api generará una versión nueva y cambiará automáticamente el valor a true. Se introdujo en Android 14.
  • backend.<type>.enabled: Estas marcas activan o desactivan cada uno de los backends para los que el compilador de 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 no se necesita alguno de estos tres backends, se debe inhabilitar de forma explícita. Rust está inhabilitado de forma predeterminada hasta Android 15 (experimental de AOSP).
  • backend.<type>.apex_available: Es la lista de nombres de APEX para la que está disponible la biblioteca de stubs generada.
  • backend.[cpp|java].gen_log: Es la marca opcional que controla si se debe generar un código adicional para recopilar información sobre la transacción.
  • backend.[cpp|java].vndk.enabled: Es la marca opcional para hacer que esta interfaz forme parte del VNDK. El valor predeterminado es false.
  • backend.[cpp|ndk].additional_shared_libraries: Esta marca se introdujo en Android 14, agrega dependencias a las bibliotecas nativas. Esta marca es útil con ndk_header y cpp_header.
  • backend.java.sdk_version: Es la marca opcional para especificar la versión del SDK en la que se compila la biblioteca de stubs de Java. El valor predeterminado es "system_current". No se debe configurar cuando backend.java.platform_apis es verdadero.
  • backend.java.platform_apis: Es la marca opcional que se debe establecer en true cuando las bibliotecas generadas deben compilar con la API de la plataforma en lugar del SDK.

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

Escribe archivos AIDL

Las interfaces en AIDL estable son similares a las tradicionales, con la excepción de que no pueden usar objetos parcelables no estructurados (porque no son estables, consulta AIDL estructurado versus estable). La principal diferencia en el AIDL estable es cómo se definen los objetos parcelables. Anteriormente, los objetos parcelables se declaraban a futuro; en el AIDL estable (y, por lo tanto, estructurado), los campos y las variables de parcelables se definen de forma explícita.

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

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

Por el momento, 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 las enumeraciones definidas por el usuario. Cuando no se especifica un valor predeterminado, se usa un valor similar a 0 o vacío. Las enumeraciones sin un valor predeterminado se inicializan en 0 incluso si no hay un enumerador cero.

Cómo usar bibliotecas de stubs

Después de agregar bibliotecas de stub como una dependencia a tu módulo, puedes incluirlas en tus archivos. Estos son ejemplos de bibliotecas de stub en el sistema de compilación (Android.mk también se puede usar para las 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 del control de versiones

La declaración de un módulo con el nombre foo también crea un objetivo en el sistema de compilación que puedes 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, según la versión de Android, y agrega un archivo .hash, que representa 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 para la versión. En esencia, imports en versions_with_info se copia del campo imports. Sin embargo, 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 verificaciones de compatibilidad entre las versiones congeladas y también entre la parte superior del árbol (ToT) y la versión sin actualizar más reciente.

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

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

  • Métodos para el final de una interfaz (o métodos con nuevos números de serie definidos de manera explícita)
  • Elementos hasta el final de un elemento parcelable (requiere que se agregue un valor predeterminado para cada elemento)
  • Valores constantes
  • En Android 11, los enumeradores
  • En Android 12, los campos al final de una unión

No se permiten otras acciones y nadie más puede modificar una interfaz (de lo contrario, corre el riesgo de entrar en conflicto con los cambios que realice un propietario).

Para probar que todas las interfaces estén bloqueadas para el lanzamiento, puedes compilar con el siguiente conjunto de variables de entorno:

  • AIDL_FROZEN_REL=true m ...: La compilación requiere que se inmovilicen todas las interfaces estables de AIDL que no tengan un campo owner: especificado.
  • AIDL_FROZEN_OWNERS="aosp test": La compilación requiere que todas las interfaces estables del AIDL se inmovilicen con el campo owner: especificado como "aosp" o "test".

Estabilidad de las importaciones

La actualización de las versiones de importaciones para las versiones congeladas de una interfaz es retrocompatible en la capa estable del AIDL. Sin embargo, para actualizarlas, es necesario actualizar todos los servidores y clientes que usan la versión anterior de la interfaz, y algunas aplicaciones pueden confundirse cuando se mezclan diferentes versiones de tipos. En general, para paquetes comunes o de solo tipos, esto es seguro porque el código ya debe estar escrito para controlar tipos desconocidos de transacciones de IPC.

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

Cómo usar interfaces con control de versiones

Métodos de interfaz

Durante el tiempo de ejecución, cuando se intenta llamar a métodos nuevos en un servidor anterior, los clientes nuevos reciben 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 dice que la API no está implementada.

Si deseas ver estrategias para controlar esto, consulta cómo consultar versiones y cómo usar los valores predeterminados.

Objetos parcelables

Cuando se agregan campos nuevos a objetos parcelables, los clientes y servidores antiguos los descartan. Cuando los clientes y servidores nuevos reciben objetos parcelables antiguos, los valores predeterminados de los campos nuevos se completan de forma automática. Esto significa que los valores predeterminados deben especificarse para todos los campos nuevos.

Los clientes no deberían esperar que los servidores usen los campos nuevos, a menos que sepan que el servidor está implementando la versión que tiene el campo definido (consulta cómo consultar versiones).

Enumeraciones y constantes

Del mismo modo, los clientes y servidores deben rechazar o ignorar los valores constantes y los enumeradores no reconocidos según corresponda, ya que se podrían agregar más en el futuro. Por ejemplo, un servidor no debe anularse cuando recibe un enumerador que no conoce. Debe ignorarse o mostrar algo para que el cliente sepa que no es compatible en esta implementación.

Uniones

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 campo nuevo. La falla se ignora si es 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 campo nuevo a un servidor anterior o cuando es un cliente antiguo que recibe la unión de un servidor nuevo.

Desarrollo basado en marcas

Las interfaces en desarrollo (descongeladas) no se pueden usar en dispositivos de lanzamiento, ya que no se garantiza que sean retrocompatibles.

El AIDL admite el resguardo del tiempo de ejecución para estas bibliotecas de interfaces no inmovilizadas, de modo que el código se escriba en la última versión no inmovilizada y se siga usando en dispositivos de lanzamiento. El comportamiento retrocompatible de los clientes es similar al comportamiento existente y, con el resguardo, las implementaciones también deben seguir esos comportamientos. Consulta Usa interfaces con control de versiones.

marca de compilación del AIDL

La marca que controla este comportamiento se define como RELEASE_AIDL_USE_UNFROZEN en build/release/build_flags.bzl. true significa que la versión no inmovilizada de la interfaz se usa en el tiempo de ejecución, y false significa que todas las bibliotecas de las versiones no inmovilizadas se comportan como su última versión sin actualizar. Puedes anular la marca true para el desarrollo local, pero debes revertirla a false antes del lanzamiento. Por lo general, el desarrollo se realiza con una configuración que tiene la marca establecida en true.

Matriz de compatibilidad y manifiestos

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

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

Matrices

Las interfaces que son propiedad del socio se agregan a matrices de compatibilidad específicas del dispositivo o del producto a las que se orienta el dispositivo durante el desarrollo. Por lo tanto, cuando se agrega una versión nueva y no inmovilizada de una interfaz a una matriz de compatibilidad, las versiones sin actualizar anteriores deben permanecer en RELEASE_AIDL_USE_UNFROZEN=false. A fin de controlar esto, usa diferentes archivos de matriz de compatibilidad para distintas configuraciones de RELEASE_AIDL_USE_UNFROZEN o permite ambas versiones en un único archivo de matriz de compatibilidad que se usa en todas las configuraciones.

Por ejemplo, cuando agregues una versión 4 no inmovilizada, usa <version>3-4</version>.

Cuando la versión 4 está bloqueada, puedes quitar la versión 3 de la matriz de compatibilidad porque la versión sin actualizar 4 se usa cuando RELEASE_AIDL_USE_UNFROZEN es false.

Manifiestos

En Android 15 (experimental de AOSP), se introdujo un cambio en libvintf para modificar los archivos de manifiesto en el tiempo de compilación en función del valor de RELEASE_AIDL_USE_UNFROZEN.

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

Cambios en el cliente de HAL

El código del cliente de HAL debe ser retrocompatible con todas las versiones sin actualizar compatibles anteriores. Cuando RELEASE_AIDL_USE_UNFROZEN es false, los servicios siempre se ven como la última versión sin actualizar o antes (por ejemplo, la llamada a métodos nuevos no inmovilizados muestra UNKNOWN_TRANSACTION o los campos parcelable nuevos tienen sus valores predeterminados). Los clientes del framework de Android deben ser retrocompatibles con versiones anteriores adicionales, pero este es un detalle nuevo para los clientes proveedores y de interfaces pertenecientes a socios.

Cambios en la implementación de HAL

La mayor diferencia en el desarrollo de HAL con el desarrollo basado en marcas es el requisito de que las implementaciones de HAL sean retrocompatibles con la última versión sin actualizar para funcionar cuando RELEASE_AIDL_USE_UNFROZEN sea false. La consideración de la retrocompatibilidad en las implementaciones y el código del dispositivo es un ejercicio nuevo. Consulta Usa interfaces con control de versiones.

Las consideraciones de retrocompatibilidad suelen ser las mismas para los clientes y los servidores, y para el código del framework y del proveedor, pero hay diferencias sutiles que debes tener en cuenta, ya que ahora implementas de manera eficaz dos versiones que usan el mismo código fuente (la versión actual no inmovilizada).

Ejemplo: Una interfaz tiene tres versiones congeladas. La interfaz se actualiza con un método nuevo. El cliente y el servicio se actualizan para usar la nueva biblioteca de la versión 4. Debido a que la biblioteca V4 se basa en una versión no inmovilizada de la interfaz, se comporta como la última versión sin actualizar, cuando RELEASE_AIDL_USE_UNFROZEN es false, y evita el uso del método nuevo.

Cuando se bloquea la interfaz, todos los valores de RELEASE_AIDL_USE_UNFROZEN usan esa versión inmovilizada, y se puede quitar el código que controla la retrocompatibilidad.

Cuando llames a métodos en devoluciones de llamada, deberás manejar correctamente el caso cuando se muestre 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 lanzamiento, por lo que no puedes suponer que el cliente enviará la versión más reciente, y los métodos nuevos podrían mostrar esto. Esto es similar a cómo los clientes estables de AIDL mantienen la retrocompatibilidad con los servidores, lo que se describe en Cómo usar interfaces con control de versiones.

// 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 los 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 descartan antes de que se complete el proceso.

Los tipos nuevos que se agregan en esta versión no inmovilizada 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.

Ten cuidado de usar enumeradores nuevos solo con la versión en la que se presentaron y no con la versión anterior.

Por lo general, usas foo->getInterfaceVersion() para ver qué versión usa la interfaz remota. Sin embargo, con la compatibilidad con el control de versiones basado en marcas, implementas dos versiones diferentes, por lo que es posible que desees obtener la versión de la interfaz actual. Para ello, obtén la versión de interfaz del objeto actual, por ejemplo, this->getInterfaceVersion() o los otros métodos para my_ver. Consulta Consulta la versión de interfaz del objeto remoto para obtener más información.

Nuevas interfaces estables de VINTF

Cuando se agrega un nuevo paquete de interfaz de AIDL, no hay última versión sin actualizar, por lo que no hay comportamiento al que recurrir cuando RELEASE_AIDL_USE_UNFROZEN es false. No uses estas interfaces. Cuando RELEASE_AIDL_USE_UNFROZEN es false, el Administrador de servicios no permitirá que el servicio registre la interfaz y los clientes no la encontrarán.

Puedes agregar los servicios de forma condicional según el valor de la marca RELEASE_AIDL_USE_UNFROZEN en el archivo makefile 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, por lo que no puedes agregarlo al dispositivo de forma condicional, puedes verificar si el servicio está declarado con IServiceManager::isDeclared(). Si se declaró y no se pudo registrar, anula el proceso. Si no está declarado, se espera que no se registre.

Cuttlefish como herramienta de desarrollo

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

Cuando RELEASE_AIDL_USE_UNFROZEN es true, Cuttlefish se usa para desarrollar versiones futuras de Android. Se orienta al nivel de FCM de Android del próximo año y a PRODUCT_SHIPPING_API_LEVEL, por lo que debe cumplir con los requisitos de software para proveedores (VSR) de la próxima versión.

Cuando RELEASE_AIDL_USE_UNFROZEN es false, Cuttlefish tiene el target-level y el PRODUCT_SHIPPING_API_LEVEL anteriores para reflejar un dispositivo de lanzamiento. En Android 14 y versiones anteriores, esta diferenciación se lograría con diferentes ramas de Git que no detectan el cambio a target-level de FCM, el nivel de API de envío ni ningún otro código orientado 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 de stub. A fin de hacer referencia a un módulo de biblioteca de stub específico para la vinculación, no uses el nombre del módulo aidl_interface, sino el nombre del módulo de la biblioteca de stub, que es ifacename-version-backend, en el que

  • ifacename: Es el nombre del módulo aidl_interface.
  • version es cualquiera de las siguientes opciones:
    • Vversion-number para las versiones sin actualizar
    • Vlatest-frozen-version-number + 1 para la versión de punta del árbol (que aún está congelada)
  • backend es cualquiera de las siguientes opciones:
    • java para el backend de Java,
    • cpp para el backend de C++,
    • ndk o ndk_platform para el backend del NDK. La primera es para apps y la segunda para el uso de la plataforma,
    • rust para el backend de Rust

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

  • Basado en la versión 1
    • foo-V1-(java|cpp|ndk|ndk_platform|rust)
  • Basado en la versión 2 (la versión estable más reciente)
    • foo-V2-(java|cpp|ndk|ndk_platform|rust)
  • Basado en la versión de ToT
    • foo-V3-(java|cpp|ndk|ndk_platform|rust)

En comparación con Android 11,

  • foo-backend, que hace referencia a la última versión estable, se convierte en foo-V2-backend
  • foo-unstable-backend, que hace referencia a la versión del ToT, se convierte en foo-V3-backend.

Los nombres del archivo 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
  • Según la versión de ToT: foo-V3-(cpp|ndk|ndk_platform|rust).so

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

Nuevos métodos de metainterfaz

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

Consulta la versión de la interfaz del objeto remoto

Los clientes pueden consultar la versión y el hash de la interfaz que el objeto remoto está implementando y comparar los valores que se muestran con los valores de la interfaz que usa 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. Es posible que se necesite la anotación @SuppressWarnings("static") para inhabilitar las advertencias, según la configuración de 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 la ruta de clase de inicio). Cuando se comparten las clases, el servidor también se vincula con la versión más reciente de las clases, aunque se podría haber compilado con una versión anterior de la interfaz. Si esta metainterfaz se implementa en la clase compartida, siempre muestra la versión más reciente. Sin embargo, si implementas el método como se muestra más arriba, el número de versión de la interfaz se incorpora en el código del servidor (porque IFoo.VERSION es un static final int que está intercalado cuando se hace referencia a él) y, por lo tanto, el método puede mostrar la versión exacta con la que se compiló el servidor.

Cómo trabajar con interfaces más antiguas

Es posible que un cliente esté actualizado con la versión más reciente de una interfaz de AIDL, pero el servidor use la interfaz del AIDL anterior. En esos casos, si llamas a un método en una interfaz antigua, se muestra UNKNOWN_TRANSACTION.

Con un AIDL estable, los clientes tienen más control. En el cliente, puedes establecer una implementación predeterminada en una interfaz de AIDL. Un método en la implementación predeterminada se invoca solo cuando no se implementa en el lado remoto (porque se compiló con una versión anterior de la interfaz). Dado que los valores predeterminados se establecen de forma global, no deben usarse desde contextos potencialmente compartidos.

Ejemplo en C++ en Android 13 y versiones 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 necesitas proporcionar la implementación predeterminada de todos los métodos en una interfaz de AIDL. Los métodos que están garantizados para implementarse en el lado remoto (porque estás seguro de que el control remoto se compiló cuando los métodos estaban en la descripción de la interfaz del AIDL) no necesitan anularse en la clase impl predeterminada.

Conversión del AIDL existente en AIDL estructurado/estable

Si ya tienes una interfaz de AIDL y un código que la usa, sigue estos pasos para convertirla en una interfaz de AIDL estable.

  1. Identifica todas las dependencias de tu interfaz. Para cada paquete del que depende la interfaz, determina si el paquete se define en un AIDL estable. Si no está definido, el paquete debe convertirse.

  2. Convertir todos los objetos parcelables de tu interfaz en objetos parcelables estables (los archivos de interfaz pueden permanecer sin cambios). Para ello, expresa su estructura directamente en archivos AIDL. Se deben reescribir las clases de administración para usar estos nuevos tipos. Esto puedes hacer antes de crear un paquete aidl_interface (a continuación).

  3. Crea un paquete aidl_interface (como se describió anteriormente) que contenga el nombre de tu módulo, sus dependencias y cualquier otra información que necesites. Para que se pueda estabilizar (no solo estructurado), también se necesita un control de versiones. Para obtener más información, consulta Interfaces de control de versiones.