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

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 {
        def multidex_version = "2.0.1"
        implementation 'androidx.multidex:multidex:$multidex_version'
    }
       

Para ver las versiones actuales de esta biblioteca, consulta la información sobre Multidex en la página de versiones.

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.

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 y compilan archivos classesN.dex en un solo archivo .oat para ejecutar en el dispositivo Android. Por lo tanto, si la versión de minSdkVersion es 21 o una versión posterior, multidex estará habilitado de forma predeterminada y no se necesitará la biblioteca de compatibilidad multidex.

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

Nota: Cuando se ejecuta la app con Android Studio, la compilación se optimiza para los dispositivos de destino en los que se implementará. Se incluye la habilitación de multidex cuando los dispositivos de destino ejecutan Android 5.0 y versiones posteriores. Como esta optimización se aplica solo cuando se implementa la app con Android Studio, debes de todos modos configurar tu compilación de lanzamiento para multidex si deseas evitar el límite de 64 K.

Cómo evitar el límite de 64 K

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 lanzamiento. 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.

Cómo configurar la app para multidex

Si la versión de minSdkVersion está configurada en 21 o una versión posterior, multidex estará habilitado de forma predeterminada y no necesitarás la biblioteca de compatibilidad multidex.

Sin embargo, si minSdkVersion está configurado en 20 o en una versión anterior, deberás usar la biblioteca de compatibilidad multidex y realizar las siguientes modificaciones en el proyecto de la app:

  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 {
          implementation 'com.android.support:multidex:1.0.3'
        }
        
  2. Según si anulas la clase Application, realiza una de las siguientes tareas:
    • Si no anulas la clase Application, edita el archivo de manifiesto para configurar android:name en la etiqueta <application> de la siguiente manera:

          <?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, cámbiala para extender MultiDexApplication (si es posible) de la siguiente manera:

      Kotlin

          class MyApplication : MultiDexApplication() {...}
          

      Java

          public class MyApplication extends MultiDexApplication { ... }
          
    • O bien, si anulas la clase Application pero no es posible cambiar la clase de base, 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 cualquier otro código mediante reflexión ni JNI antes de que se haya completado 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, la app fallará y se mostrará 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 java.lang.NoClassDefFoundError, debes especificar manualmente estas clases adicionales según sea necesario en el archivo DEX principal; para ello, debes declararlas con multiDexKeepFile o con 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 la versión 21 o en una versión 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 {
        implementation '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 llame a MultiDex.install(this)).

Cómo probar las apps de multidex

Cuando escribes pruebas de instrumentación para apps de multidex, no se requiere ninguna configuración adicional si usas una instrumentación MonitoringInstrumentation (o 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);
      ...
    }