Ativar multidex para apps com mais de 64.000 métodos

Se o app tiver definido o minSdk da API 20 ou anterior, e o app e as bibliotecas a que ele faz referência excederem 65.536 métodos, você vai encontrar erro de build abaixo, que indica que o app atingiu o limite da arquitetura de build do Android:

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

Versões mais antigas do sistema de compilação relatam um erro diferente, que indica o mesmo problema:

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

Essas condições de erro mostram um número em comum: 65.536. Esse número representa o total de referências que podem ser invocadas pelo código em um único arquivo de bytecode Dalvik Executable (DEX). Esta página explica como superar essa limitação, ativando uma configuração de app denominada multidex, que permite que o app crie e leia vários arquivos DEX.

Sobre o limite de 64 K referências

Arquivos de apps Android (APK) contêm arquivos de bytecode executáveis no formato Dalvik Executable (DEX), que contêm o código compilado usado para executar o app. A especificação do Dalvik Executable limita o total de métodos que podem ser referenciados em um único arquivo DEX a 65.536. Isso inclui métodos de framework do Android, métodos de biblioteca e métodos do seu próprio código.

No contexto da ciência da computação, o termo Kilo, ou K (link em inglês), denota 1.024 (ou 2^10). Como 65.536 equivale a 64 X 1.024, esse limite é chamado de "limite de 64 K referências".

Suporte a multidex para o Android 5.0 e versões anteriores

Versões da plataforma anteriores ao Android 5.0 (API de nível 21) usam o tempo de execução Dalvik para executar o código do app. Por padrão, o Dalvik limita os apps a um único arquivo de bytecode classes.dex por APK. Para contornar essa limitação, adicione a biblioteca multidex ao arquivo build.gradle ou build.gradle.kts do módulo:

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

Essa biblioteca se torna parte do arquivo DEX principal do app e, em seguida, gerencia o acesso aos outros arquivos DEX e ao código que eles contêm. Para conferir as versões atuais dessa biblioteca, consulte versões multidex.

Para saber mais, consulte a seção sobre como configurar seu app para multidex.

Suporte a multidex para Android 5.0 e versões mais recentes

O Android 5.0 (nível 21 da API) e versões mais recentes usam um ambiente de execução conhecido como ART que oferece suporte nativo ao carregamento de vários arquivos DEX de arquivos APK. O ART executa a pré-compilação durante a instalação do app, verificando se há arquivos classesN.dex e os compilando em um único arquivo OAT para execução no dispositivo Android. Portanto, se a minSdkVersion for 21 ou mais recente, o multidex fica ativado por padrão, e a biblioteca multidex não é necessária.

Para saber mais sobre o ambiente de execução do Android 5.0, leia Android Runtime (ART) e Dalvik.

Observação: quando você executa seu app usando o Android Studio, o build é otimizado para seus dispositivos de destino. Isso inclui a ativação de multidex quando os dispositivos de destino estiverem executando o Android 5.0 e versões mais recentes. Como essa otimização é aplicada somente ao implantar seu app usando o Android Studio, talvez seja necessário configurar seu build de lançamento para multidex para contornar o limite de 64.000 métodos.

Evitar o limite de 64.000 métodos

Antes de configurar seu app para permitir o uso de 64 mil ou mais referências de método, tome medidas para reduzir o número total de referências chamadas pelo código do app, incluindo métodos definidos pelo código do app ou pelas bibliotecas incluídas.

As seguintes estratégias podem ajudar a não atingir o limite de referências do DEX:

Analisar as dependências diretas e transitivas do app
Avalie se o valor de qualquer dependência de biblioteca grande incluída no app excede a quantidade de código adicionada. Um padrão comum, mas problemático, é incluir uma biblioteca muito grande porque alguns métodos utilitários foram úteis. Muitas vezes, a redução das dependências de código do app pode ajudar a evitar o limite de referências do DEX.
Remover código não utilizado com o R8
Ative a redução de código para executar o R8 nos builds de lançamento. Ative a redução para garantir que você não esteja enviando códigos não utilizados com os APKs. Se a redução estiver configurada corretamente, ela também vai remover códigos e recursos não utilizados das dependências.

O uso dessas técnicas pode diminuir o tamanho geral do seu APK e evitar a necessidade de multidex no seu app.

Configurar o app para multidex

Observação: se a minSdkVersion for definida como 21 ou uma versão mais recente, o multidex ficará ativado por padrão, e a biblioteca multidex não será necessária.

Caso a minSdkVersion esteja definida como 20 ou anterior, use a biblioteca multidex e faça as modificações abaixo no projeto do seu app:

  1. Modifique o arquivo build.gradle de nível de módulo para ativar o multidex e adicionar a biblioteca multidex como dependência, conforme mostrado a seguir:

    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. Dependendo de você substituir ou não a classe Application, execute uma das seguintes ações:
    • Se você não substituir a classe Application, edite o arquivo de manifesto para definir android:name na tag <application> desta maneira:

      <?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 você substituir a classe Application, mude-a para estender MultiDexApplication desta maneira:

      Kotlin

      class MyApplication : MultiDexApplication() {...}
      

      Java

      public class MyApplication extends MultiDexApplication { ... }
      
    • Se você substituir a classe Application, mas não puder mudar a classe base, substitua o método attachBaseContext() e a chamada MultiDex.install(this) para ativar o 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);
        }
      }
      

      Cuidado: não execute MultiDex.install() ou qualquer outro código por reflexão ou JNI antes da conclusão de MultiDex.install(). O rastreio multidex não seguirá essas chamadas, causando uma ClassNotFoundException ou erros de verificação devido a uma partição de classe inválida entre arquivos DEX.

