AIDL estable

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

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

Definición de una interfaz AIDL

Una definición de aidl_interface se ve así:

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

}
  • name : el nombre del módulo de interfaz AIDL que identifica de forma única una interfaz AIDL.
  • srcs : la lista de archivos fuente AIDL que componen la interfaz. La ruta para un Foo de tipo AIDL definido en un paquete com.acme debe estar en <base_path>/com/acme/Foo.aidl , donde <base_path> podría ser cualquier directorio relacionado con el directorio donde se encuentra Android.bp . En el ejemplo anterior, <base_path> es srcs/aidl .
  • local_include_dir : la ruta desde donde comienza el nombre del paquete. Corresponde a <base_path> explicado anteriormente.
  • imports : una lista de módulos aidl_interface que utiliza. Si una de sus interfaces AIDL usa una interfaz o un paquete de otra aidl_interface , ponga su nombre aquí. Puede ser el nombre solo, para hacer referencia a la última versión, 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 ha sido compatible desde Android 12
  • versions : las versiones anteriores de la interfaz que están congeladas en api_dir . A partir de Android 11, las versions están congeladas en aidl_api/ name . Si no hay versiones congeladas de una interfaz, esto no debe especificarse y no habrá comprobaciones de compatibilidad.
  • stability : la bandera opcional para la promesa de estabilidad de esta interfaz. Actualmente solo admite "vintf" . Si esto no está configurado, esto corresponde a una interfaz con estabilidad dentro de este contexto de compilación (por lo que una interfaz cargada aquí solo se puede usar con elementos compilados juntos, por ejemplo, en system.img). Si se establece en "vintf" , esto corresponde a una promesa de estabilidad: la interfaz debe mantenerse estable mientras se use.
  • gen_trace : el indicador opcional para activar o desactivar el rastreo. El valor predeterminado es false .
  • host_supported : el indicador opcional que, cuando se establece en true , hace que las bibliotecas generadas estén disponibles para el entorno del host.
  • unstable : el indicador 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.
  • backend.<type>.enabled : estos indicadores alternan cada uno de los backends para los que el compilador AIDL generará código. Actualmente, se admiten tres backends: java , cpp y ndk . Todos los backends están habilitados de forma predeterminada. Cuando no se necesita un backend específico, debe deshabilitarse explícitamente.
  • backend.<type>.apex_available : la lista de nombres de APEX para los que está disponible la biblioteca de código auxiliar generada.
  • backend.[cpp|java].gen_log : el indicador opcional que controla si generar código adicional para recopilar información sobre la transacción.
  • backend.[cpp|java].vndk.enabled : el indicador opcional para hacer que esta interfaz forme parte de VNDK. El valor predeterminado es false .
  • backend.java.platform_apis : el indicador opcional que controla si la biblioteca de código auxiliar de Java se crea con las API privadas de la plataforma. Esto debe establecerse en "true" cuando la stability se establece en "vintf" .
  • backend.java.sdk_version : el indicador opcional para especificar la versión del SDK con la que se crea la biblioteca de código auxiliar de Java. El valor predeterminado es "system_current" . Esto no debe establecerse cuando backend.java.platform_apis es verdadero.
  • backend.java.platform_apis : el indicador opcional que debe establecerse en true cuando las bibliotecas generadas deben compilarse con la API de la plataforma en lugar del SDK.

Para cada combinación de las versions y los backends habilitados, se crea una biblioteca de código auxiliar. Consulte las reglas de nomenclatura de módulos para saber cómo hacer referencia a la versión específica de la biblioteca de código auxiliar para un backend específico.

Escritura de archivos AIDL

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

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

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

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

Uso de bibliotecas de código auxiliar

Después de agregar bibliotecas auxiliares como dependencia a su módulo, puede incluirlas en sus 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):

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

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

Versiones de interfaces

Declarar un módulo con el nombre foo también crea un objetivo en el sistema de compilación que puede usar para administrar la API del módulo. Cuando se compila, foo-freeze-api agrega una nueva definición de API en api_dir o aidl_api/ name , según la versión de Android, y agrega un archivo .hash , ambos representan la versión recién congelada de la interfaz. Construir esto también actualiza la propiedad versions para reflejar la versión adicional. Una vez que se especifica la propiedad de las versions , el sistema de compilación ejecuta comprobaciones de compatibilidad entre las versiones congeladas y también entre Top of Tree (ToT) y la última versión congelada.

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

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

  • Métodos al final de una interfaz (o métodos con nuevas publicaciones seriadas explícitamente definidas)
  • Elementos al final de un paquete (requiere que se agregue un valor predeterminado para cada elemento)
  • Valores constantes
  • En Android 11, los enumeradores
  • En Android 12, campos al final de una unión

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

Para probar que todas las interfaces están congeladas para su lanzamiento, puede compilar con el siguiente conjunto de variables ambientales:

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

Uso de interfaces versionadas

Métodos de interfaz

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

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

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

Encomendables

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

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

Enumeraciones y constantes

Del mismo modo, los clientes y los servidores deben rechazar o ignorar los valores constantes no reconocidos y los enumeradores, 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. Debería ignorarlo o devolver algo para que el cliente sepa que no es compatible con esta implementación.

sindicatos

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

Reglas de nomenclatura de módulos

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

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

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

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

En comparación con Android 11,

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

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

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

Tenga en cuenta que el compilador AIDL no crea un módulo de versión unstable ni un módulo no versionado 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 metainterfaz

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

Consultar la versión de la interfaz del objeto remoto

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

Ejemplo con el backend cpp :

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

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

Ejemplo con el ndk (y ndk_platform ):

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

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

Ejemplo con el backend java :

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

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

Para el lenguaje Java, el lado remoto DEBE implementar getInterfaceVersion() y getInterfaceHash() de la siguiente manera:

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

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

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

Tratar con interfaces más antiguas

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

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

Ejemplo en C++ en Android T (AOSP experimental) y posteriores:

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

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

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

Ejemplo en Java:

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


foo.anAddedMethod(...);

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

Conversión de AIDL existente a AIDL estructurado/estable

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

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

  2. Convierta todos los parcelables en su interfaz en parcelables estables (los archivos de la interfaz en sí mismos pueden permanecer sin cambios). Hágalo expresando su estructura directamente en archivos AIDL. Las clases de gestión deben reescribirse para utilizar estos nuevos tipos. Esto se puede hacer antes de crear un paquete aidl_interface (abajo).

  3. Cree un paquete aidl_interface (como se describe arriba) que contenga el nombre de su módulo, sus dependencias y cualquier otra información que necesite. Para que sea estabilizado (no solo estructurado), también necesita ser versionado. Para obtener más información, consulte Versiones de interfaces .