Proveedor APEX

Puedes usar el formato de archivo APEX para empaquetar e instalar módulos del SO Android de nivel inferior. Permite la compilación y la instalación independientes de componentes como bibliotecas y servicios nativos, implementaciones de HAL, firmware, archivos de configuración, etcétera.

El sistema de compilación instala automáticamente los APEX del proveedor en la partición /vendor y apexd los activa durante el tiempo de ejecución, al igual que los APEX en otras particiones.

Casos de uso

Modularización de imágenes de proveedores

Los APEX facilitan la agrupación y modularización naturales de las implementaciones de funciones en las imágenes del proveedor.

Cuando las imágenes del proveedor se compilan como una combinación de APEX del proveedor compilados de forma independiente, los fabricantes de dispositivos pueden elegir fácilmente las implementaciones específicas del proveedor que desean en su dispositivo. Los fabricantes incluso pueden crear un nuevo APEX de proveedor si ninguno de los APEX proporcionados se ajusta a sus necesidades o si tienen un hardware personalizado nuevo.

Por ejemplo, un OEM puede elegir componer su dispositivo con el APEX de implementación de Wi-Fi de AOSP, el APEX de implementación de Bluetooth del SoC y un APEX de implementación de telefonía OEM personalizado.

Sin APEX del proveedor, una implementación con tantas dependencias entre los componentes del proveedor requiere una coordinación y un seguimiento cuidadosos. Cuando se unen todos los componentes (incluidos los archivos de configuración y las bibliotecas adicionales) en APEX con interfaces claramente definidas en cualquier punto de comunicación entre funciones, los diferentes componentes se vuelven intercambiables.

Iteración del desarrollador

Los APEX de proveedores ayudan a los desarrolladores a iterar más rápido mientras desarrollan módulos de proveedores, ya que agrupan una implementación de funciones completa, como el HAL de Wi-Fi, dentro de un APEX de proveedor. Luego, los desarrolladores pueden compilar y enviar de forma individual el APEX del proveedor para probar los cambios, en lugar de volver a compilar toda la imagen del proveedor.

Esto simplifica y acelera el ciclo de iteración de los desarrolladores que trabajan principalmente en un área de funciones y quieren iterar solo en esa área.

La agrupación natural de un área de atributos en un APEX también simplifica el proceso de compilación, envío y prueba de cambios para esa área de funciones. Por ejemplo, la reinstalación de un APEX actualiza automáticamente cualquier biblioteca agrupada o archivos de configuración que este incluya.

Agrupar un área de funciones en un APEX también simplifica la depuración o la reversión cuando se observa un comportamiento inadecuado del dispositivo. Por ejemplo, si la telefonía funciona mal en una compilación nueva, los desarrolladores podrían intentar instalar una implementación de telefonía APEX anterior en un dispositivo (sin necesidad de escribir en la memoria flash una compilación completa) y ver si se restablece el comportamiento correcto.

Ejemplo de flujo de trabajo:

# Build the entire device and flash. OR, obtain an already-flashed device.
source build/envsetup.sh && lunch oem_device-userdebug
m
fastboot flashall -w

# Test the device.
... testing ...

# Check previous behavior using a vendor APEX from one week ago, downloaded from
# your continuous integration build.
... download command ...
adb install <path to downloaded APEX>
adb reboot
... testing ...

# Edit and rebuild just the APEX to change and test behavior.
... edit APEX source contents ...
m <apex module name>
adb install out/<path to built APEX>
adb reboot
... testing ...

Ejemplos

Conceptos básicos

Consulta la página principal Formato de archivo APEX para obtener información genérica sobre APEX, incluidos los requisitos del dispositivo, los detalles del formato de archivo y los pasos de instalación.

En Android.bp, configurar la propiedad vendor: true hace que un módulo APEX sea un APEX del proveedor.

apex {
  ..
  vendor: true,
  ..
}

Objetos binarios y bibliotecas compartidas

Un APEX incluye dependencias transitivas dentro de la carga útil de APEX, a menos que tengan interfaces estables.

Las interfaces nativas estables para dependencias de APEX del proveedor incluyen cc_library con bibliotecas stubs y LLNDK. Estas dependencias se excluyen del empaquetado y se registran en el manifiesto de APEX. linkerconfig procesa el manifiesto para que las dependencias nativas externas estén disponibles en el entorno de ejecución.

En el siguiente fragmento, APEX contiene el objeto binario (my_service) y sus dependencias no estables (archivos *.so).

apex {
  ..
  vendor: true,
  binaries: ["my_service"],
  ..
}

En el siguiente fragmento, APEX contiene la biblioteca compartida my_standalone_lib y cualquiera de sus dependencias no estables (como se describió anteriormente).

apex {
  ..
  vendor: true,
  native_shared_libs: ["my_standalone_lib"],
  ..
}

Cómo reducir APEX

