Otimizar a velocidade do build

Tempos de build demorados atrasam o processo de desenvolvimento. Nesta página, vamos apresentar algumas técnicas para ajudar a resolver gargalos de velocidade de build.

O processo geral para melhorar a velocidade de build do app é este:

  1. Otimizar a configuração do build realizando etapas que beneficiam imediatamente a maioria dos projetos do Android Studio.
  2. Criar um perfil para seu build para identificar e diagnosticar alguns dos gargalos mais complicados que podem ser específicos do projeto ou da estação de trabalho.

Sempre que possível, implemente o app que você está desenvolvendo em um dispositivo com o Android 7.0 (nível 24 da API) ou uma versão mais recente. Versões mais recentes da Plataforma Android oferecem recursos melhores para enviar atualizações ao app, como o Android Runtime (ART) e o suporte nativo para vários arquivos DEX.

Observação: depois de lançar o primeiro build limpo, você vai perceber que os builds seguintes (limpos e incrementais) vão apresentar uma performance muito mais rápida, mesmo sem usar as otimizações descritas nesta página. Isso ocorre porque o daemon do Gradle tem um período de “aquecimento” para aumento de desempenho, semelhante a outros processos da JVM.

Otimizar a configuração do build

Siga estas dicas para aumentar a velocidade de build do seu projeto do Android Studio.

Manter suas ferramentas atualizadas

As ferramentas do Android recebem otimizações de build e novos recursos em quase todas as atualizações. Algumas das dicas nesta página presumem que você está usando a versão mais recente. Para aproveitar as otimizações mais recentes, mantenha estes itens atualizados:

Usar KSP em vez do kapt

A ferramenta Kotlin Annotation Processing (kapt) é significativamente mais lenta do que o Kotlin Symbol Processor (KSP). Se você estiver escrevendo uma origem Kotlin com anotação e usando ferramentas que processam anotações (como o Room) com suporte para KSP, migre para a KSP (em inglês).

Evitar a compilação de recursos desnecessários

Evite compilar e empacotar recursos que não estejam sendo testados, como localizações de idiomas extras e recursos de densidade de tela. Em vez disso, especifique apenas um recurso de idioma e uma densidade de tela para a variação "dev", como mostrado no exemplo abaixo:

Groovy

android {
    ...
    productFlavors {
        dev {
            ...
            // The following configuration limits the "dev" flavor to using
            // English stringresources and xxhdpi screen-density resources.
            resourceConfigurations "en", "xxhdpi"
        }
        ...
    }
}

Kotlin

android {
    ...
    productFlavors {
        create("dev") {
            ...
            // The following configuration limits the "dev" flavor to using
            // English stringresources and xxhdpi screen-density resources.
            resourceConfigurations("en", "xxhdpi")
        }
        ...
    }
}

Tentar colocar o Portal do plug-in do Gradle por último

No Android, todos os plug-ins são encontrados nos repositórios google() e mavenCentral(). No entanto, seu build pode precisar de plug-ins de terceiros que são resolvidos usando o serviço gradlePluginPortal().

O Gradle pesquisa os repositórios na ordem em que são declarados. Portanto, o desempenho do build é melhorado quando os repositórios listados primeiro contêm a maioria dos plug-ins. Por esse motivo, experimente colocar a entrada gradlePluginPortal() por último no bloco do repositório do arquivo settings.gradle. Na maioria dos casos, isso minimiza o número de pesquisas de plug-ins redundantes e melhora a velocidade do build.

Para ver mais informações sobre como o Gradle navega em vários repositórios, consulte Como declarar vários repositórios na documentação do Gradle.

Usar valores de configuração do build estáticos com seu build de depuração

Sempre use valores estáticos para propriedades que acessam o arquivo do manifesto ou os arquivos de recursos para seu tipo de build de depuração.

O uso de códigos de versão dinâmicos, nomes de versão, recursos ou qualquer outra lógica de build que mude o arquivo de manifesto exige um build de app completo sempre que você quiser implementar uma mudança, mesmo que ela precise apenas de um hot swap. Caso sua configuração de build exija essas propriedades dinâmicas, isole-as nas suas variantes de build de lançamento e mantenha os valores estáticos para os builds de depuração, conforme mostrado no exemplo abaixo.

  ...
  // Use a filter to apply onVariants() to a subset of the variants.
  onVariants(selector().withBuildType("release")) { variant ->
      // Because an app module can have multiple outputs when using multi-APK, versionCode
      // is only available on the variant output.
      // Gather the output when we are in single mode and there is no multi-APK.
      val mainOutput = variant.outputs.single { it.outputType == OutputType.SINGLE }

      // Create the version code generating task.
      val versionCodeTask = project.tasks.register("computeVersionCodeFor${variant.name}", VersionCodeTask::class.java) {
          it.outputFile.set(project.layout.buildDirectory.file("versionCode${variant.name}.txt"))
      }

      // Wire the version code from the task output.
      // map will create a lazy Provider that:
      // 1. Runs just before the consumer(s), ensuring that the producer (VersionCodeTask) has run
      //    and therefore the file is created.
      // 2. Contains task dependency information so that the consumer(s) run after the producer.
      mainOutput.versionCode.set(versionCodeTask.flatMap { it.outputFile.map { it.asFile.readText().toInt() } })
  }
  ...

  abstract class VersionCodeTask : DefaultTask() {

    @get:OutputFile
    abstract val outputFile: RegularFileProperty

    @TaskAction
    fun action() {
        outputFile.get().asFile.writeText("1.1.1")
    }
  }

