Cómo habilitar multidex para apps con más de 64K métodos

Cuando tu app y las bibliotecas a las que hace referencia superan los 65,536 métodos, se produce un error de compilación que indica que tu app alcanzó el límite de la arquitectura de compilación de Android:

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

Las versiones anteriores del sistema de compilación informan un error diferente, lo cual indica el mismo problema:

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

Ambas condiciones de error muestran un número común: 65536. Este número representa el número total de referencias que el código puede invocar en un mismo archivo de código de byte Dalvik Executable (DEX). En esta página, se explica la manera de superar este límite mediante la habilitación de una configuración de app conocida como multidex, que permite que tu app compile y lea varios archivos DEX.

Información sobre el límite de referencia de 64K

Los archivos de las apps para Android (APK) contienen archivos de códigos de byte ejecutables con formato de archivos Dalvik Executable (DEX), que tienen el código compilado empleado para ejecutar tu app. La especificación de Dalvik Executable limita la cantidad total de métodos a los que se puede hacer referencia en un archivo DEX a 65,536, incluidos los métodos de marco de trabajo de Android, de biblioteca y de tu propio código. En el contexto de la informática, el término Kilo, K, denota 1024 (o 2^10). Como 65,536 es igual a 64 X 1024, este límite se denomina "límite de referencia de 64K".

Compatibilidad de multidex con versiones anteriores a Android 5.0

Las versiones de la plataforma anteriores a Android 5.0 (nivel de API 21) usan el tiempo de ejecución de Dalvik para ejecutar código de apps. De forma predeterminada, Dalvik limita las apps a un solo archivo de código de bytes classes.dex por APK. Para evitar este límite, puedes agregar la biblioteca de compatibilidad de multidex a tu proyecto:

    dependencies {
        implementation 'androidx.multidex:multidex:2.0.1'
    }
       

Si no estás utilizando AndroidX, agrega la siguiente dependencia de biblioteca de compatibilidad en su lugar:

    dependencies {
      implementation 'com.android.support:multidex:1.0.3'
    }
    

Esta biblioteca pasa a formar parte del archivo DEX principal de tu app y, luego, administra el acceso a los archivos DEX adicionales y al código que contienen. En la sección incluida a continuación, hay más detalles sobre cómo configurar tu app para multidex.

Nota: Si tu proyecto está configurado para multidex con minSdkVersion 20 o una versión anterior, y realizas la implementación en dispositivos con Android 4.4 (nivel de API 20) o una versión anterior, Android Studio inhabilita Instant Run.

Compatibilidad de multidex con Android 5.0 y versiones posteriores

Android 5.0 (nivel de API 21) y las versiones posteriores usan un tiempo de ejecución denominado ART, que admite de manera nativa la carga de varios archivos DEX desde archivos APK. Durante la instalación de la app, ART realiza una compilación preliminar en la que se buscan archivos classesN.dex y los compila en un solo archivo .oat para que el dispositivo Android lo ejecute. De esta manera, si tu minSdkVersion es la 21 o una posterior, no necesitas la biblioteca de compatibilidad de multidex.

Para obtener más información sobre el tiempo de ejecución de Android 5.0, consulta ART y Dalvik.

Nota: Mientras usas Instant Run, Android Studio configura automáticamente tu app para multidex cuando minSdkVersion en tu app se establece en 21 o más. Como Instant Run solo funciona con la versión de depuración de tu app, debes de todos modos configurar tu compilación de lanzamiento para multidex si deseas evitar el límite de 64K.

Evita el límite de 64K

Antes de configurar tu app para habilitar el uso de 64K o más referencias de métodos, debes tomar medidas para reducir la cantidad total de referencias a las que llama el código de tu app, como los métodos definidos por el código de esta o las bibliotecas incluidas. Las siguientes estrategias pueden ayudarte a evitar alcanzar el límite de referencia de DEX:

  • Revisa las dependencias directa y transitiva de tu app: asegúrate de que cualquier dependencia de biblioteca grande que incluyas en tu app se use de modo que se supere el volumen de código que se agrega a la app. Un antipatrón común comprende la inclusión de una biblioteca muy grande porque unos pocos métodos de utilidades resultan útiles. Reducir las dependencias de código de tu app a menudo puede ayudar a evitar el límite de referencia de DEX.
  • Quita el código que no se use con ProGuard: habilita la reducción de código para ejecutar ProGuard en tus compilaciones de versiones. Habilitar la reducción garantiza que no envíes código sin usar con tus APK.

Estas técnicas pueden ayudarte a evitar la necesidad de habilitar multidex en tu app y, al mismo tiempo, reducir el tamaño general de tu APK.

Configura tu app para multidex

Para configurar tu proyecto de app de modo que use una configuración de multidex, debes hacer las siguientes modificaciones, según la versión mínima de Android que admita tu app.

