Verificaciones de Dexpreopt y <uses-library>

El sistema de compilación de Android 12 incluye cambios en la compilación AOT de archivos DEX (dexpreopt) para módulos de Java que tienen <uses-library> las dependencias. En algunos casos, estos cambios del sistema de compilación pueden interrumpir compilaciones. Usa esta página para prepararte ante las fallas y sigue las recetas que se indican en esta página para solucionarlas y mitigarlas.

Dexpreopt es el proceso de compilación anticipada de bibliotecas Java y de Google Chat. Dexpreopt se realiza en el host en el momento de la compilación (a diferencia de dexopt, que se realiza en el dispositivo). Estructura de dependencias de bibliotecas compartidas usadas por un Java módulo (una biblioteca o una app) se conoce como su contexto de cargador de clases (CLC). Para garantizar la exactitud de dexpreopt, los CLCs del tiempo de compilación y del tiempo de ejecución deben coincidir. El CLC en el tiempo de compilación es lo que usa el compilador dex2oat en el momento de dexpreopt. (se registra en los archivos ODEX) y el CLC de tiempo de ejecución es el contexto en el que código compilado previamente se carga en el dispositivo.

Estos CLC de tiempo de compilación y tiempo de ejecución deben coincidir por motivos de exactitud y rendimiento. Para mayor precisión, es necesario manejar clases duplicadas. Si las dependencias de la biblioteca compartida en el tiempo de ejecución son diferentes de las que se usan para la compilación, es posible que algunas de las clases se resuelvan de manera diferente, lo que causará errores sutiles en el tiempo de ejecución. El rendimiento también se ve afectado por las verificaciones .

Casos de uso afectados

El primer inicio es el caso de uso principal que se ve afectado por estos cambios: si ART detecta una discrepancia entre los CLC del tiempo de compilación y del tiempo de ejecución, rechaza los artefactos de dexpreopt y, en su lugar, ejecuta dexopt. Para los inicios posteriores, esto es correcto las apps se pueden dexoptar en segundo plano y almacenarse en el disco.

Áreas afectadas de Android

Esto afecta a todas las apps y bibliotecas de Java que tienen dependencias de entorno de ejecución en otras bibliotecas de Java. Android tiene miles de apps, y cientos de ellas usan las bibliotecas compartidas. Los socios también se ven afectados, ya que tienen sus propias bibliotecas y apps.

Cómo dividir cambios

El sistema de compilación debe conocer las dependencias <uses-library> antes genera reglas de compilación de dexpreopt. Sin embargo, no puede acceder al manifiesto directamente ni leer las etiquetas <uses-library> que contiene, ya que el sistema de compilación no tiene permiso para leer archivos arbitrarios cuando genera reglas de compilación (por motivos de rendimiento). Además, el manifiesto puede empaquetarse dentro de un APK o una compilación previa. Por lo tanto, el objeto <uses-library> La información debe estar presente en los archivos de compilación (Android.bp o Android.mk).

Anteriormente, ART usaba una solución que ignoraba las dependencias de bibliotecas compartidas (conocidas como &-classpath). Esto no era seguro y causaba errores sutiles, por lo que se quitó la solución en Android 12.

Como resultado, los módulos de Java que no proporcionan <uses-library> correctos información en sus archivos de compilación pueden causar fallas de compilación (causadas por una discrepancia de CLC en el tiempo de compilación) o regresiones de tiempo de primer inicio (causadas por un tiempo de inicio discrepancia de CLC seguida de dexopt).

Ruta de migración

Sigue estos pasos para corregir una compilación dañada:

  1. Configura para inhabilitar globalmente la verificación del tiempo de compilación para un producto en particular

    PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true

    en el archivo makefile del producto. Esto corrige errores de compilación (excepto en casos especiales, en la sección Cómo corregir fallas). Sin embargo, Esta es una solución temporal y puede causar una discrepancia de CLC en el inicio y luego el dexopt.

  2. Se corrigieron los módulos que fallaron antes de inhabilitar globalmente la verificación de tiempo de compilación Para ello, agrega la información necesaria de <uses-library> a sus archivos de compilación (consulta Cómo corregir fallas para obtener más detalles). Para la mayoría de los módulos, esto requiere agregar algunas líneas en Android.bp, o en Android.mk

  3. Inhabilita la verificación del tiempo de compilación y dexpreopt para los casos problemáticos, por módulo. Inhabilita dexpreopt para no desperdiciar tiempo de compilación ni almacenamiento en artefactos que se rechazan durante el inicio.

  4. Para volver a habilitar de forma global la verificación del tiempo de compilación, anula la configuración de PRODUCT_BROKEN_VERIFY_USES_LIBRARIES que se estableció en el paso 1. La compilación no debería fallar después de este cambio (debido a los pasos 2 y 3).

  5. Corrige los módulos que inhabilitaste en el paso 3, uno a la vez, y, luego, vuelve a habilitar dexpreopt y la verificación <uses-library>. Informa los errores si es necesario.