Consulte o roteiro setVersionsFromTask no GitHub para aprender a definir um código de versão dinâmico no seu projeto.

Usar versões de dependência estáticas

Ao declarar dependências nos arquivos build.gradle, evite usar números de versão dinâmicos (com um sinal de soma no fim, como 'com.android.tools.build:gradle:2.+'). O uso de números de versão dinâmicos pode causar atualizações inesperadas, dificuldade para resolver diferenças entre versões e builds mais lentos, causados pelo Gradle ao verificar se há atualizações. Portanto, use números de versão estáticos.

Criar módulos de biblioteca

Procure no seu app um código que possa ser convertido em um módulo de biblioteca do Android. Modularizar o código dessa maneira permite que o sistema compile somente os módulos que você modificar e armazene em cache essas saídas para builds futuros. A modularização também faz com que a execução de projetos paralelos (link em inglês) seja mais eficaz quando essa otimização é ativada.

Criar tarefas para lógica de build personalizada

Depois de criar um perfil para seu build, se ele mostrar que uma parte relativamente longa do tempo de build é gasta na fase **Configuring Projects**, analise os scripts build.gradle e procure um código para incluir em uma tarefa personalizada do Gradle. Quando alguma lógica de build é movida para uma tarefa, é importante garantir que a tarefa seja executada somente quando necessário, que os resultados possam ser armazenados em cache para builds futuros e que essa lógica de build possa ser executada em paralelo se você ativar a execução de projeto em paralelo (link em inglês). Para saber mais sobre as tarefas para lógica de build personalizado, leia a documentação oficial do Gradle (link em inglês).

Dica: caso seu build inclua um grande número de tarefas personalizadas, organize seus arquivos build.gradle criando classes de tarefas personalizadas (link em inglês). Adicione as classes ao diretório project-root/buildSrc/src/main/groovy/. O Gradle as inclui automaticamente no caminho de classe de todos os arquivos build.gradle do projeto.

Converter imagens para WebP

WebP é um formato de arquivo de imagem que oferece compactação com perdas (como JPEG) e transparência (como PNG). O formato WebP pode oferecer uma compactação melhor do que JPEG ou PNG.

A redução dos tamanhos de arquivos de imagem, sem que seja necessário compactar o tempo de build, pode agilizar os builds, principalmente se o app usa muitos recursos de imagem. Entretanto, você poderá perceber um pequeno aumento no uso de CPU do dispositivo ao descompactar as imagens WebP. Use o Android Studio para converter as imagens em WebP com facilidade.

Desativar a análise de PNG

Mesmo que você não converta as imagens PNG em WebP, ainda é possível agilizar o build desativando a compactação automática de imagens sempre que o app for criado.

No plug-in do Android 3.0.0 ou versões mais recentes, por padrão, o processamento de PNG é desativado somente para o tipo de build de "depuração". Para desativar essa otimização para outros tipos de build, adicione o código abaixo ao arquivo build.gradle:

Groovy

android {
    buildTypes {
        release {
            // Disables PNG crunching for the "release" build type.
            crunchPngs false
        }
    }
}

Kotlin

android {
    buildTypes {
        getByName("release") {
            // Disables PNG crunching for the "release" build type.
            isCrunchPngs = false
        }
    }
}

Como tipos de build ou variações de produto não definem essa propriedade, é necessário definir essa propriedade manualmente como true ao criar a versão de lançamento do app.

Testar o coletor de lixo em paralelo da JVM

A performance do build pode ser melhorada configurando o coletor de lixo otimizado da JVM usado pelo Gradle. Enquanto o JDK 8 é configurado para usar o coletor de lixo em paralelo por padrão, o JDK 9 e versões mais recentes são configurados para usar o coletor de lixo G1 (link em inglês).

Para potencialmente melhorar o desempenho do build, recomendamos que você teste seus builds do Gradle com o coletor de lixo em paralelo. Em gradle.properties, defina:

org.gradle.jvmargs=-XX:+UseParallelGC

Se já houver outras opções definidas nesse campo, adicione uma nova opção:

org.gradle.jvmargs=-Xmx1536m -XX:+UseParallelGC

Para medir a velocidade do build com diferentes configurações, consulte Criar perfil para seu build.

Aumentar o tamanho de heap da JVM

Se houver builds lentos e, principalmente, se os resultados do Build Analyzer mostrarem que a coleta de lixo levou mais de 15% do tempo de build, aumente o tamanho de heap da Java Virtual Machine (JVM). No arquivo gradle.properties, defina o limite como 4, 6 ou 8 gigabytes, conforme mostrado no exemplo abaixo:

org.gradle.jvmargs=-Xmx6g

