Android 12 apporte des modifications au système de compilation de la compilation AOT des fichiers DEX (dexpreopt) pour les modules Java qui ont des dépendances <uses-library>
. Dans certains cas, ces modifications du système de compilation peuvent endommager les compilations. Utilisez cette page pour vous préparer aux problèmes de compatibilité et suivez les recettes sur cette page pour les résoudre et les atténuer.
Dexpreopt est le processus de compilation anticipée des applications et des bibliothèques Java. Dexpreopt se produit sur l'hôte au moment de la compilation (contrairement à dexopt, qui se produit sur l'appareil). La structure des dépendances de bibliothèque partagée utilisées par un module Java (bibliothèque ou application) est appelée contexte du chargeur de classe (CLC, class loader context). Pour garantir l'exactitude de dexpreopt, les CLC de compilation et d'exécution doivent coïncider. La CLC au moment de la compilation est celle utilisée par le compilateur dex2oat au moment de la préoptimisation DEX (elle est enregistrée dans les fichiers ODEX). La CLC au moment de l'exécution est le contexte dans lequel le code précompilé est chargé sur l'appareil.
Ces CLC de compilation et d'exécution doivent coïncider pour des raisons d'exactitude et de performances. Pour que le code soit correct, il est nécessaire de gérer les classes en double. Si les dépendances de la bibliothèque partagée au moment de l'exécution sont différentes de celles utilisées pour la compilation, certaines classes peuvent être résolues différemment, ce qui peut entraîner des bugs d'exécution subtils. Les performances sont également affectées par les vérifications d'exécution pour les classes en double.
Cas d'utilisation concernés
Le premier démarrage est le principal cas d'utilisation concerné par ces modifications : si ART détecte une incohérence entre les CLC au moment de la compilation et au moment de l'exécution, il rejette les artefacts dexpreopt et exécute dexopt à la place. Pour les démarrages suivants, cela ne pose pas de problème, car les applications peuvent être dexoptées en arrière-plan et stockées sur le disque.
Zones d'Android concernées
Cela affecte toutes les applications et bibliothèques Java qui ont des dépendances d'exécution sur d'autres bibliothèques Java. Android compte des milliers d'applications, dont des centaines utilisent des bibliothèques partagées. Les partenaires sont également concernés, car ils possèdent leurs propres bibliothèques et applications.
Modifications destructives
Le système de compilation doit connaître les dépendances <uses-library>
avant de générer des règles de compilation dexpreopt. Toutefois, il ne peut pas accéder directement au fichier manifeste ni lire les tags <uses-library>
qu'il contient, car le système de compilation n'est pas autorisé à lire des fichiers arbitraires lorsqu'il génère des règles de compilation (pour des raisons de performances). De plus, le fichier manifeste peut être inclus dans un APK ou un fichier précompilé. Par conséquent, les informations <uses-library>
doivent être présentes dans les fichiers de compilation (Android.bp
ou Android.mk
).
Auparavant, ART utilisait une solution de contournement qui ignorait les dépendances de la bibliothèque partagée (appelée &-classpath
). Cette solution n'était pas sûre et entraînait des bugs subtils. Elle a donc été supprimée dans Android 12.
Par conséquent, les modules Java qui ne fournissent pas les informations <uses-library>
correctes dans leurs fichiers de compilation peuvent entraîner des échecs de compilation (en raison d'une incompatibilité CLC au moment de la compilation) ou des régression du temps de premier démarrage (en raison d'une incompatibilité CLC au moment du démarrage suivie d'une dexopt).
Chemin de migration
Pour corriger une compilation défaillante :
Désactivez globalement la vérification au moment de la compilation pour un produit spécifique en définissant
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
dans le fichier makefile du produit. Cela corrige les erreurs de compilation (sauf dans les cas particuliers listés dans la section Corriger les problèmes). Toutefois, il s'agit d'une solution de contournement temporaire qui peut entraîner une incompatibilité de CLC au moment du démarrage, suivie d'une dexopt.
Corrigez les modules qui ont échoué avant de désactiver globalement la vérification au moment de la compilation en ajoutant les informations
<uses-library>
nécessaires à leurs fichiers de compilation (pour en savoir plus, consultez Corriger les problèmes). Pour la plupart des modules, cela nécessite d'ajouter quelques lignes dansAndroid.bp
ou dansAndroid.mk
.Désactivez la vérification au moment de la compilation et dexpreopt pour les cas problématiques, au niveau de chaque module. Désactivez dexpreopt pour ne pas perdre de temps de compilation ni d'espace de stockage sur les artefacts qui sont rejetés au démarrage.
Réactivez globalement la vérification au moment de la compilation en supprimant la variable
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES
définie à l'étape 1. La compilation ne devrait pas échouer après cette modification (en raison des étapes 2 et 3).Corrigez les modules que vous avez désactivés à l'étape 3, un par un, puis réactivez dexpreopt et la vérification
<uses-library>
. Signalez les bugs si nécessaire.
Les vérifications <uses-library>
au moment de la compilation sont appliquées dans Android 12.
Résoudre les problèmes de rupture
Les sections suivantes vous expliquent comment résoudre des types spécifiques de problèmes.
Erreur de compilation : CLC non concordant
Le système de compilation effectue une vérification de cohérence au moment de la compilation entre les informations contenues dans les fichiers Android.bp
ou Android.mk
et le fichier manifeste. Le système de compilation ne peut pas lire le fichier manifeste, mais il peut générer des règles de compilation pour le lire (en l'extrayant d'un APK si nécessaire) et comparer les balises <uses-library>
du fichier manifeste aux informations <uses-library>
des fichiers de compilation. En cas d'échec de la vérification, l'erreur ressemble à ceci :
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
Comme le suggère le message d'erreur, il existe plusieurs solutions, selon l'urgence :
- Pour une correction temporaire à l'échelle du produit, définissez
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES := true
dans le fichier makefile du produit. La vérification de cohérence au moment de la compilation est toujours effectuée, mais un échec de la vérification n'entraîne pas un échec de la compilation. Au lieu de cela, un échec de vérification entraîne la rétrogradation du filtre du compilateur dex2oat àverify
dans dexpreopt par le système de compilation, ce qui désactive complètement la compilation AOT pour ce module. - Pour une correction rapide et globale de la ligne de commande, utilisez la variable d'environnement
RELAX_USES_LIBRARY_CHECK=true
. Il a le même effet quePRODUCT_BROKEN_VERIFY_USES_LIBRARIES
, mais est destiné à être utilisé sur la ligne de commande. La variable d'environnement remplace la variable produit. - Pour corriger la cause première de l'erreur, faites en sorte que le système de compilation connaisse les balises
<uses-library>
dans le fichier manifeste. L'examen du message d'erreur indique les bibliothèques qui posent problème (tout comme l'examen deAndroidManifest.xml
ou du fichier manifeste dans un APK qui peut être vérifié avecaapt dump badging $APK | grep uses-library
).
Pour les modules Android.bp
:
Recherchez la bibliothèque manquante dans la propriété
libs
du module. Si elle y figure, Soong ajoute normalement ces bibliothèques automatiquement, sauf dans les cas particuliers suivants :- La bibliothèque n'est pas une bibliothèque SDK (elle est définie comme
java_library
plutôt quejava_sdk_library
). - Le nom de la bibliothèque (dans le fichier manifeste) est différent de celui de son module (dans le système de compilation).
Pour résoudre ce problème temporairement, ajoutez
provides_uses_lib: "<library-name>"
dans la définition de la bibliothèqueAndroid.bp
. Pour une solution à long terme, corrigez le problème sous-jacent : convertissez la bibliothèque en bibliothèque SDK ou renommez son module.- La bibliothèque n'est pas une bibliothèque SDK (elle est définie comme
Si l'étape précédente n'a pas permis de résoudre le problème, ajoutez
uses_libs: ["<library-module-name>"]
pour les bibliothèques requises ouoptional_uses_libs: ["<library-module-name>"]
pour les bibliothèques facultatives à la définitionAndroid.bp
du module. Ces propriétés acceptent une liste de noms de modules. L'ordre relatif des bibliothèques dans la liste doit être le même que dans le fichier manifeste.
Pour les modules Android.mk
:
Vérifiez si le nom de la bibliothèque (dans le fichier manifeste) est différent de son nom de module (dans le système de compilation). Si c'est le cas, corrigez temporairement ce problème en ajoutant
LOCAL_PROVIDES_USES_LIBRARY := <library-name>
dans le fichierAndroid.mk
de la bibliothèque ou en ajoutantprovides_uses_lib: "<library-name>"
dans le fichierAndroid.bp
de la bibliothèque (les deux cas sont possibles, car un moduleAndroid.mk
peut dépendre d'une bibliothèqueAndroid.bp
). Pour une solution à long terme, corrigez le problème sous-jacent : renommez le module de bibliothèque.Ajoutez
LOCAL_USES_LIBRARIES := <library-module-name>
pour les bibliothèques requises etLOCAL_OPTIONAL_USES_LIBRARIES := <library-module-name>
pour les bibliothèques facultatives à la définitionAndroid.mk
du module. Ces propriétés acceptent une liste de noms de modules. L'ordre relatif des bibliothèques dans la liste doit être le même que dans le fichier manifeste.
Erreur de compilation : chemin de bibliothèque inconnu
Si le système de compilation ne trouve pas de chemin d'accès à un fichier JAR DEX <uses-library>
(chemin d'accès au moment de la compilation sur l'hôte ou chemin d'installation sur l'appareil), la compilation échoue généralement. Si aucun chemin n'est trouvé, cela peut indiquer que la bibliothèque est configurée de manière inattendue. Corrigez temporairement le build en désactivant dexpreopt pour le module problématique.
Android.bp (propriétés du module) :
enforce_uses_libs: false,
dex_preopt: {
enabled: false,
},
Android.mk (variables de module) :
LOCAL_ENFORCE_USES_LIBRARIES := false
LOCAL_DEX_PREOPT := false
Créez un rapport de bug pour examiner les scénarios non compatibles.
Erreur de compilation : dépendance de bibliothèque manquante
Si vous essayez d'ajouter <uses-library>
X à partir du fichier manifeste du module Y au fichier de compilation de Y, une erreur de compilation peut se produire en raison de la dépendance manquante X.
Voici un exemple de message d'erreur pour les modules Android.bp :
"Y" depends on undefined module "X"
Voici un exemple de message d'erreur pour les modules 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
Ces erreurs se produisent souvent lorsqu'une bibliothèque porte un nom différent de celui de son module correspondant dans le système de compilation. Par exemple, si l'entrée <uses-library>
du fichier manifeste est com.android.X
, mais que le nom du module de bibliothèque est simplement X
, cela génère une erreur. Pour résoudre ce problème, indiquez au système de compilation que le module nommé X
fournit un <uses-library>
nommé com.android.X
.
Voici un exemple pour les bibliothèques Android.bp
(propriété du module) :
provides_uses_lib: “com.android.X”,
Voici un exemple pour les bibliothèques Android.mk (variable de module) :
LOCAL_PROVIDES_USES_LIBRARY := com.android.X
Incohérence du CLC au moment du démarrage
Au premier démarrage, recherchez dans Logcat les messages liés à une incompatibilité CLC, comme indiqué ci-dessous :
$ adb wait-for-device && adb logcat \
| grep -E 'ClassLoaderContext [a-z ]+ mismatch' -A1
La sortie peut contenir des messages du type suivant :
[...] W system_server: ClassLoaderContext shared library size mismatch Expected=..., found=... (PCL[]... | PCL[]...)
[...] I PackageDexOptimizer: Running dexopt (dexoptNeeded=1) on: ...
Si un avertissement de non-concordance CLC s'affiche, recherchez une commande dexopt pour le module défectueux. Pour résoudre ce problème, assurez-vous que le contrôle au moment de la compilation du module est réussi. Si cela ne fonctionne pas, il s'agit peut-être d'un cas particulier qui n'est pas pris en charge par le système de compilation (par exemple, une application qui charge un autre APK, et non une bibliothèque). Le système de compilation ne gère pas tous les cas, car il est impossible de savoir avec certitude ce que l'application charge au moment de l'exécution.
Contexte du chargeur de classe
Le CLC est une structure arborescente qui décrit la hiérarchie des chargeurs de classe. Le système de compilation utilise CLC au sens strict (il ne couvre que les bibliothèques, et non les APK ni les chargeurs de classes personnalisés) : il s'agit d'une arborescence de bibliothèques qui représente la fermeture transitive de toutes les dépendances <uses-library>
d'une bibliothèque ou d'une application. Les éléments de premier niveau d'un CLC sont les dépendances <uses-library>
directes spécifiées dans le fichier manifeste (le chemin de classe). Chaque nœud d'un arbre CLC est un nœud <uses-library>
qui peut avoir ses propres sous-nœuds <uses-library>
.
Étant donné que les dépendances <uses-library>
sont un graphe orienté acyclique et pas nécessairement un arbre, CLC peut contenir plusieurs sous-arbres pour la même bibliothèque. En d'autres termes, le CLC est le graphique de dépendances "déplié" en arborescence. La duplication n'est qu'au niveau logique. Les chargeurs de classe sous-jacents réels ne sont pas dupliqués (au moment de l'exécution, il existe une seule instance de chargeur de classe pour chaque bibliothèque).
CLC définit l'ordre de recherche des bibliothèques lors de la résolution des classes Java utilisées par la bibliothèque ou l'application. L'ordre de recherche est important, car les bibliothèques peuvent contenir des classes en double. La classe est résolue en fonction de la première correspondance.
CLC sur l'appareil (exécution)
PackageManager
(dans frameworks/base
) crée un CLC pour charger un module Java sur l'appareil. Il ajoute les bibliothèques listées dans les balises <uses-library>
du fichier manifeste du module en tant qu'éléments CLC de premier niveau.
Pour chaque bibliothèque utilisée, PackageManager
récupère toutes ses dépendances <uses-library>
(spécifiées sous forme de tags dans le fichier manifeste de cette bibliothèque) et ajoute un CLC imbriqué pour chaque dépendance. Ce processus se poursuit de manière récursive jusqu'à ce que tous les nœuds feuilles de l'arborescence CLC construite soient des bibliothèques sans dépendances <uses-library>
.
PackageManager
ne connaît que les bibliothèques partagées. La définition de "partagé" dans cet usage diffère de sa signification habituelle (comme dans "partagé" par rapport à "statique"). Dans Android, les bibliothèques partagées Java sont celles listées dans les configurations XML installées sur l'appareil (/system/etc/permissions/platform.xml
). Chaque entrée contient le nom d'une bibliothèque partagée, un chemin d'accès à son fichier JAR DEX et une liste de dépendances (autres bibliothèques partagées que celle-ci utilise au moment de l'exécution et spécifie dans les balises <uses-library>
de son fichier manifeste).
En d'autres termes, deux sources d'informations permettent à PackageManager
de construire le CLC au moment de l'exécution : les balises <uses-library>
dans le fichier manifeste et les dépendances de la bibliothèque partagée dans les configurations XML.
CLC sur l'hôte (au moment de la compilation)
Le CLC n'est pas seulement nécessaire pour charger une bibliothèque ou une application, il l'est également pour en compiler une. La compilation peut se faire sur l'appareil (dexopt) ou pendant la compilation (dexpreopt). Étant donné que dexopt a lieu sur l'appareil, il dispose des mêmes informations que PackageManager
(dépendances des bibliothèques partagées et des fichiers manifestes).
Toutefois, dexpreopt a lieu sur l'hôte et dans un environnement totalement différent. Il doit obtenir les mêmes informations du système de compilation.
Ainsi, le CLC au moment de la compilation utilisé par dexpreopt et le CLC au moment de l'exécution utilisé par PackageManager
sont identiques, mais calculés de deux manières différentes.
Les CLC au moment de la compilation et de l'exécution doivent coïncider. Sinon, le code compilé AOT créé par dexpreopt est refusé. Pour vérifier l'égalité des CLC au moment de la compilation et de l'exécution, le compilateur dex2oat enregistre le CLC au moment de la compilation dans les fichiers *.odex
(dans le champ classpath
de l'en-tête du fichier OAT). Pour trouver le CLC stocké, utilisez la commande suivante :
oatdump --oat-file=<FILE> | grep '^classpath = '
Une incompatibilité de la CLC au moment de la compilation et de l'exécution est signalée dans logcat lors du démarrage. Recherchez-le à l'aide de la commande suivante :
logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'
Un décalage est mauvais pour les performances, car il oblige la bibliothèque ou l'application à être dexoptée ou à s'exécuter sans optimisation (par exemple, le code de l'application peut avoir besoin d'être extrait de l'APK en mémoire, une opération très coûteuse).
Une bibliothèque partagée peut être facultative ou requise. Du point de vue de dexpreopt, une bibliothèque requise doit être présente au moment de la compilation (son absence est une erreur de compilation). Une bibliothèque facultative peut être présente ou absente au moment de la compilation : si elle est présente, elle est ajoutée à la CLC, transmise à dex2oat et enregistrée dans le fichier *.odex
. Si une bibliothèque facultative est absente, elle est ignorée et n'est pas ajoutée à la CLC. En cas d'incohérence entre l'état au moment de la compilation et l'état au moment de l'exécution (la bibliothèque facultative est présente dans un cas, mais pas dans l'autre), les CLC au moment de la compilation et au moment de l'exécution ne correspondent pas, et le code compilé est refusé.
Informations avancées sur le système de compilation (outil de correction des fichiers manifestes)
Il arrive que des balises <uses-library>
manquent dans le fichier manifeste source d'une bibliothèque ou d'une application. Cela peut se produire, par exemple, si l'une des dépendances transitives de la bibliothèque ou de l'application commence à utiliser une autre balise <uses-library>
et que le fichier manifeste de la bibliothèque ou de l'application n'est pas mis à jour pour l'inclure.
Soong peut calculer automatiquement certains des tags <uses-library>
manquants pour une bibliothèque ou une application donnée, en tant que bibliothèques SDK dans la fermeture transitive des dépendances de la bibliothèque ou de l'application. La fermeture est nécessaire, car la bibliothèque (ou l'application) peut dépendre d'une bibliothèque statique qui dépend d'une bibliothèque SDK, et peut-être encore dépendre de manière transitive d'une autre bibliothèque.
Tous les tags <uses-library>
ne peuvent pas être calculés de cette manière, mais lorsque cela est possible, il est préférable de laisser Soong ajouter automatiquement les entrées de fichier manifeste. Cela réduit le risque d'erreur et simplifie la maintenance. Par exemple, lorsque de nombreuses applications utilisent une bibliothèque statique qui ajoute une nouvelle dépendance <uses-library>
, toutes les applications doivent être mises à jour, ce qui est difficile à gérer.