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 programación de aplicaciones (API) y la interfaz binaria de la aplicación (ABI) que proporcionan las interfaces de AIDL. El AIDL estable funciona exactamente como el AIDL, pero el sistema de compilación realiza un seguimiento de la compatibilidad de la interfaz y existen restricciones sobre lo que puedes hacer:

  • Las interfaces se definen en el sistema de compilación con aidl_interfaces.
  • Las interfaces solo pueden contener datos estructurados. Los elementos parcelables que representan los tipos preferidos se crean automáticamente según su definición de AIDL y se organizan y desorganizan automáticamente.
  • Las interfaces se pueden declarar como estables (retrocompatibles). Cuando esto sucede, se realiza un seguimiento de su API y se le asigna una versión en un archivo junto a la interfaz de AIDL.

AIDL estructurado frente a estable

AIDL estructurado hace referencia a los tipos definidos solo en AIDL. Por ejemplo, una declaración parcelable (un parcelable personalizado) no es un AIDL estructurado. Los elementos parcelables con sus campos definidos en AIDL se denominan elementos parcelables estructurados.

El AIDL estable requiere AIDL estructurado para que el sistema de compilación y el compilador puedan comprender si los cambios realizados en los 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 debe usar 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 interfaz de AIDL que identifica de forma única una interfaz de AIDL.
  • srcs: Es la lista de archivos de origen de AIDL que componen la interfaz. La ruta para un tipo AIDL Foo definido en un paquete com.acme debe estar en <base_path>/com/acme/Foo.aidl, donde <base_path> puede 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 AIDL usa una interfaz o un elemento parcelable de otro aidl_interface, escribe su nombre aquí. Puede ser el nombre solo para hacer referencia a la versión más reciente, o el nombre con el sufijo de versión (como -V1) para hacer referencia a una versión específica. Especificar una versión es compatible desde Android 12.
  • versions: Son las versiones anteriores de la interfaz que están inmovilizadas en api_dir. A partir de Android 11, los versions están inmovilizados 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 Android 13 y versiones posteriores.
  • versions_with_info: Es una lista de tuplas, cada una de las cuales contiene el nombre de una versión inmovilizada 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 interfaz IFACE de AIDL se encuentra en aidl_api/IFACE/V. Este campo se introdujo en Android 13 y no se debe modificar directamente en Android.bp. Para agregar o actualizar el campo, invoca *-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: Es la marca opcional para la promesa de estabilidad de esta interfaz. Esto solo es compatible con "vintf". Si no se establece 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 ejemplo, todos los elementos del sistema, por ejemplo, los elementos en system.img y las particiones relacionadas, o todos los elementos del proveedor, por ejemplo, los elementos en vendor.img y 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: Es 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: Es la marca opcional que, cuando se establece en true, hace que las bibliotecas generadas estén disponibles para el entorno del 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: Es la marca opcional que, cuando se establece en true, significa que la interfaz no tiene 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 genera una versión nueva y cambia automáticamente el valor a true. Se introdujo en Android 14.
  • backend.<type>.enabled: Estas marcas activan y desactivan cada uno de los backends para los que el compilador de AIDL genera código. 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.
  • backend.<type>.apex_available: Es la lista de nombres de APEX para los que está disponible la biblioteca de stub generada.
  • backend.[cpp|java].gen_log: Es la marca opcional que controla si se debe generar 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, que 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 establecer cuando backend.java.platform_apis es true.
  • backend.java.platform_apis: Es la marca opcional que se debe establecer en true cuando las bibliotecas generadas deben compilarse con la API de la plataforma en lugar del SDK.

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

Cómo escribir 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 la forma en que se definen los elementos parcelables. Anteriormente, los objetos parcelables se declaraban de forma anticipada. En el AIDL estable (y, por lo tanto, estructurado), los campos y las variables 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;
}

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 vacío o similar a 0. Las enumeraciones sin un valor predeterminado se inicializan en 0, incluso si no hay un enumerador de cero.