Las verificaciones de <uses-library> en el tiempo de compilación se aplican en Android 12.

Corregir fallas

En las siguientes secciones, se explica cómo corregir tipos específicos de fallas.

Error de compilación: Discrepancia de CLC

El sistema de compilación verifica la coherencia de tiempo de compilación entre la información de Archivos Android.bp o Android.mk y el manifiesto El sistema de compilación no puede leer el manifiesto, pero puede generar reglas de compilación para leerlo (extraer desde un APK, si es necesario) y comparar las etiquetas <uses-library> en el manifiesto en la información de <uses-library> en los archivos de compilación. Si la verificación falla, el error se verá de la siguiente manera:

error: mismatch in the <uses-library> tags between the build system and the manifest:
    - required libraries in build system: []
                     vs. in the manifest: [org.apache.http.legacy]
    - optional libraries in build system: []
                     vs. in the manifest: [com.x.y.z]
    - tags in the manifest (.../X_intermediates/manifest/AndroidManifest.xml):
        <uses-library android:name="com.x.y.z"/>
        <uses-library android:name="org.apache.http.legacy"/>

note: the following options are available:
    - to temporarily disable the check on command line, rebuild with RELAX_USES_LIBRARY_CHECK=true (this will set compiler filter "verify" and disable AOT-compilation in dexpreopt)
    - to temporarily disable the check for the whole product, set PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true in the product makefiles
    - to fix the check, make build system properties coherent with the manifest
    - see build/make/Changes.md for details

Como sugiere el mensaje de error, hay varias soluciones, dependiendo del urgencia:

  • Para obtener una corrección temporal para todo el producto, establece PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true en el archivo makefile del producto. La verificación de coherencia del tiempo de compilación aún se realiza, pero una falla de verificación no significa que haya una falla de compilación. En cambio, una falla de verificación hace que el sistema de compilación cambie a una versión inferior. Filtro del compilador dex2oat a verify en dexpreopt, que inhabilita la compilación AOT completamente para este módulo.
  • Para obtener una solución rápida y global de la línea de comandos, usa la variable de entorno RELAX_USES_LIBRARY_CHECK=true. Tiene el mismo efecto que PRODUCT_BROKEN_VERIFY_USES_LIBRARIES, pero está diseñado para usarse en la línea de comandos. El de entorno anula la variable de producto.
  • Para obtener una solución que solucione la causa raíz del error, haz que el sistema de compilación conozca las etiquetas <uses-library> en el manifiesto. Una inspección del mensaje de error muestra las bibliotecas que causan el problema (como AndroidManifest.xml o el manifiesto dentro de un APK que se puede verificar con `aapt dump badging $APK | grep uses-library`).

Para módulos Android.bp:

  1. Busca la biblioteca que falta en la propiedad libs del módulo. Si es ahí, Soong normalmente agrega esas bibliotecas automáticamente, excepto que en estas casos especiales:

    • La biblioteca no es una biblioteca de SDK (se define como java_library en lugar de que java_sdk_library).
    • La biblioteca tiene un nombre diferente (en el manifiesto) de su módulo. nombre (en el sistema de compilación).

    Para solucionar este problema de forma temporal, agrega provides_uses_lib: "<library-name>" en la definición de la biblioteca Android.bp. Para una solución a largo plazo, corrige el problema Problema: convierte la biblioteca en una biblioteca de SDK o cambia el nombre de su módulo.

  2. Si el paso anterior no te brindó una solución, agrega uses_libs: ["<library-module-name>"] para las bibliotecas requeridas o optional_uses_libs: ["<library-module-name>"] para que las bibliotecas opcionales la definición del módulo Android.bp. Estas propiedades aceptan una lista de los nombres de los módulos. El orden relativo de las bibliotecas de la lista debe ser el mismo como el orden en el manifiesto.

Para módulos Android.mk:

  1. Verifica si la biblioteca tiene un nombre diferente (en el manifiesto) del nombre del módulo (en el sistema de compilación). Si es así, soluciona el problema temporalmente agregando LOCAL_PROVIDES_USES_LIBRARY := <library-name> en el archivo Android.mk de la biblioteca o agrega provides_uses_lib: "<library-name>" en Android.bp archivo de la biblioteca (ambos casos son posibles, ya que se necesita un módulo Android.mk) podría depender de una biblioteca Android.bp). Para obtener una solución a largo plazo, corrige el problema subyacente: cambiar el nombre del módulo de la biblioteca.

  2. Agregar LOCAL_USES_LIBRARIES := <library-module-name> para obligatorio bibliotecas; agregar LOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name> para bibliotecas opcionales a la definición Android.mk del módulo. Estos aceptan una lista de nombres de módulos. El orden relativo de las bibliotecas en la lista debe ser el mismo que en el manifiesto.

