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 agrupan y desagregan 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 elemento parcelable personalizado) no es 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 AIDL que identifica de forma única una interfaz AIDL.srcs
: Es la lista de archivos de origen de AIDL que componen la interfaz. La ruta para un tipo AIDLFoo
definido en un paquetecom.acme
debe estar en<base_path>/com/acme/Foo.aidl
, en la que<base_path>
puede ser cualquier directorio relacionado con el directorio en el que se encuentraAndroid.bp
. En el ejemplo anterior,<base_path>
essrcs/aidl
.local_include_dir
: Es la ruta de acceso desde la que comienza el nombre del paquete. Corresponde a<base_path>
que se explicó anteriormente.imports
: Es una lista de los módulosaidl_interface
que usa. Si una de tus interfaces AIDL usa una interfaz o un elemento parcelable de otroaidl_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 enapi_dir
. A partir de Android 11, losversions
están inmovilizados enaidl_api/name
. Si no hay versiones inmovilizadas de una interfaz, no se debe especificar esto, y no habrá verificaciones de compatibilidad. Este campo se reemplazó porversions_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 enaidl_api/IFACE/V
. Este campo se introdujo en Android 13 y no se debe modificar directamente enAndroid.bp
. Para agregar o actualizar el campo, invoca*-update-api
o*-freeze-api
. Además, los camposversions
se migran automáticamente aversions_with_info
cuando un usuario invoca*-update-api
o*-freeze-api
.stability
: Es la marca opcional para la promesa de estabilidad de esta interfaz. Solo se admite"vintf"
. Si no se establecestability
, el sistema de compilación verifica que la interfaz sea retrocompatible, a menos que se especifiqueunstable
. No establecerlo corresponde a una interfaz con estabilidad dentro de este contexto de compilación (es decir, todos los elementos del sistema, por ejemplo, los elementos desystem.img
y las particiones relacionadas, o todos los elementos del proveedor, por ejemplo, los elementos devendor.img
y las particiones relacionadas). Sistability
se establece en"vintf"
, esto corresponde a una promesa de estabilidad: la interfaz debe mantenerse estable mientras se use.gen_trace
: Es la marca opcional para activar o desactivar el seguimiento. A partir de Android 14, el valor predeterminado estrue
para los backendscpp
yjava
.host_supported
: Es la marca opcional que, cuando se establece entrue
, 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 entrue
, 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 entrue
, significa que la interfaz no tiene cambios desde la versión anterior. Esto permite más verificaciones del tiempo de compilación. Cuando se establece enfalse
, significa que la interfaz está en desarrollo y tiene cambios nuevos, por lo que ejecutarfoo-freeze-api
genera una versión nueva y cambia automáticamente el valor atrue
. 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 ninguno 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 que esta interfaz sea parte de VNDK. El valor predeterminado esfalse
.backend.[cpp|ndk].additional_shared_libraries
: Esta marca, que se introdujo en Android 14, agrega dependencias a las bibliotecas nativas. Esta marca es útil conndk_header
ycpp_header
.backend.java.sdk_version
: Es la marca opcional para especificar la versión del SDK con la que se compila la biblioteca de stub de Java. El valor predeterminado es"system_current"
. No se debe establecer cuandobackend.java.platform_apis
estrue
.backend.java.platform_apis
: Es la marca opcional que se debe establecer entrue
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 obtener información sobre 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 interfaces 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).
Ten en cuenta que, en estos ejemplos, la versión no está presente, por lo que representa el uso de una interfaz inestable, pero los nombres de las interfaces con versiones incluyen información adicional. Consulta Interfaz de control de versiones.
cc_... {
name: ...,
// use `shared_libs:` to load your library and its transitive dependencies
// dynamically
shared_libs: ["my-module-name-cpp"],
// use `static_libs:` to include the library in this binary and drop
// transitive dependencies
static_libs: ["my-module-name-cpp"],
...
}
# or
java_... {
name: ...,
// use `static_libs:` to add all jars and classes to this jar
static_libs: ["my-module-name-java"],
// use `libs:` to make these classes available during build time, but
// not add them to the jar, in case the classes are already present on the
// boot classpath (such as if it's in framework.jar) or another jar.
libs: ["my-module-name-java"],
// use `srcs:` with `-java-sources` if you want to add classes in this
// library jar directly, but you get transitive dependencies from
// somewhere else, such as the boot classpath or another jar.
srcs: ["my-module-name-java-source", ...],
...
}
# 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
Control de versiones de interfaces
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 inmovilizadas y también entre Top of Tree (ToT) y la versión inmovilizada más reciente.
Además, debes administrar la definición de la API de la versión de ToT. Cada vez que 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 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, se corre el riesgo de que se produzcan colisiones 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 campoowner:
especificado estén inmovilizadas.AIDL_FROZEN_OWNERS="aosp test"
: La compilación requiere que todas las interfaces de AIDL estables se inmovilicen con el campoowner:
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. Por lo general, para paquetes comunes o solo de tipos, esto es seguro, ya que el código ya debe estar escrito para controlar tipos desconocidos de transacciones de IPC.
En el código de la plataforma de Android, android.hardware.graphics.common
es el ejemplo más grande 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
obtieneSTATUS_UNKNOWN_TRANSACTION
. - El backend de
java
obtieneandroid.os.RemoteException
con un mensaje que indica 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 elementos parcelables antiguos, los valores predeterminados de los campos nuevos se completan automáticamente. 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 implementa 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
Si intentas enviar una unión con un campo nuevo, se producirá un error 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
. Es posible que el módulo dependa de estas bibliotecas de forma directa o indirecta a través de 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 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 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 no inmovilizada 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 reflejar 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 no inmovilizada, N
, a la última versión inmovilizada, 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 compatible. Cuando RELEASE_AIDL_USE_UNFROZEN
es false
, los servicios siempre se ven como la última versión inmovilizada o una anterior (por ejemplo, llamar 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 de proveedores y los clientes de interfaces que pertenecen 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 inmovilizada para que funcionen cuando RELEASE_AIDL_USE_UNFROZEN
sea false
.
Considerar la retrocompatibilidad en las implementaciones y el código del dispositivo es un nuevo ejercicio. Consulta Cómo usar 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 podrían mostrar esto. Esto es similar a la forma en que los clientes estables de AIDL mantienen la retrocompatibilidad con los servidores que se describe en Cómo usar interfaces con versión.
// 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 agregan en esta versión descongelada no se pueden enviar ni recibir a través de la interfaz.
La implementación nunca recibe una llamada de 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. 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 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 una última versión inmovilizada, por lo que no hay un 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 registró, aborta 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 haya algún dispositivo de lanzamiento que se pruebe y 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 y PRODUCT_SHIPPING_API_LEVEL
de la versión de Android del próximo año, lo que requiere que satisfaga 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
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 detecten el cambio a FCM target-level
, el nivel de API de envío ni ningún 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óduloaidl_interface
.version
es uno de los siguientes:Vversion-number
para las versiones que dejarán de actualizarseVlatest-frozen-version-number + 1
para la versión de la punta del árbol (aún no congelada)
backend
es uno de los siguientes:java
para el backend de Javacpp
para el backend de C++,ndk
ondk_platform
para el backend del NDK El primero es para apps y el segundo es para el uso de la plataforma hasta Android 13. En Android 13 y versiones posteriores, solo usandk
.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:
- Según 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)
- Según la versión de ToT
foo-V3-(java|cpp|ndk|ndk_platform|rust)
En comparación con Android 11, se incluyen las siguientes novedades:
foo-backend
, que se refería a la versión estable más reciente, se convierte enfoo-V2-backend
.foo-unstable-backend
, que se refería a la versión de ToT, se convierte enfoo-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 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.
Cómo consultar la versión de la interfaz del objeto remoto
Los clientes pueden consultar la versión y el hash de la interfaz que implementa el objeto remoto 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 acceso 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, cuando se implementa el método como se indicó anteriormente, 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 se intercala 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. No es necesario anular los métodos que se garantizan que se implementarán en el lado remoto (porque tienes la certeza de que el control remoto se compila cuando los métodos estaban en la descripción de la interfaz AIDL) en la clase impl
predeterminada.
Convierte el AIDL existente en AIDL estructurado o estable
Si tienes una interfaz de AIDL existente y un código que la usa, sigue los pasos que se indican a continuación para convertirla en una interfaz de AIDL estable.
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 se define, se debe convertir el paquete.
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 los archivos AIDL. Las clases de administración deben reescribirse para usar estos tipos nuevos. Esto se puede hacer antes de crear un paquete
aidl_interface
(más abajo).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.