Agora, ao criar o app, as ferramentas de compilação do Android construirão um arquivo DEX principal (classes.dex) e arquivos DEX de apoio (classes2.dex, classes3.dex e assim por diante), conforme necessário. Em seguida, o sistema de build vai empacotar todos os arquivos DEX no APK.

Durante a execução, em vez de pesquisar apenas o arquivo classes.dex principal, as APIs multidex usam um carregador de classes especial para pesquisar todos os arquivos DEX disponíveis para os métodos.

Limitações da biblioteca multidex

A biblioteca multidex tem algumas limitações conhecidas. Ao incorporar a biblioteca à configuração do build do app, considere o seguinte:

  • A instalação de arquivos DEX durante a inicialização na partição de dados de um dispositivo é complexa e poderá resultar em erros "O app não está respondendo" (ANR) se os arquivos DEX secundários forem grandes. Para evitar esse problema, ative a redução de código para diminuir o tamanho dos arquivos DEX e remover as partes não utilizadas do código.
  • Quando executado em versões anteriores ao Android 5.0 (nível 21 da API), o uso de multidex não é suficiente para contornar o limite linearalloc (problema 37008143). Esse limite aumentou no Android 4.0 (nível 14 da API), mas isso não resolveu o problema completamente.

    Em versões anteriores ao Android 4.0, é possível atingir o limite linearalloc antes de atingir o limite de índice do DEX. Se você estiver destinando o app a níveis de API anteriores ao 14, faça um teste completo nessas versões da plataforma, uma vez que o app poderá ter problemas na inicialização ou quando determinados grupos de classes forem carregados.

    A redução de código pode diminuir ou possivelmente eliminar esses problemas.

Declarar as classes necessárias no arquivo DEX principal

Ao criar cada arquivo DEX de um app multidex, as ferramentas de compilação tomam decisões complexas para determinar as classes necessárias no arquivo DEX principal, para que o app seja iniciado corretamente. Se alguma classe necessária para a inicialização não for fornecida no arquivo DEX principal, o app falhará apresentando o erro java.lang.NoClassDefFoundError.

As ferramentas de build reconhecem os caminhos de código acessados diretamente no código do app. No entanto, esse problema pode ocorrer quando os caminhos do código estão menos visíveis, por exemplo, quando uma biblioteca que você usa tem dependências complexas. Por exemplo, se o código utiliza introspecção ou invocação de métodos Java no código nativo, essas classes talvez não sejam reconhecidas como necessárias no arquivo DEX principal.

Se você receber java.lang.NoClassDefFoundError, especifique manualmente as classes extras necessárias no arquivo DEX principal, declarando-as com a propriedade multiDexKeepProguard no seu tipo de build. Se há uma classe associada no arquivo multiDexKeepProguard, ela é adicionada ao arquivo DEX principal.

Propriedade multiDexKeepProguard

O arquivo multiDexKeepProguard usa o mesmo formato do ProGuard e oferece suporte à toda a gramática do ProGuard. Para saber mais sobre como personalizar o que é mantido no app, consulte Personalizar o código a ser mantido.

O arquivo que você especificar em multiDexKeepProguard precisará conter opções de -keep em qualquer sintaxe válida do ProGuard. Por exemplo: -keep com.example.MyClass.class. Você pode criar um arquivo chamado multidex-config.pro como o seguinte:

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

Ou, se você quiser especificar todas as classes de um pacote, o arquivo ficará assim:

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

Em seguida, você pode declarar esse arquivo para um tipo de build desta forma:

Groovy

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

Kotlin

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

Otimizar o multidex em builds de desenvolvimento

Configurações de multidex exigem um tempo de processamento de compilação significativamente maior, porque o sistema de compilação precisa tomar decisões complexas sobre quais classes precisam ser incluídas no arquivo DEX principal e quais delas podem ser incluídas nos arquivos DEX secundários. Isso significa que builds incrementais que utilizam multidex costumam ser mais demorados e podem atrasar o processo de desenvolvimento.

Para reduzir os tempos de build incremental mais longos, use a pré-dexação para reutilizar a saída multidex entre builds. A pré-dexação utiliza um formato ART disponível apenas no Android 5.0 (nível 21 da API) ou versões mais recentes. Se você está usando o Android Studio, o ambiente de desenvolvimento integrado utiliza a pré-dexação automaticamente ao implantar o app em um dispositivo com Android 5.0 (nível 21 da API) ou versão mais recente. No entanto, se você está executando os builds do Gradle na linha de comando, é necessário definir o minSdkVersion como 21 ou uma versão mais recente para ativar a pré-dexação.

Para preservar as configurações do build de produção, é possível criar duas versões do seu app usando variações de produtos (uma de desenvolvimento e outra de lançamento) com valores diferentes para minSdkVersion, conforme mostrado:

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

Para aprender outras estratégias para melhorar as velocidades de build (seja no Android Studio ou na linha de comando), leia Otimizar a velocidade do seu build. Para saber mais sobre o uso de variantes de build, consulte Configurar variações de versão.

Dica: se você tem variantes de build diferentes para diferentes necessidades de multidex, pode fornecer um arquivo de manifesto específico para cada variante, de modo que somente o arquivo da API de nível 20 e anteriores mude o nome da tag <application>. Também é possível criar uma subclasse Application diferente para cada variante, de modo que somente a subclasse para a API de nível 20 e anteriores estenda a classe MultiDexApplication ou chame MultiDex.install(this).

Testar apps multidex

Ao criar testes de instrumentação para apps multidex, nenhuma outra configuração é necessária se você usa uma instrumentação MonitoringInstrumentation (ou AndroidJUnitRunner). Se você usar outra Instrumentation, substitua o método onCreate() por este 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);
  ...
}