Usa bibliotecas de stub

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 definiciones de módulos heredados):

cc_... {
    name: ...,
    shared_libs: ["my-module-name-cpp"],
    ...
}
# or
java_... {
    name: ...,
    // can also be shared_libs if your preference 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

Declarar un módulo con el nombre foo también crea un destino 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 inmovilizada 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. Básicamente, imports en versions_with_info se copia del campo imports. Sin embargo, la versión estable más reciente se especifica en imports en versions_with_info para la importación, que no tiene una versión explícita. Después de especificar 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 la API de la versión de ToT. Cada vez que se actualiza 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 lo siguiente:

  • Métodos al final de una interfaz (o métodos con seriales nuevos definidos de forma explícita)
  • Elementos al 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 inmovilizadas para el lanzamiento, puedes compilar con las siguientes variables de entorno configuradas:

  • AIDL_FROZEN_REL=true m ...: La compilación requiere que todas las interfaces de AIDL estables que no tengan un campo owner: especificado estén inmovilizadas.
  • 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 inmovilizadas de una interfaz es retrocompatible en la capa de AIDL estable. Sin embargo, para actualizarlos, se deben actualizar todos los servidores y clientes que usan una versión anterior de la interfaz, y es posible que algunas apps se confundan 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.

Usa 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 de cpp obtiene ::android::UNKNOWN_TRANSACTION.
  • El backend de ndk obtiene STATUS_UNKNOWN_TRANSACTION.
  • El backend java obtiene android.os.RemoteException con un mensaje que dice que la API no está implementada.

Para conocer las estrategias que permiten controlar esto, consulta Cómo consultar versiones y Cómo usar valores predeterminados.

Objetos parcelables

Cuando se agregan campos nuevos a los elementos parcelables, los clientes y servidores anteriores 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 se deben especificar los valores predeterminados para todos los campos nuevos en un elemento parcelable.

Los clientes no deben 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 los servidores deben rechazar o ignorar los valores constantes y los enumeradores no reconocidos según corresponda, ya que es posible que se agreguen más en el futuro. Por ejemplo, un servidor no debe abortar cuando recibe un enumerador que no conoce. El servidor debe ignorar el enumerador o mostrar 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 campo nuevo. La falla 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). Se recibe el error si el cliente envía una unión establecida en el campo nuevo a un servidor anterior o cuando se trata de un cliente anterior que recibe la unión de un servidor nuevo.

Administra varias versiones

Un espacio de nombres del vinculador en Android puede tener solo 1 versión de una interfaz aidl específica para evitar situaciones en las que los tipos de aidl generados tengan varias definiciones. C++ tiene la Regla de definición única que requiere solo una definición de cada símbolo.

La compilación de Android proporciona un error cuando un módulo depende de diferentes versiones de la misma biblioteca de aidl_interface. El módulo puede depender de estas bibliotecas de forma directa o indirecta a través de las dependencias de sus dependencias. Estos errores muestran el gráfico de dependencias del módulo con errores a las versiones en conflicto de la biblioteca aidl_interface. Todas las dependencias deben actualizarse para incluir la misma versión (por lo general, la más reciente) de estas bibliotecas.

Si muchos módulos diferentes usan la biblioteca de interfaz, puede ser útil crear cc_defaults, java_defaults y rust_defaults para cualquier grupo de bibliotecas y procesos que deban usar la misma versión. Cuando se presenta una versión nueva de la interfaz, se pueden actualizar esos valores predeterminados y todos los módulos que los usan se actualizan juntos, lo que garantiza que no usen versiones diferentes de la interfaz.

cc_defaults {
  name: "my.aidl.my-process-group-ndk-shared",
  shared_libs: ["my.aidl-V3-ndk"],
  ...
}

