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 <uses-library>
dependencias. En algunos casos, estos cambios del sistema de compilación pueden romper compilaciones. Usa esta página para prepararte para las interrupciones y sigue las recetas que se indican en esta página para corregirlas y mitigarlas.
Dexpreopt es el proceso de compilación anticipada de bibliotecas y apps de Java. Dexpreopt se realiza en el host durante el tiempo de compilación (a diferencia de dexopt, que se realiza integrado en el dispositivo). La estructura de las dependencias de bibliotecas compartidas que usa un módulo de Java (una biblioteca o una app) se conoce como su contexto del cargador de clases (CLC). Para garantizar la exactitud de dexpreopt, los CLC de tiempo de compilación y de tiempo de ejecución deben coincidir. El CLC de 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 se carga el código precompilado en el dispositivo.
Estos CLC de tiempo de compilación y de tiempo de ejecución deben coincidir por motivos de exactitud y rendimiento. En cuanto a la exactitud, es necesario controlar las clases duplicadas. Si las dependencias de bibliotecas compartidas en el tiempo de ejecución son diferentes de las que se usan para la compilación, algunas de las clases podrían resolverse de manera diferente, lo que causaría errores sutiles en el tiempo de ejecución. El rendimiento también se ve afectado por las verificaciones de tiempo de ejecución para las clases duplicadas.
Casos de uso afectados
El primer arranque es el caso de uso principal que se ve afectado por estos cambios: si ART detecta una falta de coincidencia entre los CLC de tiempo de compilación y de tiempo de ejecución, rechaza los artefactos de dexpreopt y ejecuta dexopt en su lugar. Para los arranques posteriores, esto está bien porque 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 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.
Cambios rotundos
El sistema de compilación necesita conocer las dependencias <uses-library> antes de que
genere reglas de compilación de dexpreopt. Sin embargo, no puede acceder al manifiesto directamente
ni leer las <uses-library>
etiquetas en él, 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 se puede empaquetar dentro de un APK o un elemento compilado previamente. Por lo tanto, la <uses-library>
información debe estar presente en los archivos de compilación (Android.bp o Android.mk).
Anteriormente, ART usaba una solución alternativa que ignoraba las dependencias de bibliotecas compartidas (conocida
como &-classpath). Esto no era seguro y causaba errores sutiles, por lo que se quitó la solución alternativa
en Android 12.
Como resultado, los módulos de Java que no proporcionan <uses-library>
información correcta en sus archivos de compilación pueden causar interrupciones de compilación (debido a una
falta de coincidencia de CLC de tiempo de compilación) o regresiones de tiempo de primer arranque (debido a una falta de coincidencia de CLC de tiempo de arranque
seguida de dexopt).
Ruta de migración
Sigue estos pasos para corregir una compilación interrumpida:
Inhabilita globalmente la verificación de tiempo de compilación para un producto en particular configurando
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := trueen el archivo makefile del producto. Esto corrige los errores de compilación (excepto en casos especiales, que se enumeran en la sección Cómo corregir interrupciones). Sin embargo, esta es una solución alternativa temporal y puede causar una falta de coincidencia de CLC de tiempo de arranque seguida de dexopt.
Corrige los módulos que fallaron antes de inhabilitar globalmente la verificación de tiempo de compilación agregando la información
<uses-library>necesaria a sus archivos de compilación (consulta Cómo corregir interrupciones para obtener más detalles). Para la mayoría de los módulos, esto requiere agregar algunas líneas enAndroid.bpo enAndroid.mk.Inhabilita la verificación de tiempo de compilación y dexpreopt para los casos problemáticos, por módulo. Inhabilita dexpreopt para no perder tiempo de compilación ni almacenamiento en artefactos que se rechazan en el arranque.
Vuelve a habilitar globalmente la verificación de tiempo de compilación anulando
PRODUCT_BROKEN_VERIFY_USES_LIBRARIESque se configuró 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, de a uno por vez, y luego vuelve a habilitar dexpreopt y la
<uses-library>verificación. Presenta errores si es necesario.
Las verificaciones <uses-library> de tiempo de compilación se aplican en Android 12.
Cómo corregir interrupciones
En las siguientes secciones, se explica cómo corregir tipos específicos de interrupciones.
Error de compilación: Falta de coincidencia de CLC
El sistema de compilación realiza una verificación de coherencia de 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 (extraerlo
de un APK si es necesario) y comparar las etiquetas <uses-library> en el manifiesto
con la información <uses-library> en los archivos de compilación. Si falla la verificación, el error se ve 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, existen varias soluciones, según la urgencia:
- Para una corrección temporal en todo el producto, configura
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := trueen el archivo makefile del producto. Aún se realiza la verificación de coherencia de tiempo de compilación, pero una falla de verificación no significa una falla de compilación. En cambio, una falla de verificación hace que el sistema de compilación disminuya el filtro del compilador dex2oat averifyen dexpreopt, lo que inhabilita por completo la compilación AOT para este módulo. - Para una correcció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ñada para usarse en la línea de comandos. La variable de entorno anula la variable del producto. - Para una solución que corrija la causa raíz del error, haz que el sistema de compilación reconozca
las
<uses-library>etiquetas en el manifiesto. Una inspección del mensaje de error muestra qué bibliotecas causan el problema (al igual que la inspección deAndroidManifest.xmlo 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
libsdel módulo. Si está allí, Soong normalmente agrega esas bibliotecas automáticamente, excepto en estos casos especiales:- La biblioteca no es una biblioteca del SDK (se define como
java_libraryen lugar dejava_sdk_library). - La biblioteca tiene un nombre de biblioteca diferente (en el manifiesto) de su nombre de módulo (en el sistema de compilación).
Para corregir esto de forma temporal, agrega
provides_uses_lib: "<library-name>"en laAndroid.bpdefinición de biblioteca. Para una solución a largo plazo, corrige el problema subyacente: convierte la biblioteca en una biblioteca del 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.bpdel 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.
Para los módulos Android.mk:
Verifica si la biblioteca tiene un nombre de biblioteca diferente (en el manifiesto) de su nombre de módulo (en el sistema de compilación). Si es así, corrige esto de forma temporal agregando
LOCAL_PROVIDES_USES_LIBRARY := <library-name>en elAndroid.mkarchivo de la biblioteca o agregaprovides_uses_lib: "<library-name>"en elAndroid.bparchivo de la biblioteca (ambos casos son posibles, ya que un móduloAndroid.mkpuede depender de una bibliotecaAndroid.bp). Para una solución a largo plazo, corrige el problema subyacente: cambia el nombre del módulo de la biblioteca.Agrega
LOCAL_USES_LIBRARIES := <library-module-name>para las bibliotecas requeridas; agregaLOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>para las bibliotecas opcionales a la definiciónAndroid.mkdel 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 biblioteca desconocida
Si el sistema de compilación no puede encontrar una ruta de acceso a un jar DEX <uses-library> (ya sea una ruta de acceso de tiempo de compilación en el host o una ruta de acceso de instalación integrado en el dispositivo), suele fallar la compilación. Si no se encuentra una ruta de acceso, puede indicar que la biblioteca está configurada de alguna 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
Presenta un error para investigar cualquier situación no admitida.
Error de compilación: Falta dependencia de biblioteca
Si intentas agregar <uses-library> X del manifiesto del módulo Y al archivo de compilación
para Y, es posible que se produzca un error de compilación debido a la falta de dependencia, X.
Este es un mensaje de error de ejemplo para los módulos Android.bp:
"Y" depends on undefined module "X"
Este es un mensaje de error de ejemplo 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 se denomina de manera diferente a su módulo correspondiente en el sistema de compilación. Por ejemplo, si la entrada del manifiesto
<uses-library> 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 para las bibliotecas Android.bp (propiedad del módulo):
provides_uses_lib: “com.android.X”,
Este es un ejemplo para las bibliotecas Android.mk (variable del módulo):
LOCAL_PROVIDES_USES_LIBRARY := com.android.X
Falta de coincidencia de CLC de tiempo de arranque
En el primer arranque, busca en logcat mensajes relacionados con la falta de coincidencia 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 formulario 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 falta de coincidencia de CLC, busca un comando dexopt para el módulo defectuoso. Para corregirlo, asegúrate de que pase la verificación de tiempo de compilación para el módulo. Si eso no funciona, es posible que sea un caso especial que no admita el sistema de compilación (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 similar a un árbol que describe la jerarquía del cargador de clases. El
sistema de compilación usa CLC en un sentido restringido (solo cubre bibliotecas, no APKs ni
cargadores de clases personalizados): 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 clase). Cada nodo de un árbol CLC es un
<uses-library> nodo que puede tener sus propios <uses-library> subnodos.
Debido a que las dependencias <uses-library> son un gráfico 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 realiza 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 se resuelven 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 en el dispositivo (tiempo de ejecución)
PackageManager (en frameworks/base) crea un CLC para cargar un módulo de Java integrado 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 usada, PackageManager obtiene todas sus <uses-library>
dependencias (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 <uses-library>
dependencias.
PackageManager solo conoce las bibliotecas compartidas. La definición de compartido en este uso difiere de su significado habitual (como en compartido vs. estático). En Android,
las bibliotecas compartidas de Java son las que se enumeran en las configuraciones XML que se instalan
integrado 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
<uses-library> etiquetas en su manifiesto).
En otras palabras, existen dos fuentes de información que permiten que PackageManager
construya CLC en el tiempo de ejecución: etiquetas <uses-library> en el manifiesto y
dependencias de bibliotecas compartidas en configuraciones XML.
CLC 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 integrado en el dispositivo (dexopt) o durante la compilación (dexpreopt). Dado que dexopt se realiza integrado 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 completamente 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 rechaza el código compilado por AOT creado por dexpreopt. Para verificar la igualdad de los CLC de tiempo de compilación y de tiempo de ejecución, el compilador dex2oat registra el CLC de 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 falta de coincidencia de CLC de tiempo de compilación y de tiempo de ejecución se informa en logcat durante el arranque. Búscalo con este comando:
logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'
La falta de coincidencia es perjudicial para el rendimiento, ya que obliga a la biblioteca o a la app a dexoptarse o a ejecutarse 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 requerida. Desde el punto de vista de dexpreopt, una biblioteca requerida 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 tiempo de 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 falta de coincidencia entre el estado de tiempo de compilación y de tiempo de ejecución (la biblioteca opcional está presente en un caso, pero no en el otro), entonces los CLC de tiempo de compilación y de tiempo de ejecución no coinciden y se rechaza el código compilado.
Detalles avanzados del sistema de compilación (corrector de manifiesto)
A veces, faltan etiquetas <uses-library> en el manifiesto fuente de una
biblioteca o app. Esto puede suceder, por ejemplo, si una de las dependencias transitivas
de la biblioteca o la app comienza a usar otra etiqueta <uses-library> y el
manifiesto de la biblioteca o la 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 la app. El cierre es necesario porque la biblioteca (o la app) puede
depender de una biblioteca estática que depende de una biblioteca del SDK y, posiblemente, pueda
volver 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 es posible, 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 una biblioteca estática
que agrega una nueva <uses-library> dependencia, se deben actualizar todas las apps, lo que es difícil de mantener.