Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

Métodos para ativar multidex para apps com mais de 64 K

Quando seu app e as bibliotecas a que ele faz referência excederem 65.536 métodos, ocorrerá um erro de compilação que indica que seu app atingiu o limite da arquitetura de compilação do Android:

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

As 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
    

Ambas as condições de erro exibem 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 compile e leia vários arquivos DEX.

Sobre o limite de 64 K referências

Arquivos de app 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 formato Dalvik Executable limita o total de métodos que podem ser referenciados em um só arquivo DEX a 65.536. Isso inclui métodos de framework do Android, métodos de biblioteca e métodos do próprio código. No contexto da ciência da computação, o termo Kilo, K, 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".

Compatibilidade com multidex antes do Android 5.0

Versões da plataforma anteriores ao Android 5.0 (API nível 21) usam o tempo de execução da Dalvik para executar o código do app. Por padrão, a Dalvik limita os apps a um único arquivo de bytecode classes.dex por APK. Para contornar essa limitação, você pode adicionar a biblioteca de suporte a multidex ao seu projeto:

    dependencies {
        def multidex_version = "2.0.1"
        implementation 'androidx.multidex:multidex:$multidex_version'
    }
       

Para visualizar as versões atuais desta biblioteca, consulte as informações de Multidex na página de versões.

Se você não estiver usando o AndroidX, adicione a seguinte dependência de biblioteca de suporte:

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

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. Saiba mais detalhes na seção abaixo sobre como configurar seu app para multidex.

Compatibilidade com multidex para Android 5.0 e versões posteriores

O Android 5.0 (API nível 21) e versões posteriores usam um tempo de execução chamado ART, que oferece compatibilidade nativa com o carregamento de vários arquivos DEX a partir de arquivos APK. O ART executa a pré-compilação no momento da instalação do app, que verifica a presença de arquivos classesN.dex e os compila em um único arquivo .oat para execução no dispositivo Android. Portanto, se sua minSdkVersion for 21 ou posterior, o multidex é ativado por padrão e a biblioteca de suporte multidex não será necessária.

Para saber mais sobre o tempo de execução do Android 5.0, leia ART e Dalvik.

Observação: ao executar seu aplicativo usando o Android Studio, a versão é otimizada para os dispositivos de destino para a que você implanta. Isso inclui a ativação de multidex quando os dispositivos de destino estiverem executando o Android 5.0 e versões posteriores. Como essa otimização é aplicada somente ao implantar seu aplicativo usando o Android Studio, talvez seja necessário configurar sua versão de lançamento para multidex para evitar o limite de 64K.

Evitar o limite de 64 K

Antes de configurar seu app para permitir o uso de 64 K ou mais referências a métodos, reduza o número total de referências chamadas pelo código do seu 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:

  • Verifique as dependências diretas e transitivas do app: todas as dependências de bibliotecas grandes incluídas no app precisam ser usadas de forma a compensar a quantidade de código adicionada ao app. Um antipadrão comum é 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.
  • Remova códigos não utilizados com o ProGuard: ative a redução de código para executar o ProGuard nas versões de lançamento. A redução ativada garante que você não inclua código não utilizado nos APKs.

O uso dessas técnicas pode ajudar a evitar a necessidade de ativar multidex no app, além de diminuir o tamanho geral do APK.

Configurar o app para multidex

Se sua minSdkVersion estiver configurada para 21 ou posterior, o multidex é ativado por padrão e a biblioteca de suporte multidex não será necessária.

No entanto, se sua minSdkVersion estiver definida como 20 ou anterior, será necessário usar a biblioteca de suporte multidex e fazer as seguintes modificações em seu projeto de aplicativo:

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

        android {
            defaultConfig {
                ...
                minSdkVersion 15
                targetSdkVersion 28
                multiDexEnabled true
            }
            ...
        }
    
        dependencies {
          implementation 'com.android.support:multidex:1.0.3'
        }
        
  2. Dependendo de você modificar ou não a classe Application, execute uma das seguintes ações:
    • Se você não modificou a classe Application, edite seu arquivo de manifesto para definir android:name na tag <application> da seguinte 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="android.support.multidex.MultiDexApplication" >
                  ...
              </application>
          </manifest>
          
    • Se você modificou a classe Application, altere-a para estender o MultiDexApplication (se possível) da seguinte forma:

      Kotlin

          class MyApplication : MultiDexApplication() {...}
          

      Java

          public class MyApplication extends MultiDexApplication { ... }
          
    • Ou, se você modificou a classe Application, mas não foi possível alterar a classe base, modifique o método attachBaseContext() e chame 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 meio de reflexão ou JNI antes da conclusão de MultiDex.install(). O rastreio multidex não seguirá essas chamadas, causando ClassNotFoundException ou erros de verificação devido a uma partição de classe inválida entre arquivos DEX.

Agora, ao compilar 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 compilação empacotará todos os arquivos DEX no APK.

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

