Android 12 tiene cambios en el sistema de compilación para la compilación anticipada de archivos DEX (dexpreopt) para módulos de Java que tienen dependencias <uses-library>
. En algunos casos, estos cambios del sistema de compilación pueden romper 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 y apps de Java. Dexpreopt se realiza en el host en el momento de la compilación (a diferencia de dexopt, que se realiza en el dispositivo). La estructura de dependencias de bibliotecas compartidas que usa un módulo de Java (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 del tiempo de compilación es lo que usa el compilador dex2oat en el tiempo de dexpreopt (se registra en los archivos ODEX) y el CLC del tiempo de ejecución es el contexto en el que se carga el código precompilado en el dispositivo.
Estos CLCs de tiempo de compilación y tiempo de ejecución deben coincidir por motivos de exactitud y rendimiento. Para que sea correcto, es necesario controlar las 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 causa errores sutiles en el tiempo de ejecución. El rendimiento también se ve afectado por las verificaciones del tiempo de ejecución en busca de clases duplicadas.
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 aceptable, ya que las apps se pueden dexoptar en segundo plano y almacenar en el disco.
Áreas afectadas de Android
Esto afecta a todas las apps y bibliotecas de Java que tienen dependencias de tiempo de ejecución en otras bibliotecas de Java. Android tiene miles de apps, y cientos de ellas usan bibliotecas compartidas. Los socios también se ven afectados, ya que tienen sus propias bibliotecas y apps.
Cómo dividir los cambios
El sistema de compilación debe conocer las dependencias de <uses-library>
antes de generar 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, la información de <uses-library>
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 información correcta de <uses-library>
en sus archivos de compilación pueden causar pérdidas de compilación (causadas por una discrepancia de CLC en el tiempo de compilación) o regresión en el primer inicio (causada por una discrepancia de CLC en el tiempo de inicio seguida de dexopt).
Ruta de migración
Sigue estos pasos para corregir una compilación dañada:
Para inhabilitar de forma global la verificación del tiempo de compilación de un producto en particular, establece
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
en el archivo makefile del producto. Esto corrige los errores de compilación (excepto los casos especiales que se enumeran en la sección Cómo corregir las fallas). Sin embargo, esta es una solución temporal y puede causar una discrepancia de CLC en el tiempo de inicio seguida de dexopt.
Para corregir los módulos que fallaron antes de inhabilitar de forma global la verificación del tiempo de compilación, agrega la información
<uses-library>
necesaria a sus archivos de compilación (consulta Cómo corregir las fallas para obtener más información). Para la mayoría de los módulos, esto requiere agregar algunas líneas enAndroid.bp
o enAndroid.mk
.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.
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).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>
. Si es necesario, informa errores.
Las verificaciones de <uses-library>
en el tiempo de compilación se aplican de forma forzosa en Android 12.
Cómo corregir daños
En las siguientes secciones, se explica cómo corregir tipos específicos de daños.
Error de compilación: discrepancia de CLC
El sistema de compilación realiza una verificación de coherencia durante el tiempo de compilación entre la información de los 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 (extrayéndolo de un APK si es necesario) y comparar las etiquetas <uses-library>
del manifiesto con 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 se sugiere en el mensaje de error, hay varias soluciones, según la 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 en el tiempo de compilación aún se realiza, pero una falla de verificación no significa que haya una falla de compilación. En su lugar, una falla de verificación hace que el sistema de compilación revierta el filtro del compilador de dex2oat averify
en dexpreopt, lo que inhabilita la compilación AOT por completo 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 quePRODUCT_BROKEN_VERIFY_USES_LIBRARIES
, pero está diseñado para usarse en la línea de comandos. La variable de entorno anula la variable del 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 qué bibliotecas causan el problema (al igual que inspeccionarAndroidManifest.xml
o el manifiesto dentro de un APK que se puede verificar con "aapt dump badging $APK | grep uses-library
").
Para los módulos Android.bp
:
Busca la biblioteca faltante en la propiedad
libs
del módulo. Si está allí, Soong suele agregar esas bibliotecas automáticamente, excepto en los siguientes casos especiales:- La biblioteca no es una biblioteca del SDK (se define como
java_library
en lugar dejava_sdk_library
). - La biblioteca tiene un nombre diferente (en el manifiesto) del nombre del módulo (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 bibliotecaAndroid.bp
. Para obtener una solución a largo plazo, corrige el problema subyacente: convierte la biblioteca en una biblioteca de SDK o cambia el nombre de su módulo.- La biblioteca no es una biblioteca del SDK (se define como
Si el paso anterior no proporcionó una resolución, agrega
uses_libs: ["<library-module-name>"]
para las bibliotecas requeridas ooptional_uses_libs: ["<library-module-name>"]
para las bibliotecas opcionales a la definiciónAndroid.bp
del módulo. Estas propiedades aceptan una lista de nombres de módulos. El orden relativo de las bibliotecas de la lista debe ser el mismo que el orden del manifiesto.
Para los módulos Android.mk
:
Comprueba si la biblioteca tiene un nombre diferente (en el manifiesto) del nombre del módulo (en el sistema de compilación). Si es así, para solucionarlo temporalmente, agrega
LOCAL_PROVIDES_USES_LIBRARY := <library-name>
en el archivoAndroid.mk
de la biblioteca o agregaprovides_uses_lib: "<library-name>"
en el archivoAndroid.bp
de la biblioteca (ambos casos son posibles, ya que un móduloAndroid.mk
puede depender de una bibliotecaAndroid.bp
). Para obtener una solución a largo plazo, corrige el problema subyacente: cambia el nombre del módulo de biblioteca.Agrega
LOCAL_USES_LIBRARIES := <library-module-name>
para las bibliotecas requeridas yLOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>
para las bibliotecas opcionales a la definiciónAndroid.mk
del módulo. Estas propiedades 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. Para corregir la compilación de forma temporal, inhabilita dexpreopt para el módulo problemático.
Android.bp (propiedades del módulo):
enforce_uses_libs: false,
dex_preopt: {
enabled: false,
},
Android.mk (variables del 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 mensaje de error de muestra para los módulos Android.bp:
"Y" depends on undefined module "X"
Este es un mensaje de error de muestra para los 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 es cuando una biblioteca tiene un nombre diferente al de su módulo correspondiente 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, dile 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 en el CLC 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 del formato 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 el módulo defectuoso. 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 sistema de compilación no controla todos los casos, ya que, en el momento de la compilación, es imposible saber con certeza qué carga la app en 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 sistema de compilación usa CLC en un sentido estricto (solo abarca bibliotecas, no APK ni cargadores de clases personalizadas): es un árbol de bibliotecas que representa el cierre transitivo de todas las dependencias <uses-library>
de una biblioteca o app. Los elementos de nivel superior de un CLC son las dependencias <uses-library>
directas especificadas en el manifiesto (la ruta de acceso a clases). 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).
El CLC define el orden de búsqueda de las bibliotecas cuando resuelve las clases de Java que usa la biblioteca o la 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 conoce las bibliotecas compartidas. La definición de compartido en este uso difiere de su significado habitual (como en compartido o 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 que PackageManager
compile CLC en el tiempo de ejecución: las etiquetas <uses-library>
en el manifiesto y las dependencias de bibliotecas compartidas en la configuración XML.
CLC integrado en el host (tiempo de 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). Dado que dexopt se realiza en el dispositivo, tiene la misma información que PackageManager
(manifiestos y dependencias de bibliotecas compartidas).
Sin embargo, Dexpreopt se realiza en el host y en un entorno totalmente diferente, y debe obtener la misma información del sistema de compilación.
Por lo tanto, el CLC de tiempo de compilación que usa dexpreopt y el CLC de tiempo de ejecución que usa PackageManager
son lo mismo, pero se calculan de dos maneras diferentes.
Los CLC de tiempo de compilación y de tiempo de ejecución deben coincidir; de lo contrario, se rechazará el código compilado por AOT que crea dexpreopt. 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 del 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 perjudicial para el rendimiento, ya que obliga a que la biblioteca o la app se dexopten o se ejecuten sin optimizaciones (por ejemplo, es posible que el código de la app deba 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 momento de la compilación (su ausencia es un error de compilación). Una biblioteca opcional puede estar presente o ausente en el momento de la compilación: si está presente, se agrega al CLC, se pasa a dex2oat y se registra 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 calcular de esta manera, pero, cuando sea posible, es preferible permitir que Soong agregue entradas de manifiesto automáticamente, ya que es menos propenso a errores y simplifica el mantenimiento. Por ejemplo, cuando muchas apps usan una biblioteca estática que agrega una nueva dependencia de <uses-library>
, se deben actualizar todas las apps, lo que es difícil de mantener.