Em seguida, faça um teste para conferir se a velocidade de build melhorou. A maneira mais fácil de determinar o tamanho ideal de heap é aumentar um pouco o limite e, em seguida, testar se houve uma melhoria suficiente na velocidade de build.

Caso o coletor de lixo em paralelo da JVM seja utilizado, a linha inteira vai ficar assim:

org.gradle.jvmargs=-Xmx6g -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 -XX:+UseParallelGC -XX:MaxMetaspaceSize=1g

Para analisar os erros de memória da JVM, ative a flag HeapDumpOnOutOfMemoryError (link em inglês). Com isso, a JVM gera um heap dump quando o sistema fica sem memória.

Usar classes R não transitivas

Use as classes R não transitivas para builds mais rápidos em apps com vários módulos. Isso ajuda a evitar a duplicação de recursos, garantindo que a classe R de cada módulo contenha apenas referências aos próprios recursos, sem extrair referências das dependências. Assim, os builds ficam mais rápidos e você evita a recompilação. Esse é o comportamento padrão no Plug-in do Android para Gradle 8.0.0 e versões mais recentes.

A partir do Android Studio Bumblebee, as classes R não transitivas são ativadas por padrão para novos projetos. Você pode atualizar projetos criados em versões anteriores do Android Studio para usar classes R não transitivas. Para isso, acesse Refactor > Migrate to Non-Transitive R Classes.

Para saber mais sobre os recursos de app e a classe R, consulte a Visão geral dos recursos de aplicativo.

Usar classes R não constantes

Use campos não constantes da classe R em apps e testes para melhorar a incrementabilidade da compilação Java e permitir uma redução de recursos mais precisa. Os campos da classe R sempre não são constantes para as bibliotecas, já que os recursos são numerados ao empacotar o APK do app ou do teste que depende dessa biblioteca. Esse é o comportamento padrão no Plug-in do Android para Gradle 8.0.0 e versões mais recentes.

Desativar a flag do Jetifier

Como a maioria dos projetos usa bibliotecas AndroidX diretamente, você pode remover a flag Jetifier para melhorar a performance do build. Para remover a flag Jetifier, defina android.enableJetifier=false no arquivo gradle.properties.

O Build Analyzer pode fazer uma verificação para definir se a flag pode ser removida com segurança para permitir que o projeto tenha um build com melhor performance e possa ser migrado das bibliotecas de Suporte do Android descontinuadas. Para saber mais sobre o Build Analyzer, consulte Resolver problemas de desempenho do build.

Usar o cache de configuração

O cache de configuração permite que o Gradle registre informações sobre o gráfico de tarefas de build e as reutilize nos builds seguintes. Assim, o Gradle não precisa reconfigurar todo o build novamente.

Para ativar o cache de configuração, siga estas etapas:

  1. Verifique se todos os plug-ins do projeto são compatíveis.

    Use o Build Analyzer para verificar se o projeto oferece suporte ao cache de configuração. O Build Analyzer executa uma sequência de builds de teste para determinar se o recurso pode ser ativado no projeto. Consulte o problema 13490 (link em inglês) para acessar uma lista de plug-ins com suporte.

  2. Adicione o seguinte código ao arquivo gradle.properties:

      org.gradle.configuration-cache=true
      # Use this flag carefully, in case some of the plugins are not fully compatible.
      org.gradle.configuration-cache.problems=warn

Quando o cache de configuração está ativado, a saída do build mostra a mensagem Calculating task graph as no configuration cache is available for tasks na primeira execução do projeto. Durante execuções subsequentes, a saída do build vai informar Reusing configuration cache.

Para saber mais sobre o cache de configuração, consulte a postagem do blog Análise aprofundada do armazenamento da configuração em cache e a documentação do Gradle sobre o cache de configuração (links em inglês).

Problemas de configuração de cache introduzidos no Gradle 8.1 e no Plug-in do Android para Gradle 8.1

O cache de configuração ficou estável no Gradle 8.1 e introduziu o rastreamento de API de arquivos. Chamadas como File.exists(), File.isDirectory() e File.list() são gravadas pelo Gradle para rastrear arquivos de entrada de configuração.

O Plug-in do Android para Gradle (AGP) 8.1 usa essas APIs File para alguns arquivos que o Gradle não considera como entradas de cache. Isso aciona uma invalidação de cache extra quando usada com o Gradle 8.1 e versões mais recentes, diminuindo a performance do build. As seguintes opções são tratadas como entradas de cache no AGP 8.1:

Entrada Issue Tracker Corrigido em
$GRADLE_USER_HOME/android/FakeDependency.jar Problema 289232054 AGP 8.2
saída do cmake Problema 287676077 AGP 8.2
$GRADLE_USER_HOME/.android/analytics.settings Problema 278767328 AGP 8.3

Se você usa essas APIs ou um plug-in que usa essas APIs, pode haver uma regressão no tempo de build, porque algumas lógicas de build que usam essas APIs podem acionar uma invalidação de cache extra. Consulte Melhorias no rastreamento de entrada da configuração do build para ver uma discussão sobre esses padrões e como corrigir a lógica de build ou desativar temporariamente o rastreamento da API de arquivos.