Error de compilación: ruta de acceso a la biblioteca desconocida

Si el sistema de compilación no puede encontrar una ruta de acceso a un jar DEX de <uses-library> (ya sea una ruta de acceso en el tiempo de compilación en el host o una ruta de acceso de instalación en el dispositivo), la compilación suele fallar. Si no se encuentra una ruta, es posible que la biblioteca esté configurada de una manera inesperada. Inhabilita dexpreopt para corregir la compilación de forma temporal para el problema módulo.

Android.bp (propiedades del módulo):

enforce_uses_libs: false,
dex_preopt: {
    enabled: false,
},

Android.mk (variables de módulo):

LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false

Envía un informe de errores para investigar las situaciones que no se admiten.

Error de compilación: falta la dependencia de la biblioteca

Un intento de agregar <uses-library> X del manifiesto del módulo Y al archivo de compilación de Y puede generar un error de compilación debido a la dependencia faltante, X.

Este es un ejemplo de mensaje de error para módulos de Android.bp:

"Y" depends on undefined module "X"

Este es un ejemplo de mensaje de error para módulos Android.mk:

'.../JAVA_LIBRARIES/com.android.X_intermediates/dexpreopt.config', needed by '.../APPS/Y_intermediates/enforce_uses_libraries.status', missing and no known rule to make it

Una fuente común de estos errores ocurre cuando una biblioteca tiene un nombre diferente al de su al módulo correspondiente se le asigna un nombre en el sistema de compilación. Por ejemplo, si la entrada <uses-library> del manifiesto es com.android.X, pero el nombre del módulo de la biblioteca es solo X, se produce un error. Para resolver este caso, indícale al sistema de compilación que el módulo llamado X proporciona un <uses-library> llamado com.android.X.

Este es un ejemplo de bibliotecas Android.bp (propiedad del módulo):

provides_uses_lib: “com.android.X”,

Este es un ejemplo de bibliotecas de Android.mk (variable de módulo):

LOCAL_PROVIDES_USES_LIBRARY := com.android.X

Discrepancia de CLC en el tiempo de inicio

En el primer inicio, busca en logcat mensajes relacionados con la discrepancia de CLC, como se muestra a continuación:

$ adb wait-for-device && adb logcat \
  | grep -E 'ClassLoaderContext [a-z ]+ mismatch' -A1

El resultado puede tener mensajes como el que se muestra aquí:

[...] W system_server: ClassLoaderContext shared library size mismatch Expected=..., found=... (PCL[]... | PCL[]...)
[...] I PackageDexOptimizer: Running dexopt (dexoptNeeded=1) on: ...

Si recibes una advertencia de discrepancia de CLC, busca un comando dexopt para la módulo. Para solucionarlo, asegúrate de que se apruebe la verificación del tiempo de compilación del módulo. Si eso no funciona, es posible que el tuyo sea un caso especial que el sistema de compilación no admite (como una app que carga otro APK, no una biblioteca). El El sistema de compilación no maneja todos los casos, ya que durante el tiempo de compilación es imposible saber con certeza qué carga la app durante el tiempo de ejecución.

Contexto del cargador de clases

El CLC es una estructura en forma de árbol que describe la jerarquía del cargador de clases. El usa CLC en un sentido limitado (solo abarca bibliotecas, no APK ni de clase personalizada): un árbol de bibliotecas que representa cierre de todas las dependencias de <uses-library> de una biblioteca o app El nivel superior elementos de un CLC son las dependencias <uses-library> directas especificadas en el manifiesto (la ruta de clase). Cada nodo de un árbol de CLC es un nodo <uses-library> que puede tener sus propios subnodos <uses-library>.

Debido a que las dependencias de <uses-library> son un grafo acíclico dirigido y no necesariamente un árbol, el CLC puede contener varios subárboles para la misma biblioteca. En otras palabras, el CLC es el gráfico de dependencias “desplegado” en un árbol. La duplicación solo se produce a nivel lógico; los cargadores de clases subyacentes reales no se duplican (en el tiempo de ejecución, hay una sola instancia de cargador de clases para cada biblioteca).

CLC define el orden de búsqueda de las bibliotecas al resolver las clases de Java que usa la biblioteca o app. El orden de búsqueda es importante porque las bibliotecas pueden contener clases duplicadas y la clase se resuelve en la primera coincidencia.

CLC integrado en el dispositivo (tiempo de ejecución)

PackageManager (en frameworks/base) crea un CLC para cargar un módulo Java en el dispositivo. Agrega las bibliotecas que se enumeran en las etiquetas <uses-library> en el manifiesto del módulo como elementos CLC de nivel superior.

