Attiva il multidex per le app con più di 64.000 metodi

Se la tua app ha minSdk API 20 o versioni precedenti e l'app e le librerie a cui fa riferimento superano i 65.536 metodi, ricevi il seguente errore di build che indica che la tua app ha raggiunto il limite dell'architettura di build di Android:

trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.

Le versioni precedenti del sistema di build segnalano un errore diverso, che indica lo stesso problema:

Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536

Queste condizioni di errore mostrano un numero comune: 65536. Questo numero rappresenta il numero totale di riferimenti che possono essere richiamati dal codice all'interno di un singolo file di bytecode Dalvik Executable (DEX). In questa pagina viene spiegato come superare questo limite attivando una configurazione dell'app nota come multidex, che consente all'app di creare e leggere più file DEX.

Informazioni sul limite di riferimento di 64.000

I file delle app per Android (APK) contengono file bytecode eseguibili sotto forma di file Dalvik Executable (DEX), che contengono il codice compilato utilizzato per eseguire l'app. La specifica del file eseguibile Dalvik limita a 65.536 il numero totale di metodi a cui è possibile fare riferimento all'interno di un singolo file DEX, tra cui metodi framework Android, metodi di libreria e metodi nel tuo codice.

Nel contesto dell'informatica, il termine kilo, o K, indica 1024 (o 2^10). Poiché 65.536 è uguale a 64 x 1024, questo limite è indicato come _64.000 limite di riferimento_.

Supporto Multidex versioni precedenti ad Android 5.0

Le versioni della piattaforma precedenti ad Android 5.0 (livello API 21) utilizzano il runtime Dalvik per l'esecuzione del codice dell'app. Per impostazione predefinita, Dalvik limita le app a un singolo file di bytecode classes.dex per APK. Per aggirare questo limite, aggiungi la libreria multidex al file build.gradle o build.gradle.kts a livello di modulo:

trendy

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")
}

Questa libreria diventa parte del file DEX principale dell'app e poi gestisce l'accesso ai file DEX aggiuntivi e al codice che contengono. Per visualizzare le versioni correnti di questa libreria, vedi Versioni multidex.

Per maggiori dettagli, consulta la sezione su come configurare l'app per multidex.

Supporto Multidex per Android 5.0 e versioni successive

Android 5.0 (livello API 21) e versioni successive utilizza un runtime chiamato ART che supporta in modo nativo il caricamento di più file DEX dai file APK. ART esegue la precompilazione al momento dell'installazione dell'app, analizza i file classesN.dex e li compila in un singolo file OAT per l'esecuzione da parte del dispositivo Android. Di conseguenza, se minSdkVersion è 21 o superiore, il multidex è abilitato per impostazione predefinita e non è necessaria la libreria multidex.

Per ulteriori informazioni sul runtime Android 5.0, leggi Android Runtime (ART) e Dalvik.

Nota: quando esegui la tua app con Android Studio, la build è ottimizzata per i dispositivi di destinazione su cui esegui il deployment. Ciò include l'attivazione del multidex quando i dispositivi di destinazione eseguono Android 5.0 o versioni successive. Poiché questa ottimizzazione viene applicata solo quando esegui il deployment dell'app utilizzando Android Studio, potrebbe comunque essere necessario configurare la build di release per multidex per evitare il limite di 64.000.

Evita il limite di 64.000

Prima di configurare l'app per abilitare l'utilizzo di 64.000 o più riferimenti ai metodi, adotta le misure necessarie per ridurre il numero totale di riferimenti chiamati dal codice dell'app, inclusi i metodi definiti dal codice dell'app o dalle librerie incluse.

Le seguenti strategie possono aiutarti a evitare di raggiungere il limite di riferimento DEX:

Esamina le dipendenze dirette e transitive della tua app
Valuta se il valore di qualsiasi dipendenza dalla libreria di grandi dimensioni che includi nell'app supera la quantità di codice aggiunta all'app. Un pattern comune ma problematico è includere una libreria molto grande perché sono stati utili alcuni metodi di utilità. Ridurre le dipendenze del codice dell'app spesso consente di evitare il limite di riferimento DEX.
Rimuovi il codice inutilizzato con R8
Abilita la riduzione del codice per eseguire R8 per le build della release. Attiva la riduzione per assicurarti di non spedire il codice inutilizzato con i tuoi APK. Se la riduzione del codice è configurata correttamente, può anche rimuovere il codice e le risorse inutilizzati dalle dipendenze.

