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. AIDL estable funciona exactamente igual que AIDL, pero el sistema de compilación hace 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 objetos Parcelables que representan los tipos preferidos se crean automáticamente según su definición de AIDL y se serializan y deserializan automáticamente.
- Las interfaces se pueden declarar como estables (retrocompatibles). Cuando esto sucede, su API se rastrea y se versiona en un archivo junto a la interfaz de AIDL.
AIDL estructurado versus estable
AIDL estructurado hace referencia a los tipos definidos puramente en AIDL. Por ejemplo, una declaración de parcelable (un parcelable personalizado) no es AIDL estructurado. Los objetos Parcelable con sus campos definidos en AIDL se denominan Parcelables estructurados.
AIDL estable requiere AIDL estructurado para que el sistema de compilación y el compilador puedan comprender si los cambios realizados en los 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 la interfaz AIDL que identifica de forma única una interfaz AIDL.srcs
: Es la lista de archivos fuente de AIDL que componen la interfaz. La ruta de un tipo de AIDLFoo
definido en un paquetecom.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 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>
, como se explicó anteriormente.imports
: Es una lista de los módulos deaidl_interface
que usa este objeto. Si una de tus interfaces de AIDL usa una interfaz o un objeto parcelable de otroaidl_interface
, coloca su nombre aquí. Puede ser el nombre por sí 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. La especificación de una versión es compatible desde Android 12.versions
: Son las versiones anteriores de la interfaz que se congelan enapi_dir
. A partir de Android 11, losversions
se congelan enaidl_api/name
. Si no hay versiones congeladas de una interfaz, no se debe especificar 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 congelada y una lista con las 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
. El campo se agrega o actualiza invocando*-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 admite"vintf"
. Sistability
no está configurado, el sistema de compilación verifica que la interfaz sea compatible con versiones anteriores, a menos que se especifiqueunstable
. El valor no establecido corresponde a una interfaz con estabilidad dentro de este contexto de compilación (por lo que son todas las cosas del sistema, por ejemplo, las cosas ensystem.img
y las particiones relacionadas, o todas las cosas del proveedor, por ejemplo, las cosas envendor.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 registro. A partir de Android 14, el valor predeterminado estrue
para los backendscpp
yjava
.host_supported
: Es una 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 indicar que esta interfaz no necesita ser estable. Cuando se establece entrue
, el sistema de compilación no crea el volcado de la API para la interfaz ni requiere que se actualice.frozen
: Es una marca opcional que, cuando se establece entrue
, significa que la interfaz no ha cambiado desde la versión anterior. Esto permite más verificaciones durante la 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 o 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 código auxiliar generada.backend.[cpp|java].gen_log
: Es la marca opcional que controla si se genera código adicional para recopilar información sobre la transacción.backend.[cpp|java].vndk.enabled
: Es la marca opcional para que esta interfaz forme parte del 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 código auxiliar 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 en función de la API de la plataforma en lugar del SDK.
Para cada combinación de versiones y backends habilitados, se crea una biblioteca de código auxiliar. Para saber cómo hacer referencia a la versión específica de la biblioteca de stubs para un backend específico, consulta Reglas de nomenclatura de módulos.
Escribe 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 AIDL estable es cómo se definen los objetos Parcelable. Anteriormente, los objetos parcelables se declaraban por adelantado. En AIDL estable (y, por lo tanto, estructurado), los campos y las variables de los objetos 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 cero.
Usa bibliotecas de stubs
Después de agregar bibliotecas de código auxiliar como dependencia a tu módulo, puedes incluirlas en tus archivos. Estos son ejemplos de bibliotecas de código auxiliar 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, no se incluye la versión, por lo que se representa el uso de una interfaz inestable, pero los nombres de las interfaces con versiones incluyen información adicional. Consulta Control de versiones de interfaces.
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
, ambos representan la versión recién congelada de la interfaz. foo-freeze-api también actualiza la propiedad versions_with_info
para reflejar la versión adicional y imports
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 versión Top of Tree (ToT) y la versión congelada 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 números de serie nuevos definidos de forma explícita)
- Elementos al final de un objeto 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 conflictos con los cambios que realice un propietario).
Para probar que todas las interfaces estén congeladas 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 estables de AIDL que no tengan especificado el campoowner:
estén inmovilizadas.AIDL_FROZEN_OWNERS="aosp test"
: La compilación requiere que todas las interfaces AIDL estables se inmovilicen con el campoowner:
especificado como "aosp" o "test".
Estabilidad de las importaciones
La actualización de las versiones de las importaciones para las versiones inmovilizadas de una interfaz es compatible con versiones anteriores en la capa de AIDL estable. Sin embargo, para actualizar estos elementos, 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 los paquetes comunes o de solo tipos, esto es seguro porque el código ya debe estar escrito para controlar los tipos desconocidos de las 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 versiones
Métodos de interfaz
En el tiempo de ejecución, cuando se intenta llamar a métodos nuevos en un servidor antiguo, 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
recibeandroid.os.RemoteException
con un mensaje que indica que la API no está implementada.
Para conocer las estrategias para controlar esto, consulta cómo consultar versiones y cómo usar valores predeterminados.
Objetos Parcelables
Cuando se agregan campos nuevos a los objetos Parcelable, los clientes y servidores antiguos los descartan. Cuando los clientes y servidores nuevos reciben objetos Parcelable antiguos, los valores predeterminados para los campos nuevos se completan automáticamente. Esto significa que se deben especificar valores predeterminados para todos los campos nuevos en un objeto 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 definido el campo (consulta cómo consultar versiones).
Enums 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 debería anular la operación cuando recibe un enumerador que no conoce. El servidor debe ignorar el enumerador o devolver algo para que el cliente sepa que no se admite en 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 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 anterior que recibe la unión de un servidor nuevo.
Administra varias versiones
Un espacio de nombres del vinculador en Android solo puede tener 1 versión de una interfaz aidl
específica para evitar situaciones en las que los tipos 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 aidl_interface
. Es posible que el módulo dependa 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 desde el módulo con errores hasta 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 la interfaz, puede ser útil crear cc_defaults
, java_defaults
y rust_defaults
para cualquier grupo de bibliotecas y procesos que necesiten usar la misma versión. Cuando se introduce una nueva versión 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 diferentes versiones 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.
aidl_interfaces_defaults
se puede usar para mantener una definición de las versiones más recientes de las dependencias para un aidl_interface
que se puede actualizar en un solo lugar y que pueden usar todos los módulos aidl_interface
que deseen 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 (sin congelar) no se pueden usar en dispositivos de lanzamiento, ya que no se garantiza que sean retrocompatibles.
AIDL admite la alternativa de tiempo de ejecución para estas bibliotecas de interfaces no congeladas, de modo que el código se pueda escribir en función de la versión no congelada más reciente y se pueda seguir usando en los dispositivos de lanzamiento. El comportamiento retrocompatible de los clientes es similar al comportamiento existente y, con la alternativa, las implementaciones también deben seguir esos comportamientos. Consulta Usa interfaces con versiones.
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 en 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 y establecerla en 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 cada lado de la interfaz de proveedor.
La mayoría de los dispositivos que no son Cuttlefish solo se orientan a la matriz de compatibilidad más reciente después de que se congelan las interfaces, por lo que no hay diferencias en las bibliotecas de AIDL basadas en RELEASE_AIDL_USE_UNFROZEN
.
Matrices
Las interfaces propiedad del socio 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
. Puedes controlar esto usando diferentes archivos de matriz de compatibilidad para diferentes configuraciones de RELEASE_AIDL_USE_UNFROZEN
o permitiendo 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 no inmovilizada, usa <version>3-4</version>
.
Cuando se congela la versión 4, puedes quitar la versión 3 de la matriz de compatibilidad, ya que se usa la versión 4 congelada 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 momento de la compilación según el valor de RELEASE_AIDL_USE_UNFROZEN
.
Los manifiestos y los fragmentos de manifiesto declaran qué versión de una interfaz implementa un servicio. Cuando se usa la versión más reciente no inmovilizada de una interfaz, se debe actualizar el manifiesto para reflejar esta nueva versión. Cuando RELEASE_AIDL_USE_UNFROZEN=false
las entradas del manifiesto se ajustan con libvintf
para reflejar el cambio en la biblioteca de AIDL generada. La versión se modificó de la versión no congelada, N
, a la última versión congelada, N - 1
. Por lo tanto, los usuarios no necesitan administrar varios manifiestos o fragmentos de manifiestos 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 congelada anterior admitida. 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 devuelve UNKNOWN_TRANSACTION
, o los campos parcelable
nuevos tienen sus valores predeterminados). Los clientes del framework de Android deben ser compatibles con versiones anteriores adicionales, pero este es un detalle nuevo para los clientes del proveedor y los clientes de las interfaces propiedad del socio.
Cambios en la implementación de HAL
La mayor diferencia en el desarrollo del HAL con el desarrollo basado en marcas es el requisito de que las implementaciones del HAL sean retrocompatibles con la última versión inmovilizada para que funcionen cuando RELEASE_AIDL_USE_UNFROZEN
es false
.
Tener en cuenta la retrocompatibilidad en las implementaciones y el código del dispositivo es un ejercicio nuevo. Consulta Usa interfaces con versiones.
Las consideraciones de compatibilidad con versiones anteriores suelen ser las mismas para los clientes y los servidores, y para el código del framework y el código del proveedor, pero existen diferencias sutiles que debes tener en cuenta, ya que ahora implementas de manera efectiva dos versiones que usan el mismo código fuente (la versión actual sin congelar).
Ejemplo: Una interfaz tiene tres versiones inmovilizadas. La interfaz se actualiza con un método nuevo. Tanto el cliente como el servicio se actualizan para usar la nueva biblioteca de la versión 4. Dado que la biblioteca V4 se basa en una versión no congelada de la interfaz, se comporta como la última versión congelada, la versión 3, cuando RELEASE_AIDL_USE_UNFROZEN
es false
, y evita el uso del nuevo método.
Cuando la interfaz está congelada, todos los valores de RELEASE_AIDL_USE_UNFROZEN
usan esa versión congelada, y se puede quitar el código que controla la retrocompatibilidad.
Cuando llames a métodos en devoluciones de llamada, debes controlar correctamente el caso en el que se devuelve 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 devolver esto. Esto es similar a cómo los clientes de AIDL estables mantienen la compatibilidad con versiones anteriores con los servidores, como se describe en Usa interfaces con 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 no 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 al salir del proceso.
Los tipos nuevos agregados 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 métodos nuevos de ningún cliente cuando RELEASE_AIDL_USE_UNFROZEN
es false
.
Ten cuidado de usar los nuevos enumeradores solo con la versión en la que se introdujeron y no con la versión anterior.
Normalmente, usas foo->getInterfaceVersion()
para ver qué versión 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 la 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
es false
, Service Manager 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 make 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 declara y no se pudo registrar, se aborta el proceso. Si no se declara, se espera que falle el registro.
Nuevas interfaces de extensión estables de VINTF
Las nuevas interfaces de extensión no tienen una versión anterior a la que recurrir y, como no están registradas con ServiceManager
ni declaradas en los manifiestos de VINTF, no se puede usar IServiceManager::isDeclared()
para determinar cuándo adjuntar la interfaz de extensión a otra interfaz.
La variable RELEASE_AIDL_USE_UNFROZEN
se puede usar para determinar si se debe adjuntar la nueva interfaz de extensión no inmovilizada a la interfaz existente para evitar usarla en dispositivos lanzados. La interfaz debe estar congelada para poder usarse en dispositivos lanzados.
Las pruebas de VTS vts_treble_vintf_vendor_test
y vts_treble_vintf_framework_test
detectan cuándo se usa una interfaz de extensión no inmovilizada en un dispositivo lanzado y arrojan un error.
Si la interfaz de extensión no es nueva y tiene una versión congelada anteriormente, se recurre a esa versión congelada anteriormente y no se requieren pasos adicionales.
Cuttlefish como herramienta de desarrollo
Cada año, después de que se congela 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 la versión del próximo año.
Cuando RELEASE_AIDL_USE_UNFROZEN
es true
, Cuttlefish se usa para el desarrollo de futuras versiones de Android. Se orienta al nivel de FCM y PRODUCT_SHIPPING_API_LEVEL
del lanzamiento de Android del próximo año, por lo que debe satisfacer los Requisitos de software del proveedor (VSR) del próximo lanzamiento.
Cuando RELEASE_AIDL_USE_UNFROZEN
es false
, Cuttlefish tiene los valores anteriores de target-level
y PRODUCT_SHIPPING_API_LEVEL
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 recogen el cambio en target-level
de FCM, el nivel de API de envío ni ningún otro código que se oriente a la próxima versión.
Reglas de nombres de módulos
En Android 11, se crea automáticamente un módulo de biblioteca de código auxiliar para cada combinación de versiones y back-ends habilitados. Para hacer referencia a un módulo de biblioteca de código auxiliar específico para la vinculación, no uses el nombre del módulo aidl_interface
, sino el nombre del módulo de biblioteca de código auxiliar, que es ifacename-version-backend, donde
ifacename
: Nombre del móduloaidl_interface
version
es uno de los siguientes valores:Vversion-number
para las versiones que dejarán de actualizarseVlatest-frozen-version-number + 1
para la versión de la punta del árbol (que aún no se congeló)
backend
es uno de los siguientes valores: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 que admite tanto el NDK como C++. En este caso, 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)
- Según la versión de ToT
foo-V3-(java|cpp|ndk|ndk_platform|rust)
En comparación con Android 11, se realizaron los siguientes cambios:
foo-backend
, que hacía referencia a la versión estable más reciente, se convierte enfoo-V2-backend
.foo-unstable-backend
, que hacía referencia 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
- Según 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 metadatos
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 implementa el objeto remoto, y comparar los valores devueltos 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 el de 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 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 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 acceso de inicio de la clase). Cuando se comparten las 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 interfaz de metadatos se implementa en la clase compartida, siempre devuelve la versión más reciente. Sin embargo, si implementas el método como se indicó anteriormente, el número de versión de la interfaz se incorporará en el código del servidor (porque IFoo.VERSION
es un static final int
que se inserta cuando se hace referencia a él) y, por lo tanto, el método puede devolver la versión exacta con la que se compiló el servidor.
Cómo trabajar con interfaces anteriores
Es posible que un cliente se actualice con la versión más reciente de una interfaz AIDL, pero el servidor use la interfaz AIDL anterior. En esos casos, llamar a un método en una interfaz anterior devuelve UNKNOWN_TRANSACTION
.
Con AIDL estable, los clientes tienen más control. En el cliente, puedes establecer una implementación predeterminada para una interfaz de AIDL. Se invoca un método en la implementación predeterminada solo cuando el método 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 se deben usar 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 de AIDL. No es necesario anular los métodos que se garantiza que se implementarán en el extremo remoto (porque tienes la certeza de que el extremo remoto se compiló cuando los métodos estaban en la descripción de la interfaz de AIDL) en la clase impl
predeterminada.
Cómo convertir el AIDL existente en AIDL estructurado o estable
Si tienes una interfaz AIDL y código existentes que la usan, sigue estos pasos para convertir la interfaz en una interfaz AIDL estable.
Identifica todas las dependencias de tu interfaz. Para cada paquete del que depende la interfaz, determina si el paquete se define en AIDL estable. Si no se define, se debe convertir el paquete.
Convierte todos los objetos Parcelable de tu interfaz en objetos Parcelable estables (los archivos de la 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 nuevos tipos. Esto se puede hacer antes de crear un paquete
aidl_interface
(a continuación).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 sea estable (no solo estructurado), también debe tener versiones. Para obtener más información, consulta Control de versiones de interfaces.