Para cada biblioteca utilizada, PackageManager obtiene todas sus dependencias de <uses-library> (especificadas como etiquetas en el manifiesto de esa biblioteca) y agrega un CLC anidado para cada dependencia. Este proceso continúa de forma recursiva hasta que todos los nodos hoja del árbol CLC construido sean bibliotecas sin dependencias de <uses-library>.

PackageManager solo reconoce las bibliotecas compartidas. La definición de “compartida” este uso difiere de su significado habitual (como compartido frente a estático). En Android, las bibliotecas compartidas de Java son aquellas que se enumeran en las configuraciones XML que se instalan en el dispositivo (/system/etc/permissions/platform.xml). Cada entrada contiene el nombre de una biblioteca compartida, una ruta de acceso a su archivo JAR DEX y una lista de dependencias (otras bibliotecas compartidas que esta usa en el tiempo de ejecución y especifica en las etiquetas <uses-library> de su manifiesto).

En otras palabras, hay dos fuentes de información que permiten PackageManager para construir CLC en el tiempo de ejecución: etiquetas <uses-library> en el manifiesto las dependencias de bibliotecas compartidas en configuraciones XML.

CLC en el host (en el momento de la compilación)

El CLC no solo es necesario cuando se carga una biblioteca o una app, sino también cuando se compila una. La compilación puede ocurrir en el dispositivo (dexopt) o durante la compilación (dexpreopt). Como el dexopt se realiza en el dispositivo, tiene las mismas información como PackageManager (manifiestos y dependencias de bibliotecas compartidas). Dexpreopt, en cambio, se lleva a cabo en el host y en un entorno y debe obtener la misma información del sistema de compilación.

Por lo tanto, el CLC del tiempo de compilación que usa dexpreopt y el CLC del tiempo de ejecución que usa PackageManager son lo mismo, pero se calculan de dos maneras diferentes.

Los CLC en el tiempo de compilación y en el entorno de ejecución deben coincidir; de lo contrario, el código compilado por AOT que creó dexpreopt se rechazan. Para verificar la igualdad de los CLC del tiempo de compilación y del tiempo de ejecución, el compilador de dex2oat registra los CLC del tiempo de compilación en los archivos *.odex (en el campo classpath del encabezado del archivo OAT). Para encontrar el CLC almacenado, usa este comando:

oatdump --oat-file=<FILE> | grep '^classpath = '

La discrepancia de CLC en el tiempo de compilación y el tiempo de ejecución se informa en logcat durante el inicio. Para buscarlo, usa el siguiente comando:

logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'

La discrepancia es negativa para el rendimiento, ya que obliga a la biblioteca o app a dexoptado o ejecutarse sin optimizaciones (por ejemplo, el código de la app podría deben extraerse en la memoria del APK, una operación muy costosa).

Una biblioteca compartida puede ser opcional o obligatoria. Desde el punto de vista de dexpreopt, una biblioteca obligatoria debe estar presente en el tiempo de compilación (su la ausencia es un error de compilación). Una biblioteca opcional puede estar presente o ausente durante la compilación: si está presente, se agrega al CLC, se pasa a dex2oat y registrado en el archivo *.odex. Si falta una biblioteca opcional, se omite y no se agrega al CLC. Si hay una discrepancia entre el estado del tiempo de compilación y el tiempo de ejecución (la biblioteca opcional está presente en un caso, pero no en el otro), los CLC del tiempo de compilación y el tiempo de ejecución no coinciden, y se rechaza el código compilado.

Detalles avanzados del sistema de compilación (corrector de manifiestos)

A veces, faltan etiquetas <uses-library> en el manifiesto de origen de una biblioteca o app. Esto puede suceder, por ejemplo, si una de las dependencias transitivas de la biblioteca o app comienza a usar otra etiqueta <uses-library> y el manifiesto de la biblioteca o app no se actualiza para incluirla.

Soong puede calcular automáticamente algunas de las etiquetas <uses-library> faltantes para una biblioteca o app determinada, como las bibliotecas del SDK en el cierre de dependencia transitiva de la biblioteca o app. El cierre es necesario porque la biblioteca (o app) puede depender de una biblioteca estática que depende de una biblioteca del SDK y, posiblemente, vuelva a depender de forma transitiva a través de otra biblioteca.

No todas las etiquetas <uses-library> se pueden procesar de esta manera, pero cuando es posible, es es preferible permitir que Soong agregue entradas de manifiesto automáticamente. es menos propenso a errores y simplifica el mantenimiento. Por ejemplo, cuando muchas apps usan un que agrega una nueva dependencia <uses-library>, todas las apps deben se actualizan, lo cual es difícil de mantener.