Si tu minSdkVersion se estableció en 21 o un valor superior, lo único que debes hacer es establecer multiDexEnabled en true en tu archivo build.gradle de nivel de módulo, como se muestra a continuación:

    android {
        defaultConfig {
            ...
            minSdkVersion 21
            targetSdkVersion 28
            multiDexEnabled true
        }
        ...
    }
    

Sin embargo, si tu minSdkVersion se estableció en 20 o un valor inferior, debes usar la biblioteca de compatibilidad de multidex, como se indica a continuación:

  1. Modifica el archivo build.gradle de nivel de módulo para habilitar multidex y agregar la biblioteca de multidex como dependencia, como se muestra a continuación:

        android {
            defaultConfig {
                ...
                minSdkVersion 15
                targetSdkVersion 28
                multiDexEnabled true
            }
            ...
        }
    
        dependencies {
          compile 'com.android.support:multidex:1.0.3'
        }
        
  2. Según anules la clase Application o no, haz una de las siguientes acciones:
    • Si no anulas la clase Application, modifica tu archivo de manifiesto para establecer android:name en la etiqueta <application> como se muestra a continuación:

          <?xml version="1.0" encoding="utf-8"?>
          <manifest xmlns:android="http://schemas.android.com/apk/res/android"
              package="com.example.myapp">
              <application
                      android:name="android.support.multidex.MultiDexApplication" >
                  ...
              </application>
          </manifest>
          
    • Si anulas la clase Application, debes cambiarla para extender MultiDexApplication (si es posible) como se muestra a continuación:

      Kotlin

          class MyApplication : MultiDexApplication() {...}
          

      Java

          public class MyApplication extends MultiDexApplication { ... }
          
    • Por otro lado, si anulas la clase Application y no puedes cambiar la clase básica, como alternativa puedes anular el método attachBaseContext() y llamar a MultiDex.install(this) para habilitar 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);
            }
          }
          

      Precaución: No ejecutes MultiDex.install() ni ningún otro código por medio de reflexión o JNI antes de que se complete MultiDex.install(). El seguimiento de multidex no seguirá esas llamadas, lo que provocará errores de ClassNotFoundException o de verificación debido a una partición de clase incorrecta entre archivos DEX.

Cuando compilas tu app, las herramientas de compilación de Android crean un archivo DEX principal (classes.dex) y archivos DEX de respaldo (classes2.dex, classes3.dex, etc.) según sea necesario. El sistema de compilación luego empaqueta todos los archivos DEX en tu APK.

En el tiempo de ejecución, las API de multidex usan un cargador de clase especial para buscar tus métodos en todos los archivos DEX disponibles (en lugar de hacerlo solo en el archivo classes.dex principal).

Limitaciones de la biblioteca de compatibilidad de multidex

La biblioteca de compatibilidad de multidex tiene algunas limitaciones conocidas que debes tener en cuenta y para las cuales debes realizar pruebas cuando la incorpores en la configuración de compilación de tu app:

  • La instalación de archivos DEX durante el inicio en una partición de datos del dispositivo es compleja y puede generar errores de "Aplicación no responde" (ANR) si los archivos DEX secundarios son grandes. En ese caso, debes aplicar una reducción de código mediante Proguard para minimizar el tamaño de los archivos DEX y quitar las partes de código no usadas.
  • Cuando ejecutas versiones anteriores a Android 5.0 (nivel de API 21), el uso de multidex no es suficiente para evitar el límite linearalloc (problema 78035). Este límite se incrementó en Android 4.0 (nivel de API 14), pero no lo resolvió por completo. Y en las versiones anteriores a Android 4.0, es posible que alcances el límite de linearalloc antes de alcanzar el límite del índice DEX. Si apuntas a niveles de API anteriores al 14, haz una prueba minuciosa con estas versiones de la plataforma, ya que tu app puede tener problemas en el inicio o cuando se cargan grupos de clases particulares.

    La reducción de código puede disminuir o, quizá, eliminar esos problemas.

Declara las clases requeridas en el archivo DEX principal

Al compilar cada archivo DEX para una app multidex, las herramientas de compilación toman decisiones complejas para determinar las clases que se necesitan en el archivo DEX principal de modo que tu app pueda iniciarse con éxito. Si no se suministra alguna de las clases requeridas durante el inicio en el archivo DEX principal, tu app se bloquea con el error java.lang.NoClassDefFoundError.

Este caso no se aplicará si accedes al código directamente desde el código de la app, ya que las herramientas de compilación reconocen las rutas de acceso de ese código. Sin embargo, podría darse cuando las rutas de acceso del código son menos visibles; por ejemplo, cuando una de las bibliotecas usadas posee dependencias complejas. Por ejemplo, si el código usa introspección o invocación de métodos Java de código nativo, es posible que no se reconozcan esas clases como se requiere en el archivo DEX principal.

