Activer multidex pour les applications comportant plus de 64 000 méthodes

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 permettre l'utilisation de 64 000 références de méthode ou plus, prenez des mesures pour 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 le code de votre application 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 votre minSdkVersion 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 :

  1. 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")
    }
    
  2. 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éfinir android: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 étendre MultiDexApplication 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éthode attachBaseContext() et en appelant MultiDex.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 de MultiDex.install(). Le traçage multidex ne suivra pas ces appels, ce qui entraînera une exception ClassNotFoundException 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.

Pour conserver les paramètres de votre compilation de production, vous pouvez créer deux versions de votre application à l'aide de types de produit : une version pour le développement et une autre pour la version définitive, avec des valeurs différentes pour 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);
  ...
}