cc_library {
  name: "foo",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

cc_binary {
  name: "bar",
  defaults: ["my.aidl.my-process-group-ndk-shared"],
  ...
}

Cuando los módulos aidl_interface importan otros módulos aidl_interface, se crean dependencias adicionales que requieren que se usen versiones específicas en conjunto. Esta situación puede ser difícil de administrar cuando hay módulos aidl_interface comunes que se importan en varios módulos aidl_interface que se usan juntos en los mismos procesos.

Se puede usar aidl_interfaces_defaults para mantener una definición de las versiones más recientes de las dependencias de un aidl_interface que se puede actualizar en un solo lugar y que pueden usar todos los módulos aidl_interface que quieran importar esa interfaz común.

aidl_interface_defaults {
  name: "android.popular.common-latest-defaults",
  imports: ["android.popular.common-V3"],
  ...
}

aidl_interface {
  name: "android.foo",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

aidl_interface {
  name: "android.bar",
  defaults: ["my.aidl.latest-ndk-shared"],
  ...
}

Desarrollo basado en marcas

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

AIDL admite el resguardo de tiempo de ejecución para estas bibliotecas de interfaz no inmovilizadas para que el código se escriba en la versión más reciente no inmovilizada y aún se use 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 Cómo usar interfaces con versión.

Marca de compilación de AIDL

La marca que controla este comportamiento es RELEASE_AIDL_USE_UNFROZEN, que se define en build/release/build_flags.bzl. true significa que se usa la versión no inmovilizada de la interfaz durante el tiempo de ejecución, y false significa que las bibliotecas de las versiones no inmovilizadas se comportan como su última versión inmovilizada. Puedes anular la marca a 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 a ambos lados de la interfaz de proveedor.

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

Matrices

Las interfaces que pertenecen a los socios se agregan a las 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 sin inmovilizar de una interfaz a una matriz de compatibilidad, las versiones inmovilizadas anteriores deben permanecer para RELEASE_AIDL_USE_UNFROZEN=false. Para ello, puedes usar diferentes archivos de matriz de compatibilidad para diferentes configuraciones de RELEASE_AIDL_USE_UNFROZEN o permitir ambas versiones en un solo archivo de matriz de compatibilidad que se use en todas las configuraciones.

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

Cuando la versión 4 esté inmovilizada, puedes quitar la versión 3 de la matriz de compatibilidad, ya que la versión inmovilizada 4 se usa cuando RELEASE_AIDL_USE_UNFROZEN es false.

Manifiestos

En Android 15, se introduce un cambio en libvintf para modificar los archivos de manifiesto en el tiempo de compilación según el valor de RELEASE_AIDL_USE_UNFROZEN.

Los manifiestos y los fragmentos de manifiestos declaran qué versión de una interfaz implementa un servicio. Cuando se usa la versión más reciente de una interfaz sin inmovilizar, se debe actualizar el manifiesto para que refleje esta versión nueva. Cuando RELEASE_AIDL_USE_UNFROZEN=false, libvintf ajusta las entradas del manifiesto para reflejar el cambio en la biblioteca de AIDL generada. La versión se modifica de la versión sin actualizar, N, a la última versión sin actualizar, N - 1. Por lo tanto, los usuarios no necesitan administrar varios manifiestos o 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 cada versión anterior inmovilizada que se admita. Cuando RELEASE_AIDL_USE_UNFROZEN es false, los servicios siempre se ven como la última versión sin actualizar o una anterior (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 que el requisito de que las implementaciones de HAL sean retrocompatibles con la última versión sin actualizar cuando RELEASE_AIDL_USE_UNFROZEN sea false. Considerar la retrocompatibilidad en las implementaciones y el código del dispositivo es un nuevo ejercicio. Consulta Usa interfaces con control de versiones.

Por lo general, las consideraciones de retrocompatibilidad son las mismas para los clientes y los servidores, y para el código del framework y el código 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 sin inmovilizar).

Ejemplo: Una interfaz tiene tres versiones inmovilizadas. 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 inmovilizada, la versión 3, cuando RELEASE_AIDL_USE_UNFROZEN es false, y evita el uso del método nuevo.

Cuando la interfaz está inmovilizada, 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, debes controlar de forma fluida el caso en el que se muestra UNKNOWN_TRANSACTION. Es posible que los clientes implementen dos versiones diferentes de una devolución de llamada según la configuración de la versión, por lo que no puedes suponer que el cliente envía la versión más reciente, y los métodos nuevos pueden mostrar esto. Esto es similar a cómo los clientes estables de AIDL mantienen la retrocompatibilidad con los servidores, como 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 sea false y los valores de los campos nuevos que un servicio intenta enviar se descarten al salir del proceso.

Los tipos nuevos que se agregaron 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 métodos nuevos 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 presentan, 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 te recomendamos que obtengas la versión de la interfaz actual. Puedes hacerlo si obtienes la versión de interfaz del objeto actual, por ejemplo, this->getInterfaceVersion() o los otros métodos para my_ver. Consulta Cómo consultar la versión de la 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 sea 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 makefile del dispositivo:

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

Si el servicio forma parte de un proceso más grande, por lo que no puedes agregarlo al dispositivo de forma condicional, puedes verificar si el servicio se declara con IServiceManager::isDeclared(). Si se declaró y no se pudo registrar, anula el proceso. Si no se declara, se espera que no se registre.

Cuttlefish como herramienta de desarrollo

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

Cuando RELEASE_AIDL_USE_UNFROZEN es true, se usa Cuttlefish para el desarrollo de futuras versiones 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 FCM target-level, el nivel de API de envío ni cualquier otro código orientado a la próxima versión.

Reglas de nombres de los módulos

En Android 11, para cada combinación de las versiones y los backend habilitados, se crea automáticamente un módulo de biblioteca de stub. Para 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 biblioteca de stub, que es ifacename-version-backend, donde

  • ifacename: Es el nombre del módulo aidl_interface.
  • version es uno de los siguientes:
    • Vversion-number para las versiones congeladas
    • Vlatest-frozen-version-number + 1 para la versión de punta de árbol (que aún está congelada)
  • backend es uno de los siguientes:
    • java para el backend de Java
    • cpp para el backend de C++,
    • ndk o ndk_platform para el backend del NDK El primero es para apps y el segundo es para uso de la plataforma hasta Android 13. En Android 13 y versiones posteriores, solo usa ndk.
    • rust para el backend de Rust.

Supongamos que hay un módulo con el nombre foo y su versión más reciente 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 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 se refería a la versión de ToT, se convierte en foo-V3-backend.

Los nombres de los archivos de salida siempre son 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 versión para una interfaz de AIDL estable. A partir de Android 12, el nombre del módulo generado a partir de una interfaz AIDL estable siempre incluye su versión.

Nuevos métodos de interfaz de meta

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

En el caso del lenguaje Java, el extremo remoto DEBE implementar getInterfaceVersion() y getInterfaceHash() de la siguiente manera (se usa super en lugar de IFoo para evitar errores de copia y pegado). 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 clases, el servidor también se vincula con la versión más reciente de las clases, aunque se haya 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 lidiar con interfaces más antiguas

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

Con un AIDL estable, los clientes tienen más control. En el lado del cliente, puedes configurar una implementación predeterminada en una interfaz AIDL. Un método en la implementación predeterminada solo se invoca cuando no está implementado 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 es necesario que proporciones la implementación predeterminada de todos los métodos en una interfaz 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.

Convierte el AIDL existente en AIDL estructurado o 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 está definido en AIDL estable. Si no está definido, el paquete debe convertirse.

  2. Convierte todos los elementos parcelables de tu interfaz en elementos 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 se puede hacer antes de crear un paquete aidl_interface (más abajo).

  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 estabilize (no solo se estructure), también debe tener control de versiones. Para obtener más información, consulta Interfaces de control de versiones.