L'utilizzo di queste tecniche può aiutarti a ridurre le dimensioni complessive dell'APK ed evitare la necessità del multidex nella tua app.

Configura la tua app per il multidex

Nota: se il tuo minSdkVersion è impostato su 21 o un numero successivo, il multidex è abilitato per impostazione predefinita e non è necessaria la libreria multidex.

Se minSdkVersion è impostato su 20 o su un valore inferiore, devi utilizzare la libreria multidex e apportare le seguenti modifiche al progetto dell'app:

  1. Modifica il file build.gradle a livello di modulo per abilitare il multidex e aggiungere la libreria multidex come dipendenza, come mostrato qui:

    trendy

    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. A seconda che tu esegua l'override della classe Application, esegui una delle seguenti operazioni:
    • Se non esegui l'override della classe Application, modifica il file manifest in modo da impostare android:name nel tag <application> nel seguente modo:

      <?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>
      
    • Se esegui l'override della classe Application, modificala in modo da estendere MultiDexApplication nel seguente modo:

      Kotlin

      class MyApplication : MultiDexApplication() {...}
      

      Java

      public class MyApplication extends MultiDexApplication { ... }
      
    • Se esegui l'override della classe Application ma non è possibile modificare la classe di base, sostituisci invece il metodo attachBaseContext() e chiama MultiDex.install(this) per attivare 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);
        }
      }
      

      Attenzione: non eseguire MultiDex.install() o qualsiasi altro codice tramite riflessione o JNI prima del completamento di MultiDex.install(). Il tracciamento multidex non seguirà queste chiamate, causando ClassNotFoundException o errori di verifica a causa di una partizione di classe errata tra i file DEX.

Ora, quando crei la tua app, gli strumenti di creazione di Android creano un file DEX principale (classes.dex) e i file DEX di supporto (classes2.dex, classes3.dex e così via), a seconda delle esigenze. Quindi, il sistema di compilazione pacchettizza tutti i file DEX nell'APK.

In fase di runtime, invece di cercare solo nel file classes.dex principale, le API multidex utilizzano uno speciale caricatore di classi per cercare tutti i file DEX disponibili per i tuoi metodi.

Limitazioni della libreria multidex

La libreria multidex ha alcune limitazioni note. Quando incorpori la libreria nella configurazione di compilazione dell'app, considera quanto segue:

  • L'installazione dei file DEX durante l'avvio sulla partizione dati di un dispositivo è complessa e può causare errori ANR (Application Not Responding) se i file DEX secondari sono di grandi dimensioni. Per evitare questo problema, attiva la riduzione del codice per ridurre al minimo le dimensioni dei file DEX e rimuovi le parti di codice non utilizzate.
  • Quando esegui l'esecuzione su versioni precedenti ad Android 5.0 (livello API 21), l'utilizzo del multidex non è sufficiente per aggirare il limite linearalloc (problema 37008143). Questo limite è stato aumentato in Android 4.0 (livello API 14), ma il problema non è stato completamente risolto.

    Nelle versioni precedenti ad Android 4.0, potresti raggiungere il limite linearalloc prima di raggiungere il limite per l'indice DEX. Quindi, se hai scelto come target livelli API inferiori a 14, esegui test accurati su queste versioni della piattaforma, perché la tua app potrebbe presentare problemi all'avvio o quando vengono caricati gruppi specifici di classi.

    La riduzione del codice può ridurre o potenzialmente eliminare questi problemi.

Dichiara le classi richieste nel file DEX principale

Durante la creazione di ogni file DEX per un'app multidex, gli strumenti di creazione eseguono un processo decisionale complesso per determinare quali classi sono necessarie nel file DEX principale in modo che l'app possa avviarsi correttamente. Se nel file DEX principale non viene fornita una classe richiesta durante l'avvio, l'app si arresta in modo anomalo e viene visualizzato l'errore java.lang.NoClassDefFoundError.