Es posible que APEX aumente de tamaño porque agrupa dependencias inestables. Te recomendamos que uses la vinculación estática. Las bibliotecas comunes, como libc++.so y libbase.so, pueden vincularse de forma estática a los objetos binarios de HAL. Crear una dependencia para proporcionar una interfaz estable puede ser otra opción. La dependencia no se incluirá en el APEX.

Implementaciones de HAL

Para definir una implementación de HAL, proporciona los objetos binarios y las bibliotecas correspondientes dentro de un APEX del proveedor similar a los siguientes ejemplos:

Para encapsular por completo la implementación de HAL, APEX también debe especificar cualquier fragmento y secuencia de comandos de init de VINTF relevante.

Fragmentos de VINTF

Los fragmentos de VINTF se pueden entregar desde un APEX del proveedor cuando los fragmentos se encuentran en etc/vintf del APEX.

Usa la propiedad prebuilts para incorporar los fragmentos de VINTF en el APEX.

apex {
  ..
  vendor: true,
  prebuilts: ["fragment.xml"],
  ..
}

prebuilt_etc {
  name: "fragment.xml",
  src: "fragment.xml",
  sub_dir: "vintf",
}

APIs de consulta

Cuando se agregan fragmentos de VINTF a APEX, usa las APIs de libbinder_ndk para obtener las asignaciones de interfaces HAL y nombres de APEX.

  • AServiceManager_isUpdatableViaApex("com.android.foo.IFoo/default"): true si la instancia de HAL se define en APEX.
  • AServiceManager_getUpdatableApexName("com.android.foo.IFoo/default", ...): Obtiene el nombre de APEX que define la instancia de HAL.
  • AServiceManager_openDeclaredPassthroughHal("mapper", "instance", ...): Úsalo para abrir un HAL de transferencia.

Secuencias de comandos de init

Los APEX pueden incluir secuencias de comandos de init de dos maneras: (A) un archivo de texto precompilado dentro de la carga útil de APEX o (B) una secuencia de comandos de init normal en /vendor/etc. Puedes configurar ambos para el mismo APEX.

Secuencia de comandos de init en APEX:

prebuilt_etc {
  name: "myinit.rc",
  src: "myinit.rc"
}

apex {
  ..
  vendor: true,
  prebuilts: ["myinit.rc"],
  ..
}

Las secuencias de comandos de init en los APEX de proveedores pueden tener directivas on <property or event> y definiciones service.

Asegúrate de que una definición de service apunte a un objeto binario en el mismo APEX. Por ejemplo, com.android.foo APEX puede definir un servicio llamado foo-service.

on foo-service /apex/com.android.foo/bin/foo
  ...

Ten cuidado cuando uses directivas on. Dado que las secuencias de comandos de init en APEX se analizan y ejecutan después de que se activan los APEX, no se pueden usar algunos eventos o propiedades. Usa apex.all.ready=true para activar acciones lo antes posible. Los APEX de arranque pueden usar on init, pero no on early-init.

Firmware

Ejemplo:

Incorpora el firmware en un APEX del proveedor con el tipo de módulo prebuilt_firmware, como se muestra a continuación.

prebuilt_firmware {
  name: "my.bin",
  src: "path_to_prebuilt_firmware",
  vendor: true,
}

apex {
  ..
  vendor: true,
  prebuilts: ["my.bin"],  // installed inside APEX as /etc/firmware/my.bin
  ..
}

