Lorsque le minSdk
d'API de votre application est égal ou inférieur à 20 et que votre application et les bibliothèques auxquelles elle fait référence dépassent 65 536 méthodes, une erreur de compilation vous indique que l'application a atteint la limite de l'architecture de compilation Android :
trouble writing output: Too many field references: 131000; max is 65536. You may try using --multi-dex option.
Les anciennes versions du système de compilation signalent une autre erreur, mais qui fait référence au même problème :
Conversion to Dalvik format failed: Unable to execute dex: method ID not in [0, 0xffff]: 65536
Ces deux messages d'erreur affichent le même nombre : 65536. Il s'agit du nombre total de références pouvant être appelées par le code dans un seul fichier bytecode DEX (Dalvik Executable). Cette page explique comment contourner cette limite en activant une configuration appelée multidex, qui permet à votre application de créer et de lire plusieurs fichiers DEX.
À propos de la limite de 64 000 références
Les fichiers d'application Android (APK) contiennent des fichiers bytecode exécutables sous la forme de fichiers Dalvik Executable (DEX), qui incluent le code compilé permettant d'exécuter votre application. La norme DEX limite le nombre total de méthodes pouvant être référencées dans un seul fichier DEX à 65 536, ce qui comprend les méthodes de framework Android, celles des bibliothèques et celles de votre propre code.
Dans un contexte informatique, le terme kilo (k) est équivalent à 1 024 unités (ou 2^10 unités). Puisque 65 536 correspond à 64 × 1 024, cette limite est appelée "limite des 64 000 références".Prise en charge de multidex avant Android 5.0
Les versions de la plate-forme antérieures à Android 5.0 (niveau d'API 21) utilisent l'environnement Dalvik pour exécuter le code de l'application. Par défaut, Dalvik limite les applications à un seul fichier bytecode classes.dex
par APK. Pour contourner cette limitation, ajoutez la bibliothèque multidex au fichier build.gradle
ou build.gradle.kts
au niveau du module :
Groovy
dependencies { def multidex_version = "2.0.1" implementation "androidx.multidex:multidex:$multidex_version" }
Kotlin
dependencies { val multidex_version = "2.0.1" implementation("androidx.multidex:multidex:$multidex_version") }
Cette bibliothèque fait alors partie du fichier DEX principal de votre application. Elle gère l'accès aux fichiers DEX supplémentaires ainsi qu'au code qu'ils contiennent. Pour les versions actuelles de cette bibliothèque, reportez-vous à la page des versions de multidex.
Pour en savoir plus, consultez la section Configurer votre application pour multidex.Prise en charge de multidex pour Android 5.0 ou version ultérieure
Android 5.0 (niveau d'API 21) et les versions ultérieures utilisent un environnement d'exécution appelé ART, qui permet de charger de manière native plusieurs fichiers DEX à partir de fichiers APK. ART effectue une précompilation au moment de l'installation de l'application, en recherchant les fichiers classesN.dex
et en les compilant dans un seul fichier OAT qui sera exécuté par l'appareil Android. Par conséquent, si votre minSdkVersion
est égale ou supérieure à 21, multidex est activé par défaut et vous n'avez pas besoin de la bibliothèque multidex.
Pour en savoir plus sur l'environnement d'exécution Android 5.0, consultez Android Runtime (ART) et Dalvik.
Remarque : Lorsque vous exécutez votre application avec Android Studio, le build est optimisé pour les appareils cibles sur lesquels vous effectuez le déploiement. Cela comprend l'activation de multidex lorsque les appareils cibles sont équipés d'Android 5.0 ou version ultérieure. Étant donné que cette optimisation n'est appliquée que lors du déploiement de votre application à l'aide d'Android Studio, vous devrez peut-être encore configurer votre build pour multidex afin d'éviter la limite des 64 000.
Éviter la limite des 64 000
Avant de configurer votre application pour contourner la limite des 64 000 références de méthode, vous devez réduire le nombre total de références appelées par le code de votre application, y compris les méthodes définies par celui-ci ou par les bibliothèques incluses.
Les stratégies suivantes peuvent vous aider à rester en dessous de la limite fixée par la norme DEX :
- Vérifier les dépendances directes et transitives de votre application
- Déterminez si les dépendances de bibliothèque volumineuse que vous incluez dans l'application dépassent la quantité de code ajoutée à l'application. Il est courant d'inclure une bibliothèque très volumineuse et de n'en utiliser que quelques méthodes, mais cette approche pose problème. Réduire les dépendances du code de votre application permet souvent de rester en dessous de la limite des références DEX.
- Supprimer le code inutilisé avec R8
- Activez la minification de code pour exécuter R8 sur vos builds. Cela vous permet de ne pas transmettre de code inutilisé avec vos APK. Si elle est correctement configurée, l'opération de minification peut également supprimer le code et les ressources inutilisés de vos dépendances.
Ces techniques peuvent vous aider à éviter d'activer multidex dans votre application tout en réduisant la taille globale de votre APK.
Configurer votre application pour multidex
Remarque : Si votreminSdkVersion
est définie sur 21 ou plus, multidex est activé par défaut et vous n'avez pas besoin de la bibliothèque associée.
Toutefois, si votre minSdkVersion
est défini sur 20 ou moins, vous devez utiliser la bibliothèque multidex et apporter les modifications suivantes à votre projet :
-
Modifiez le fichier
build.gradle
au niveau du module pour activer multidex et ajoutez la bibliothèque associée en tant que dépendance, comme indiqué ci-dessous :Groovy
android { defaultConfig { ... minSdkVersion 15 targetSdkVersion 33 multiDexEnabled true } ... } dependencies { implementation "androidx.multidex:multidex:2.0.1" }
Kotlin
android { defaultConfig { ... minSdk = 15 targetSdk = 33 multiDexEnabled = true } ... } dependencies { implementation("androidx.multidex:multidex:2.0.1") }
- Selon que vous forcez ou non la classe
Application
, effectuez l'une des opérations suivantes :Si vous ne forcez pas la classe
Application
, modifiez le fichier manifeste pour définirandroid:name
dans la balise<application>
comme suit :<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.myapp"> <application android:name="androidx.multidex.MultiDexApplication" > ... </application> </manifest>
Si vous forcez la classe
Application
, modifiez-la pour étendreMultiDexApplication
comme suit :Kotlin
class MyApplication : MultiDexApplication() {...}
Java
public class MyApplication extends MultiDexApplication { ... }
Si vous forcez la classe
Application
, mais qu'il n'est pas possible de modifier la classe de base, vous pouvez contourner le problème en forçant la méthodeattachBaseContext()
et en appelantMultiDex.install(this)
pour activer multidex :Kotlin
class MyApplication : SomeOtherApplication() { override fun attachBaseContext(base: Context) { super.attachBaseContext(base) MultiDex.install(this) } }
Java
public class MyApplication extends SomeOtherApplication { @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this); } }
Attention : N'exécutez pas
MultiDex.install()
ni aucun autre code par réflexion ou JNI avant la fin deMultiDex.install()
. Le traçage multidex ne suivra pas ces appels, ce qui entraînera une exceptionClassNotFoundException
ou une erreur de vérification en raison d'une partition de classe incorrecte entre les fichiers DEX.
Désormais, lorsque vous compilez votre application, les outils de compilation Android créent un fichier DEX principal (classes.dex
) et en prennent d'autres en charge (classes2.dex
, classes3.dex
, etc.) selon vos besoins.
Le système de compilation crée ensuite un package avec l'ensemble des fichiers DEX et l'ajoute à votre APK.
Lors de l'exécution, au lieu de rechercher uniquement le fichier classes.dex
principal, les API multidex utilisent un chargeur de classe spécial afin de rechercher tous les fichiers DEX disponibles pour vos méthodes.
Limites de la bibliothèque multidex
La bibliothèque multidex présente certaines limites connues. Lorsque vous l'intégrez à la configuration de compilation de votre application, tenez compte des points suivants :
- L'installation de fichiers DEX au démarrage sur la partition de données d'un appareil est complexe et peut entraîner des erreurs de type "L'application ne répond pas" (ANR) si les fichiers DEX secondaires sont volumineux. Pour éviter ce problème, activez la minification de code. Cela aura pour effet de réduire la taille des fichiers DEX et de supprimer les portions de code inutiles.
- Sur des versions antérieures à Android 5.0 (niveau d'API 21), l'utilisation de multidex ne suffit pas à contourner la limite linearalloc (problème 37008143). Cette limite a été augmentée dans Android 4.0 (niveau d'API 14), mais le problème n'a pas été complètement résolu.
Sur les versions antérieures à Android 4.0, vous pouvez atteindre la limite linearalloc avant d'atteindre la limite d'index DEX. Par conséquent, si vous ciblez des niveaux d'API inférieurs à 14, effectuez des tests approfondis sur ces versions, car votre application peut rencontrer des problèmes au démarrage ou lors du chargement de groupes de classes spécifiques.
La minification de code permet de réduire, voire d'éliminer, ces problèmes.
Déclarer les classes requises dans le fichier DEX principal
Dans le cadre d'une application multidex, lors de la compilation de chaque fichier DEX, les outils de compilation prennent des décisions complexes pour déterminer les classes qui doivent être ajoutées au fichier DEX principal. Cela a pour but d'éviter tout problème au démarrage de l'application. Si une classe requise au démarrage n'est pas fournie dans le fichier DEX principal, votre application plante et l'erreur java.lang.NoClassDefFoundError
apparaît.
Les outils de compilation reconnaissent les chemins du code directement accessible à partir du code de votre application. Toutefois, ce problème peut survenir lorsque les chemins de code sont moins visibles, par exemple quand une bibliothèque que vous utilisez repose sur des dépendances complexes. Par exemple, si le code utilise l'introspection ou l'appel de méthodes Java à partir de code natif, il est possible que ces classes ne soient pas reconnues comme obligatoires dans le fichier DEX principal.
Si vous recevez une erreur java.lang.NoClassDefFoundError
, vous devez spécifier manuellement ces classes supplémentaires dans le fichier DEX principal en les déclarant avec la propriété multiDexKeepProguard
dans votre type de compilation. Si une classe figure dans le fichier multiDexKeepProguard
, elle est ajoutée au fichier DEX principal.
Propriété multiDexKeepProguard
Le fichier multiDexKeepProguard
utilise le même format que ProGuard et accepte toute la syntaxe associée. Pour savoir comment personnaliser ce qui est conservé dans votre application, consultez Personnaliser le code à conserver.
Le fichier que vous spécifiez dans multiDexKeepProguard
doit contenir des options -keep
dans une syntaxe ProGuard valide. Par exemple, -keep com.example.MyClass.class
. Vous pouvez créer un fichier appelé multidex-config.pro
qui se présente comme suit :
-keep class com.example.MyClass -keep class com.example.MyClassToo
Si vous souhaitez spécifier toutes les classes d'un package, le fichier se présente comme suit :
-keep class com.example.** { *; } // All classes in the com.example package
Vous pouvez ensuite déclarer ce fichier pour un type de compilation comme indiqué ci-dessous :
Groovy
android { buildTypes { release { multiDexKeepProguard file('multidex-config.pro') ... } } }
Kotlin
android { buildTypes { getByName("release") { multiDexKeepProguard = file("multidex-config.pro") ... } } }
Optimiser multidex dans les compilations de développement
Une configuration multidex nécessite un temps de traitement de compilation considérablement plus long, car le système doit prendre des décisions complexes concernant les classes à inclure dans le fichier DEX principal et celles pouvant être incluses dans des fichiers secondaires. Cela signifie que les compilations incrémentielles utilisant multidex prennent généralement plus de temps et peuvent ralentir votre processus de développement.
Pour accélérer les compilations incrémentielles, vous devez utiliser la conversion au format .dex pour réutiliser la sortie multidex entre les compilations.
Cette conversion repose sur un format ART disponible uniquement sous Android 5.0 (niveau d'API 21) ou version ultérieure. Si vous utilisez Android Studio, l'IDE a automatiquement recours à la conversion au format .dex lorsque vous déployez votre application sur un appareil équipé d'Android 5.0 (niveau d'API 21) ou version ultérieure.
Toutefois, si vous lancez des compilations Gradle à partir de la ligne de commande, vous devez définir minSdkVersion
sur 21 ou plus pour activer la conversion au format .dex.
minSdkVersion
, comme indiqué ci-dessous :
Groovy
android { defaultConfig { ... multiDexEnabled true // The default minimum API level you want to support. minSdkVersion 15 } productFlavors { // Includes settings you want to keep only while developing your app. dev { // Enables pre-dexing for command-line builds. When using // Android Studio 2.3 or higher, the IDE enables pre-dexing // when deploying your app to a device running Android 5.0 // (API level 21) or higher, regardless of minSdkVersion. minSdkVersion 21 } prod { // If you've configured the defaultConfig block for the production version of // your app, you can leave this block empty and Gradle uses configurations in // the defaultConfig block instead. You still need to include this flavor. // Otherwise, all variants use the "dev" flavor configurations. } } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { implementation "androidx.multidex:multidex:2.0.1" }
Kotlin
android { defaultConfig { ... multiDexEnabled = true // The default minimum API level you want to support. minSdk = 15 } productFlavors { // Includes settings you want to keep only while developing your app. create("dev") { // Enables pre-dexing for command-line builds. When using // Android Studio 2.3 or higher, the IDE enables pre-dexing // when deploying your app to a device running Android 5.0 // (API level 21) or higher, regardless of minSdkVersion. minSdk = 21 } create("prod") { // If you've configured the defaultConfig block for the production version of // your app, you can leave this block empty and Gradle uses configurations in // the defaultConfig block instead. You still need to include this flavor. // Otherwise, all variants use the "dev" flavor configurations. } } buildTypes { getByName("release") { isMinifyEnabled = true proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") } } } dependencies { implementation("androidx.multidex:multidex:2.0.1") }
Pour en savoir plus sur les stratégies permettant d'accélérer la compilation (à partir d'Android Studio ou de la ligne de commande), consultez Optimiser la vitesse de compilation. Pour en savoir plus sur l'utilisation des variantes de compilation, consultez Configurer des variantes de compilation.
Astuce : Si vous avez différentes variantes de compilation pour différents besoins multidex, vous pouvez fournir un fichier manifeste distinct pour chaque variante afin que seul le fichier des niveaux d'API 20 et inférieurs modifie le nom de la balise <application>
. Vous pouvez également créer une autre sous-classe Application
pour chaque variante afin que seule la sous-classe des niveaux d'API 20 et inférieurs étende la classe MultiDexApplication
ou appelle MultiDex.install(this)
.
Tester les applications multidex
Lorsque vous écrivez des tests d'instrumentation pour des applications multidex, aucune configuration supplémentaire n'est requise si vous utilisez une instrumentation MonitoringInstrumentation
ou AndroidJUnitRunner
. Si vous utilisez une autre Instrumentation
, vous devez forcer sa méthode onCreate()
à l'aide du code suivant :
Kotlin
fun onCreate(arguments: Bundle) { MultiDex.install(targetContext) super.onCreate(arguments) ... }
Java
public void onCreate(Bundle arguments) { MultiDex.install(getTargetContext()); super.onCreate(arguments); ... }