Android 12 apporte des modifications au système de compilation pour 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 pannes et suivez les recettes qui y figurent pour les résoudre et les atténuer.
Dexpreopt est le processus de compilation anticipée des bibliothèques et applications Java. Dexpreopt s'effectue sur l'hôte au moment de la compilation (contrairement à dexopt, qui s'effectue sur l'appareil). La structure des dépendances de bibliothèque partagée utilisées par un module Java (une bibliothèque ou une application) est appelée contexte de chargeur de classe (CLC). Pour garantir la validité de dexpreopt, les CLC au moment de la compilation et de l'exécution doivent coïncider. Le CLC au moment de la compilation est ce que le compilateur dex2oat utilise au moment de la compilation dexpreopt (il est enregistré dans les fichiers ODEX), et le 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 des raisons de correction, 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 entraîne des bugs subtils au moment de l'exécution. Les performances sont également affectées par les vérifications d'exécution des 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 un décalage 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 n'est pas un problème, car les applications peuvent être désoptimisées en arrière-plan et stockées sur le disque.
Composants Android concernés
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 ont 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 et lire les balises <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 empaqueté dans un APK ou un 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 un correctif qui ignorait les dépendances de bibliothèque partagée (appelées &-classpath
). Cette solution n'était pas sécurisée et causait des bugs subtils. Elle a donc été supprimée dans Android 12.
Par conséquent, les modules Java qui ne fournissent pas d'informations <uses-library>
correctes dans leurs fichiers de compilation peuvent entraîner des erreurs de compilation (causées par un décalage de CLC au moment de la compilation) ou des régressions au premier démarrage (causées par un décalage de CLC au moment du démarrage suivi de dexopt).
Chemin de migration
Pour corriger un build défectueux, procédez comme suit:
Désactiver globalement la vérification au moment de la compilation pour un produit spécifique à l'aide d'un paramètre
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 erreurs). Toutefois, il s'agit d'une solution temporaire qui peut entraîner un décalage CLC au démarrage, suivi de dexopt.
Corrigez les modules qui ont échoué avant d'avoir désactivé 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 la section Corriger les erreurs). Pour la plupart des modules, cela nécessite d'ajouter quelques lignes dansAndroid.bp
ouAndroid.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 gaspiller du temps de compilation et de l'espace de stockage sur des artefacts qui sont rejetés au démarrage.
Réactivez globalement la vérification au moment de la compilation en désactivant
PRODUCT_BROKEN_VERIFY_USES_LIBRARIES
, qui était défini à l'étape 1. La compilation ne devrait pas échouer après ce changement (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 non-fonctionnement
Les sections suivantes vous expliquent comment corriger des types de pannes spécifiques.
Erreur de compilation: incohérence CLC
Le système de compilation effectue une vérification de cohérence au moment de la compilation entre les informations des 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 se présente comme suit:
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, en fonction de 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 la cohérence au moment de la compilation est toujours effectuée, mais un échec de vérification ne signifie pas un échec de compilation. Au lieu de cela, un échec de vérification fait que le système de compilation rétrograde le filtre du compilateur dex2oat versverify
dans dexpreopt, ce qui désactive entièrement 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 de produit. - Pour résoudre la cause de l'erreur, informez le système de compilation des balises
<uses-library>
dans le fichier manifeste. Une inspection du message d'erreur indique les bibliothèques à l'origine du problème (comme l'inspection deAndroidManifest.xml
ou du fichier manifeste dans un APK pouvant ê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 c'est le cas, Soong ajoute normalement ces bibliothèques automatiquement, sauf dans les cas suivants:- La bibliothèque n'est pas une bibliothèque de 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 son nom de 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 de SDK ou renommez son module.- La bibliothèque n'est pas une bibliothèque de 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 celui du 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-le temporairement 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 d'accès à la bibliothèque inconnu
Si le système de compilation ne parvient pas à trouver un 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. L'échec de la recherche d'un chemin d'accès 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
Envoyez un bug pour examiner les scénarios non pris en charge.
Erreur de compilation: dépendance de bibliothèque manquante
Une tentative d'ajout de <uses-library>
X à partir du fichier manifeste du module Y au fichier de compilation pour Y peut entraîner une erreur de compilation 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
Une source courante de ces erreurs est lorsqu'une bibliothèque est nommée différemment 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
, une erreur se produit. 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é de 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 contrôleur en boucle fermée au démarrage
Au premier démarrage, recherchez dans Logcat les messages liés à la non-concordance CLC, comme indiqué ci-dessous:
$ adb wait-for-device && adb logcat \
| grep -E 'ClassLoaderContext [a-z ]+ mismatch' -A1
La sortie peut contenir des messages de la forme suivante:
[...] 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 la vérification au moment de la compilation du module est réussie. Si cela ne fonctionne pas, il est possible que votre cas soit particulier et non 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 au moment de la compilation, 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 un CLC au sens strict (il ne couvre que les bibliothèques, pas les APK ni les chargeurs de classe personnalisés): il s'agit d'un arbre 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 niveau supérieur d'un CLC sont les dépendances <uses-library>
directes spécifiées dans le fichier manifeste (le chemin d'accès aux classes). Chaque nœud d'un arbre CLC est un nœud <uses-library>
pouvant avoir ses propres sous-nœuds <uses-library>
.
Étant donné que les dépendances <uses-library>
sont un graphe orienté acyclique et non nécessairement un arbre, le CLC peut contenir plusieurs sous-arbres pour la même bibliothèque. En d'autres termes, le CLC est le graphique des dépendances "déplié" en arbre. La duplication ne se produit qu'au niveau logique. Les chargeurs de classe sous-jacents réels ne sont pas dupliqués (au moment de l'exécution, il n'y a qu'une seule instance de chargeur de classe pour chaque bibliothèque).
Le 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, et la classe est résolue en fonction de la première correspondance.
CLC sur l'appareil (temps d'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 niveau supérieur.
Pour chaque bibliothèque utilisée, PackageManager
obtient toutes ses dépendances <uses-library>
(spécifiées en tant que balises 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 de feuilles de l'arbre CLC construit soient des bibliothèques sans dépendances <uses-library>
.
PackageManager
n'est conscient que des bibliothèques partagées. La définition de "partagé" dans cet usage diffère de son sens habituel (par exemple, partagé par rapport à statique). Sous 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écifiées dans les balises <uses-library>
de son fichier manifeste).
En d'autres termes, il existe deux sources d'informations qui permettent à PackageManager
de créer des CLC au moment de l'exécution: les balises <uses-library>
dans le fichier manifeste et les dépendances de bibliothèque partagées dans les configurations XML.
CLC sur l'hôte (au moment de la compilation)
Le CLC n'est pas seulement nécessaire lors du chargement d'une bibliothèque ou d'une application, mais également lors de la compilation. La compilation peut se produire sur l'appareil (dexopt) ou lors de la compilation (dexpreopt). Comme dexopt se produit sur l'appareil, il dispose des mêmes informations que PackageManager
(fichiers manifestes et dépendances de bibliothèques partagées).
Dexpreopt, cependant, se déroule sur l'hôte et dans un environnement totalement différent. Il doit obtenir les mêmes informations auprès du système de compilation.
Par conséquent, 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 au moment 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 = '
Un décalage entre le CLC au moment de la compilation et au moment de l'exécution est signalé dans logcat au démarrage. Recherchez-le à l'aide de la commande suivante:
logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'
Les différences sont mauvaises pour les performances, car elles obligent la bibliothèque ou l'application à être déxoptée ou à s'exécuter sans optimisations (par exemple, le code de l'application peut devoir être extrait en mémoire de l'APK, une opération très coûteuse).
Une bibliothèque partagée peut être facultative ou obligatoire. 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 au 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. Si l'état au moment de la compilation et de l'exécution ne correspond pas (la bibliothèque facultative est présente dans un cas, mais pas dans l'autre), les CLC au moment de la compilation et de l'exécution ne correspondent pas et le code compilé est rejeté.
Détails du système de compilation avancé (outil de correction de 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 certaines des balises <uses-library>
manquantes pour une bibliothèque ou une application donnée, comme les bibliothèques de SDK dans la fermeture de dépendance transitive 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 de SDK, et peut-être à nouveau dépendre de manière transitive via une autre bibliothèque.
Toutes les balises <uses-library>
ne peuvent pas être calculées de cette manière, mais dans la mesure du possible, il est préférable de laisser Soong ajouter automatiquement des entrées de fichier manifeste. Cela réduit le risque d'erreurs 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.