Los módulos prebuilt_firmware se instalan en el directorio <apex name>/etc/firmware de APEX. ueventd analiza los directorios /apex/*/etc/firmware para encontrar módulos de firmware.

El file_contexts de APEX debe etiquetar correctamente las entradas de carga útil del firmware para garantizar que ueventd pueda acceder a estos archivos durante el tiempo de ejecución. Por lo general, la etiqueta vendor_file es suficiente. Por ejemplo:

(/.*)? u:object_r:vendor_file:s0

Módulos de kernel

Incorpora módulos de kernel en un APEX de proveedor como módulos compilados previamente, de la siguiente manera.

prebuilt_etc {
  name: "my.ko",
  src: "my.ko",
  vendor: true,
  sub_dir: "modules"
}

apex {
  ..
  vendor: true,
  prebuilts: ["my.ko"],  // installed inside APEX as /etc/modules/my.ko
  ..
}

El file_contexts del APEX debe etiquetar correctamente las entradas de carga útil del módulo del kernel. Por ejemplo:

/etc/modules(/.*)? u:object_r:vendor_kernel_modules:s0

Los módulos de kernel deben instalarse de forma explícita. En la siguiente secuencia de comandos de init de ejemplo en la partición del proveedor, se muestra la instalación a través de insmod:

my_init.rc:

on early-boot
  insmod /apex/myapex/etc/modules/my.ko
  ..

Superposiciones de recursos en tiempo de ejecución

Ejemplo:

Incorpora superposiciones de recursos del entorno de ejecución en un APEX del proveedor con la propiedad rros.

runtime_resource_overlay {
    name: "my_rro",
    soc_specific: true,
}


apex {
  ..
  vendor: true,
  rros: ["my_rro"],  // installed inside APEX as /overlay/my_rro.apk
  ..
}

Otros archivos de configuración

Los APEX de proveedores admiten varios otros archivos de configuración que suelen encontrarse en la partición del proveedor como precompilados dentro de los APEX de proveedores, y se agregarán más.

Ejemplos:

APEX de proveedores de arranque

Algunos servicios de HAL, como keymint, deberían estar disponibles antes de que se activen los APEX. Por lo general, esos HAL establecen early_hal en su definición de servicio en la secuencia de comandos de init. Otro ejemplo es la clase animation, que generalmente se inicia antes que el evento post-fs-data. Cuando se empaqueta un servicio HAL anterior en el APEX del proveedor, crea el dominio raíz "vendorBootstrap": true en su manifiesto de APEX para que se pueda activar antes. Ten en cuenta que los APEX de arranque solo se pueden activar desde la ubicación compilada previamente, como /vendor/apex, no desde /data/apex.

Propiedades del sistema

Estas son las propiedades del sistema que el framework lee para admitir APEX del proveedor:

  • input_device.config_file.apex=<apex name>: Cuando se establece, los archivos de configuración de entrada (*.idc, *.kl y *.kcm) se buscan desde el directorio /etc/usr del APEX.
  • ro.vulkan.apex=<apex name>: Cuando se establece, el controlador de Vulkan se carga desde APEX. Dado que los HAL anteriores usan el controlador de Vulkan, haz que APEX inicie APEX y configura ese espacio de nombres del vinculador para que sea visible.

Configura las propiedades del sistema en las secuencias de comandos de init con el comando setprop.

Funciones de desarrollo adicionales

Selección de APEX durante el inicio

Ejemplo:

Los desarrolladores también pueden instalar varias versiones de APEX de proveedores que comparten el mismo nombre y clave de APEX y, luego, elegir qué versión se activa durante cada inicio mediante sysprops persistentes. En algunos casos de uso de desarrolladores, esto podría ser más simple que instalar una copia nueva del APEX con adb install.

Ejemplos de casos de uso:

  • Instala 3 versiones del APEX del proveedor de HAL de Wi-Fi: Los equipos de QA pueden ejecutar pruebas manuales o automatizadas con una versión, reiniciar en otra versión y volver a ejecutar las pruebas, y luego comparar los resultados finales.
  • Instala 2 versiones de APEX actual y experimental del proveedor de la HAL de la cámara: Los participantes de la prueba interna pueden usar la versión experimental sin descargar ni instalar un archivo adicional, por lo que pueden volver a la versión anterior con facilidad.

Durante el inicio, apexd busca sysprops con un formato específico para activar la versión correcta de APEX.

Los formatos esperados para la clave de propiedad son los siguientes:

  • Bootconfig
    • Se usa para establecer el valor predeterminado en BoardConfig.mk.
    • androidboot.vendor.apex.<apex name>
  • propósito del sistema persistente
    • Se usa para cambiar el valor predeterminado establecido en un dispositivo que ya se inició.
    • Anula el valor de bootconfig si está presente.
    • persist.vendor.apex.<apex name>

El valor de la propiedad debe ser el nombre de archivo del APEX que se debe activar.

// Default version.
apex {
  name: "com.oem.camera.hal.my_apex_default",
  vendor: true,
  ..
}

// Non-default version.
apex {
  name: "com.oem.camera.hal.my_apex_experimental",
  vendor: true,
  ..
}

La versión predeterminada también se debe configurar con bootconfig en BoardConfig.mk:

# Example for APEX "com.oem.camera.hal" with the default above:
BOARD_BOOTCONFIG += \
    androidboot.vendor.apex.com.oem.camera.hal=com.oem.camera.hal.my_apex_default

Después de que se inicie el dispositivo, cambia la versión activada configurando la sysprop persistente:

$ adb root;
$ adb shell setprop \
    persist.vendor.apex.com.oem.camera.hal \
    com.oem.camera.hal.my_apex_experimental;
$ adb reboot;

Si el dispositivo admite la actualización de bootconfig después de la actualización de firmware (como a través de los comandos fastboot oem), cambiar la propiedad bootconfig para el APEX instalado varias veces también cambia la versión activada durante el inicio.

En el caso de los dispositivos de referencia virtuales basados en Cuttlefish, puedes usar el comando --extra_bootconfig_args para configurar la propiedad bootconfig directamente durante el inicio. Por ejemplo:

launch_cvd --noresume \
  --extra_bootconfig_args "androidboot.vendor.apex.com.oem.camera.hal:=com.oem.camera.hal.my_apex_experimental";