Si se muestra el error java.lang.NoClassDefFoundError, debes especificar manualmente estas clases adicionales según sea necesario en el archivo DEX principal, para lo cual debes declararlas con el multiDexKeepFile o la propiedad multiDexKeepProguard en tu tipo de compilación. Si se hace coincidir una clase, ya sea en el multiDexKeepFile o en el archivo multiDexKeepProguard, esta se agrega al archivo DEX principal.

Propiedad multiDexKeepFile

El archivo que especificas en multiDexKeepFile debe contener una clase por línea, en el formato com/example/MyClass.class. Por ejemplo, puedes crear un archivo denominado multidex-config.txt parecido a este:

    com/example/MyClass.class
    com/example/MyOtherClass.class
    

Luego, puedes declarar ese archivo para un tipo de compilación, como se indica a continuación:

    android {
        buildTypes {
            release {
                multiDexKeepFile file('multidex-config.txt')
                ...
            }
        }
    }
    

Recuerda que Gradle lee las rutas de acceso relativas al archivo build.gradle, por lo cual el ejemplo anterior funciona si multidex-config.txt está en el mismo directorio que el archivo build.gradle.

Propiedad multiDexKeepProguard

El archivo multiDexKeepProguard usa el mismo formato que Proguard y admite toda la gramática de Proguard. Para obtener más información sobre el formato y la gramática de Proguard, consulta la sección Opciones de Keep del manual de Proguard.

El archivo que especifiques en multiDexKeepProguard debe incluir opciones de -keep en cualquier sintaxis de ProGuard válida. Por ejemplo, -keep com.example.MyClass.class. Puedes crear un archivo denominado multidex-config.pro parecido a este:

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

Si deseas especificar todas las clases en un paquete, el archivo tendrá el siguiente aspecto:

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

Luego, puedes declarar ese archivo para un tipo de compilación, como se indica a continuación:

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

Optimiza multidex en compilaciones de desarrollo

Una configuración de multidex requiere un tiempo de procesamiento de compilaciones mucho mayor, ya que el sistema de compilación debe tomar decisiones complejas sobre las clases que deben incluirse en el archivo DEX principal y las que pueden incluirse en los archivos DEX secundarios. Por este motivo, las compilaciones incrementales que usan multidex en general tardan más tiempo y pueden ralentizar tu proceso de desarrollo.

Para mitigar los tiempos de compilación incrementales más largos, debes usar pre-dexing a fin de reutilizar los resultados de multidex entre compilaciones. El proceso de pre-dexing se basa en un formato ART solo disponible en Android 5.0 (nivel de API 21) y versiones posteriores. Si usas Android Studio 2.3 y versiones posteriores, el IDE utiliza automáticamente este elemento cuando implementa tu app en un dispositivo con Android 5.0 (nivel de API 21) o versiones posteriores.

Sugerencia: El complemento de Android para Gradle 3.0.0 y versiones posteriores incluye mejoras adicionales para optimizar las velocidades de compilación, como el dex por clase (de modo que solo las clases que modifiques se vuelven a convertir a dex). En general, para que tengas la mejor experiencia de desarrollo, siempre debes actualizar a la versión de Android Studio más reciente y a la versión más reciente del complemento de Android.

Sin embargo, si estás ejecutando compilaciones de Gradle desde la línea de comandos, debes configurar minSdkVersion en 21 o posterior para habilitar el pre-dexing. Una estrategia útil para conservar la configuración de tu compilación de producción es crear dos versiones de tu app usando tipos de productos: un tipo de desarrollo y uno de lanzamiento con valores diferentes para minSdkVersion, como se muestra a continuación.

    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 what you set for
                // 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 {
        compile 'com.android.support:multidex:1.0.3'
    }
    

Para conocer más estrategias que ayudan a mejorar las velocidades de compilación (desde Android Studio o la línea de comandos), consulta Cómo optimizar la velocidad de compilación. Para obtener más información sobre el uso de variantes de compilación, consulta la sección Cómo configurar variantes de compilación.

Sugerencia: Ahora que tienes diferentes variantes de compilación para diferentes necesidades de multidex, también puedes proporcionar un archivo de manifiesto diferente para cada variante (de modo que solo la variante para el nivel de API 20 y niveles anteriores cambie el nombre de la etiqueta <application>), o bien crear una subclase de Application diferente para cada variante (de modo que solo la variante para el nivel 20 de API y niveles inferiores extienda la clase MultiDexApplication o invoque MultiDex.install(this)).

Prueba las apps de multidex

Cuando escribes pruebas de instrumentación para apps de multidex, no se requieren configuraciones adicionales si usas una instrumentación MonitoringInstrumentation (o un AndroidJUnitRunner). Si usas otra Instrumentation, debes anular su método onCreate() con el siguiente código:

Kotlin

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

Java

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