Gli strumenti di creazione riconoscono i percorsi di codice del codice a cui si accede direttamente dal codice dell'app. Tuttavia, questo problema può verificarsi quando i percorsi del codice sono meno visibili, ad esempio quando una libreria che utilizzi ha dipendenze complesse. Ad esempio, se il codice utilizza l'introspezione o la chiamata di metodi Java dal codice nativo, queste classi potrebbero non essere riconosciute come necessarie nel file DEX principale.

Se ricevi java.lang.NoClassDefFoundError, devi specificare manualmente le classi aggiuntive richieste nel file DEX principale dichiarandole con la proprietà multiDexKeepProguard nel tipo di build. Se una classe trova una corrispondenza nel file multiDexKeepProguard, viene aggiunta al file DEX principale.

proprietà multiDexKeepProguard

Il file multiDexKeepProguard utilizza lo stesso formato di ProGuard e supporta l'intera grammatica di ProGuard. Per ulteriori informazioni su come personalizzare gli elementi memorizzati nell'app, vedi Personalizzare il codice da conservare.

Il file specificato in multiDexKeepProguard deve contenere opzioni -keep in qualsiasi sintassi di ProGuard valida. Ad esempio, -keep com.example.MyClass.class. Puoi creare un file denominato multidex-config.pro che ha il seguente aspetto:

-keep class com.example.MyClass
-keep class com.example.MyClassToo

Se vuoi specificare tutte le classi di un pacchetto, il file avrà il seguente aspetto:

-keep class com.example.** { *; } // All classes in the com.example package

Quindi puoi dichiarare quel file per un tipo di build come segue:

trendy

android {
    buildTypes {
        release {
            multiDexKeepProguard file('multidex-config.pro')
            ...
        }
    }
}

Kotlin

android {
    buildTypes {
        getByName("release") {
            multiDexKeepProguard = file("multidex-config.pro")
            ...
        }
    }
}

Ottimizza il multidex nelle build di sviluppo

Una configurazione multidex richiede tempi di elaborazione della build notevolmente maggiori, perché il sistema di compilazione deve prendere decisioni complesse su quali classi devono essere incluse nel file DEX principale e su quali classi possono essere incluse nei file DEX secondari. Ciò significa che le build incrementali che utilizzano multidex richiedono in genere più tempo e possono rallentare il processo di sviluppo.

Per ridurre i tempi di build incrementali più lunghi, usa il pre-dexing per riutilizzare l'output multidex tra le build. Il pre-dexing si basa su un formato ART disponibile solo su Android 5.0 (livello API 21) e versioni successive. Se usi Android Studio, l'IDE utilizza automaticamente il pre-dexing durante il deployment dell'app su un dispositivo con Android 5.0 (livello API 21) o versioni successive. Tuttavia, se esegui build Gradle dalla riga di comando, devi impostare minSdkVersion su 21 o su un valore superiore per abilitare il pre-dexing.

Per mantenere le impostazioni della build di produzione, puoi creare due versioni dell'app utilizzando le versioni di prodotto (una versione con una versione di sviluppo e una versione con una versione di release), con valori diversi per minSdkVersion, come mostrato di seguito:

trendy

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")
}

Per scoprire altre strategie che contribuiscono a migliorare la velocità delle build da Android Studio o dalla riga di comando, leggi l'articolo Ottimizzare la velocità della build. Per maggiori informazioni sull'utilizzo delle varianti di build, consulta Configurare le varianti di build.

Suggerimento: se hai varianti di build diverse per esigenze multidex diverse, puoi fornire un file manifest diverso per ogni variante, in modo che il nome del tag <application> venga modificato solo dal file per il livello API 20 e livelli precedenti. Puoi anche creare una sottoclasse Application diversa per ogni variante, in modo che solo la sottoclasse per il livello API 20 e livelli inferiori estende la classe MultiDexApplication o le chiamate MultiDex.install(this).

Testa app multidex

Quando scrivi test di strumentazione per app multidex, non è necessaria alcuna configurazione aggiuntiva se utilizzi una strumentazione MonitoringInstrumentation o AndroidJUnitRunner. Se utilizzi un altro Instrumentation, devi sostituire il relativo metodo onCreate() con il codice seguente:

Kotlin

fun onCreate(arguments: Bundle) {
  MultiDex.install(targetContext)
  super.onCreate(arguments)
  ...
}

Java

public void onCreate(Bundle arguments) {
  MultiDex.install(getTargetContext());
  super.onCreate(arguments);
  ...
}