O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

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 anteriores 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 crie e leia vários arquivos DEX.

Sobre o limite de 64 K referências

Arquivos de app para Android (APK) contêm arquivos de bytecode executáveis no formato Dalvik Executable (DEX) (link em inglês), 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, 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 de nível 21) usam o tempo de execução 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 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. Veja mais detalhes na seção abaixo sobre como configurar seu app para multidex.

Compatibilidade com multidex para Android 5.0 e versões mais recentes

O Android 5.0 (API de nível 21) e versões mais recentes 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 mais recente, 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 (link em inglês).

Observação: ao executar 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 evitar o limite de 64 K.

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.
  • Remover código não usado com o R8: ative a redução de código para executar o R8 nos builds 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 definida como 21 ou mais recente, o multidex será 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 no seu projeto de 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:

    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 modificar a classe Application, edite o arquivo de manifesto para definir android:name na tag <application> da seguinte forma:

      <?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ê modificar a classe Application, mude-a para estender MultiDexApplication, se possível, da seguinte forma:

      Kotlin

      class MyApplication : MultiDexApplication() {...}
      

      Java

      public class MyApplication extends MultiDexApplication { ... }
      
    • Ou, se você modificar a classe Application, mas não for possível mudar 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 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 compilação empacotará todos os arquivos DEX no APK.

Durante o tempo de execução, as multidex APIs 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 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. 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 (API de nível 21), o uso de multidex não é suficiente para contornar o limite linearAlloc (problema 78035, link em inglês). Esse limite foi aumentado no Android 4.0 (API de nível 14), mas isso não resolveu o problema completamente. Além disso, 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 pode ocorrer com código acessado diretamente no código do app, porque as ferramentas de compilação reconhecem esses caminhos de código. Entretanto, é 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.

Portanto, se você receber java.lang.NoClassDefFoundError, será necessário especificar manualmente essas outras classes no arquivo DEX principal, declarando-as com a propriedade multiDexKeepFile ou multiDexKeepProguard no seu tipo de build (links em inglês). 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 como o seguinte:

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

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

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

Lembre-se de que o Gradle lê caminhos relativos 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 do Proguard e é compatível com toda a gramática do Proguard. Para ver mais informações sobre o formato e a gramática do Proguard, consulte a seção Opções do Keep no manual do Proguard (link em inglês).

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 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 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 retardar o processo de desenvolvimento.

Para reduzir os tempos mais longos do build incremental, use 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 (API de nível 21) ou versões mais recentes. Se você usa o Android Studio 2.3 ou mais recente, 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 de nível 21) ou mais recente.

Dica: o Plug-in do Android para Gradle 3.0.0 e versões mais recentes 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 conseguir 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 os builds do Gradle na linha de comando, será necessário definir o minSdkVersion como 21 ou mais recente para ativar a pré-dexação. Uma estratégia útil para preservar as configurações do build de 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, conforme 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 compilação diferentes para necessidades multidex diferentes, também é possível fornecer um arquivo de manifesto diferente para cada variante (para que apenas o manifesto para a API de nível 20 e anteriores mudem o nome da tag <application>) ou criar uma subclasse Application para cada variante (para que somente a API de nível 20 e anteriores estendam a classe MultiDexApplication ou chamem 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() com o seguinte 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);
  ...
}