Limitações de biblioteca de suporte a multidex

A biblioteca de suporte a multidex tem algumas limitações que você precisa conhecer e testar ao incorporá-la na configuração de compilação do seu app:

  • 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, na sigla em inglês) se os arquivos DEX secundários forem grandes. Nesse caso, aplique a redução de código com o ProGuard 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 (API nível 21), o uso de multidex não é suficiente para contornar o limite linearAlloc (problema 78035). Esse limite foi aumentado no Android 4.0 (API nível 14), mas isso não resolveu o problema completamente. E em versões anteriores ao Android 4.0, é possível atingir o limite linearAlloc antes de atingir o limite de índice do DEX. Portanto, se você estiver segmentando níveis de API inferiores a 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.

Isso não deve ocorrer com código acessado diretamente no código do app, porque as ferramentas de compilação reconhecem esses caminhos de código. Mas, é possível que isso ocorra quando os caminhos do código são menos visíveis, como quando uma biblioteca usada 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.

Então, se você receber java.lang.NoClassDefFoundError, será necessário especificar manualmente essas outras classes como necessárias no arquivo DEX principal, declarando-as com multiDexKeepFile ou com a propriedade multiDexKeepProguard no seu tipo de compilação. Se uma classe for correspondida no arquivo multiDexKeepFile ou multiDexKeepProguard, ela será adicionada ao arquivo DEX principal.

Propriedade multiDexKeepFile

O arquivo especificado em multiDexKeepFile precisa conter uma classe por linha, no formato com/example/MyClass.class. Por exemplo, você pode criar um arquivo chamado multidex-config.txt com esta aparência:

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

Em seguida, você pode declarar esse arquivo para um tipo de compilação da seguinte forma:

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

Lembre-se de que o Gradle lê caminhos relacionados ao arquivo build.gradle. Portanto, o exemplo acima funcionará se multidex-config.txt estiver no mesmo diretório do arquivo build.gradle.

Propriedade multiDexKeepProguard

O arquivo multiDexKeepProguard usa o mesmo formato que o ProGuard e é compatível com toda a gramática do ProGuard. Para saber mais sobre o formato e a gramática do ProGuard, consulte a seção Keep Options no manual do ProGuard.

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 com esta aparência:

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

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

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

Em seguida, você pode declarar esse arquivo para um tipo de compilação da seguinte forma:

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

Otimizar o multidex em compilações 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 devem ser incluídas no arquivo DEX principal e quais delas podem ser incluídas nos arquivos DEX secundários. Isso significa que compilações incrementais que utilizam multidex costumam ser mais demoradas e podem retardar o processo de desenvolvimento.

Para reduzir os tempos mais longos da compilação incremental, você deve usar pré-dexação para reutilizar a saída multidex entre compilações. A pré-dexação utiliza um formato ART disponível apenas no Android 5.0 (API nível 21) ou posterior. Se você usa o Android Studio 2.3 ou posterior, o ambiente de desenvolvimento integrado (IDE, na sigla em inglês) utiliza esse recurso automaticamente ao implantar seu app em um dispositivo com Android 5.0 (API nível 21) ou posterior.

Dica: o plug-in Android for Gradle 3.0.0 e versões posteriores incluem outras melhorias para otimizar as velocidades de compilação, como a dexação por classe (para que apenas as classes modificadas sejam redexadas). Em geral, para atingir a melhor experiência de desenvolvimento, atualize sempre para a versão mais recente do Android Studio e do plug-in do Android.

No entanto, se você estiver executando as compilações de Gradle a partir da linha de comando, será necessário definir o minSdkVersion como 21 ou posterior para ativar a pré-dexação. Uma estratégia útil para preservar as configurações da compilação do produção é criar duas versões do seu app usando variações de produtos: uma variação de desenvolvimento e uma variação de lançamento com valores diferentes para minSdkVersion, como mostrado abaixo.

    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 aprender outras estratégias para melhorar as velocidades de compilação (seja no Android Studio ou na linha de comando), leia Otimizar a velocidade de compilação. Para saber mais sobre o uso de variantes de compilação, consulte Configurar variantes de compilação.

Dica: agora que você tem variantes de versão distintas para diferentes necessidades de multidex, você também pode oferecer um arquivo de manifesto diferente para cada variante (para que apenas o manifesto para a API nível 20 e anteriores alterem o nome da tag <application>) ou criar uma subclasse Application para cada variante (para que apenas o manifesto para a API nível 20 e anteriores estenda a classe MultiDexApplication ou chame MultiDex.install(this)).

Testar apps multidex

Ao escrever testes de instrumentação para apps multidex, nenhuma outra configuração será necessária se você usar uma instrumentação MonitoringInstrumentation (ou AndroidJUnitRunner). Se você usar outra Instrumentation, modifique o método onCreate() dela com o código a seguir:

Kotlin

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

Java

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