Otimizar a velocidade da sua versão

Longos tempos de compilação atrasam o processo de desenvolvimento. Esta página oferece algumas técnicas para ajudar você a resolver os gargalos de velocidade de compilação.

O processo geral para melhorar sua velocidade de compilação é o seguinte:

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

Ao desenvolver seu app, você precisa implantá-lo em um dispositivo com o Android 7.0 (API de nível 24) ou uma versão posterior, sempre que possível. Versões mais recentes da plataforma Android implementam maneiras melhores para enviar atualizações para o app, como o Android Runtime (ART) e a compatibilidade nativa com vários arquivos DEX.

Observação: depois da sua primeira compilação limpa, você perceberá que as subsequentes (limpas e incrementais) têm um desempenho muito mais rápido, mesmo sem o uso de qualquer uma das 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 de JVM.

Otimizar a configuração da sua compilação

Siga estas dicas para aumentar a velocidade de compilação do seu projeto do Android Studio.

Manter suas ferramentas atualizadas

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

Criar uma variante de compilação para desenvolvimento

Muitas das configurações de que você precisa ao preparar seu app para lançamento não são necessárias durante o desenvolvimento do app. A ativação de processos desnecessários reduz a velocidade das compilações incrementais e limpas. Por isso, configure uma variante de compilação que mantenha apenas as configurações necessárias para o desenvolvimento do seu app. O exemplo a seguir cria uma variação "dev" (desenvolvimento) e uma variação "prod" (produção), para as configurações da versão de lançamento:

    android {
      ...
      defaultConfig {...}
      buildTypes {...}
      productFlavors {
        // When building a variant that uses this flavor, the following configurations
        // override those in the defaultConfig block.
        dev {
          // To avoid using legacy multidex when building from the command line,
          // set minSdkVersion to 21 or higher. When using Android Studio 2.3 or higher,
          // the build automatically avoids legacy multidex when deploying to a device running
          // API level 21 or higher—regardless of what you set as your minSdkVersion.
          minSdkVersion 21
          versionNameSuffix "-dev"
          applicationIdSuffix '.dev'
        }

        prod {
          // If you've configured the defaultConfig block for the release version of
          // your app, you can leave this block empty and Gradle uses configurations in
          // the defaultConfig block instead. You still need to create this flavor.
          // Otherwise, all variants use the "dev" flavor configurations.
        }
      }
    }
    

Se a configuração de compilação já usa as variações do produto para criar versões diferentes do seu app, você pode combinar as configurações "dev" e "prod" com essas variações ao usar as dimensões de variação. Por exemplo, se você já configurou uma variação "demo" (demonstração) e "full" (completa), poderá usar o exemplo de configuração a seguir para criar variações combinadas, como "devDemo" e "prodFull".

    android {
      ...
      defaultConfig {...}
      buildTypes {...}

      // Specifies the flavor dimensions you want to use. The order in which you
      // list each dimension determines its priority, from highest to lowest,
      // when Gradle merges variant sources and configurations. You must assign
      // each product flavor you configure to one of the flavor dimensions.

      flavorDimensions "stage", "mode"

      productFlavors {
        dev {
          dimension "stage"
          minSdkVersion 21
          versionNameSuffix "-dev"
          applicationIdSuffix '.dev'
          ...
        }

        prod {
          dimension "stage"
          ...
        }

        demo {
          dimension "mode"
          ...
        }

        full {
          dimension "mode"
          ...
        }
      }
    }
    

Ativar sincronização de projeto de variante única

Sincronizar seu projeto com a configuração da compilação é uma etapa importante para permitir que o Android Studio entenda como o projeto está estruturado. No entanto, esse processo pode levar muito tempo em projetos grandes. Se seu projeto usa diversas variantes de compilação, agora você pode otimizar as sincronizações, limitando-as apenas à variante selecionada no momento.

Você precisa usar o Android Studio 3.3 ou versões posteriores com o plug-in do Android para Gradle 3.3.0 ou versões posteriores para ativar essa otimização. A otimização é ativada por padrão em todos os projetos.

Para ativar essa otimização manualmente, clique em File > Settings > Experimental > Gradle ( Android Studio > Preferences > Experimental > Gradle em um Mac) e marque a caixa de seleção Only sync the active variant.

Observação: essa otimização é totalmente compatível com projetos que incluem as linguagens Java e C++, e é parcialmente compatível com Kotlin. Ao ativar a otimização para projetos com conteúdo em Kotlin, a sincronização do Gradle volta a usar variantes completas internamente.

Evitar a compilação de recursos desnecessários

Evite compilar e empacotar recursos que não estão sendo testados (como localizações de idiomas extras e recursos de densidade de tela). Você pode fazer isso apenas especificando um recurso de idioma e uma densidade de tela para a variação “dev”, como mostrado no exemplo a seguir:

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

Desativar o Crashlytics para suas compilações de depuração

Se você não precisar gerar um relatório do Crashlytics (link em inglês), agilize as compilações de depuração desativando esse plug-in da seguinte maneira:

    android {
      ...
      buildTypes {
        debug {
          ext.enableCrashlytics = false
        }
    }
    

Você também precisa desativar o kit do Crashlytics no ambiente de execução para compilações de depuração alterando a forma como inicializa a compatibilidade com Fabric no seu app, como mostrado abaixo:

Kotlin

    // Initializes Fabric for builds that don't use the debug build type.
    Crashlytics.Builder()
            .core(CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
            .build()
            .also { crashlyticsKit ->
                Fabric.with(this, crashlyticsKit)
            }
    

Java

    // Initializes Fabric for builds that don't use the debug build type.
    Crashlytics crashlyticsKit = new Crashlytics.Builder()
        .core(new CrashlyticsCore.Builder().disabled(BuildConfig.DEBUG).build())
        .build();

    Fabric.with(this, crashlyticsKit);
    

Desativar geração de ID de compilação automática

Se quiser usar o Crashlytics com as compilações de depuração, você ainda pode agilizar as compilações incrementais impedindo que o Crashlytics atualize recursos do app com o código de compilação exclusivo dele em cada compilação. Como esse ID de compilação é armazenado em um arquivo de recurso ao qual o manifesto faz referência, desabilitar a geração de ID de compilação automática também permite usar a opção Apply Changes alongside Crashlytics para suas compilações de depuração.

Para evitar que o Crashlytics atualize constantemente o código de compilação, adicione o seguinte ao seu arquivo build.gradle:

    android {
      ...
      buildTypes {
        debug {
          ext.alwaysUpdateBuildId = false
        }
    }
    

Para saber mais sobre como otimizar as compilações ao usar o Crashlytics, leia a documentação oficial (link em inglês).

Usar valores de configuração de compilação estáticos com sua compilação de depuração

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

Por exemplo, usar códigos de versão dinâmicos, nomes de versão, recursos ou qualquer outra lógica de compilação que altere o arquivo de manifesto exige uma compilação de APK completa sempre que você quiser executar uma alteração, mesmo que a alteração precise apenas de um hot swap. Se a configuração de compilação exigir essas propriedades dinâmicas, isole-as nas suas variantes de compilação de lançamento e mantenha os valores estáticos para as compilações de depuração, conforme mostrado no arquivo build.gradle abaixo.

    int MILLIS_IN_MINUTE = 1000 * 60
    int minutesSinceEpoch = System.currentTimeMillis() / MILLIS_IN_MINUTE

    android {
        ...
        defaultConfig {
            // Making either of these two values dynamic in the defaultConfig will
            // require a full APK build and reinstallation because the AndroidManifest.xml
            // must be updated.
            versionCode 1
            versionName "1.0"
            ...
        }

        // The defaultConfig values above are fixed, so your incremental builds don't
        // need to rebuild the manifest (and therefore the whole APK, slowing build times).
        // But for release builds, it's okay. So the following script iterates through
        // all the known variants, finds those that are "release" build types, and
        // changes those properties to something dynamic.
        applicationVariants.all { variant ->
            if (variant.buildType.name == "release") {
                variant.mergedFlavor.versionCode = minutesSinceEpoch;
                variant.mergedFlavor.versionName = minutesSinceEpoch + "-" + variant.flavorName;
            }
        }
    }
    

Usar versões de dependência estáticas

Ao declarar dependências nos seus arquivos build.gradle, evite usar números de versão com um sinal de adição no final, como 'com.android.tools.build:gradle:2.+'. Usar números de versão dinâmicos pode causar atualizações inesperadas, dificuldade para resolver diferenças de versão e compilações mais lentas causadas pelo Gradle ao verificar se há atualizações. Em vez disso, você use números de versão estáticos/codificados.

Ativar o modo off-line

Se você estiver usando uma conexão lenta, os tempos de compilação poderão ser prejudicados quando o Gradle tentar usar recursos de rede para resolver dependências. Você pode solicitar que o Gradle evite usar recursos de rede usando apenas os artefatos armazenados localmente em cache.

Para usar o Gradle off-line ao compilar com o Android Studio, faça o seguinte:

  1. Abra a janela Preferences, clicando em File > Settings (no Mac, Android Studio > Preferences).
  2. No painel à esquerda, clique em Build, Execution, Deployment > Gradle.
  3. Marque a caixa de seleção Offline work.
  4. Clique em Apply ou OK.

Se você estiver compilando pela linha de comando, passe a opção --offline.

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 futuras compilações. Isso também torna a execução de projetos paralelos (link em inglês) mais eficaz, quando você ativa essa otimização.

Criar tarefas para lógica de compilação personalizada

Depois que você criar um perfil de compilação, se ele mostrar que uma parte relativamente grande do tempo de compilação é passada na fase “Configuring Projects”, analise os scripts build.gradle e procure um código que possa ser incluído em uma tarefa personalizada do Gradle. Ao mover alguma lógica de compilação para uma tarefa, ela será executada somente quando necessário. Os resultados podem ser armazenados em cache para compilações futuras, e essa lógica de compilação se qualifica para execução em paralelo, se você ativar a execução de projeto em paralelo (link em inglês). Para saber mais, leia a documentação oficial do Gradle (link em inglês).

Dica: se sua compilação incluir um grande número de tarefas personalizadas, organize seus arquivos build.gradle criando classes de tarefas personalizadas (link em inglês). Adicione suas classes ao diretório project-root/buildSrc/src/main/groovy/ para que o Gradle as inclua automaticamente no caminho de classe de todos os arquivos build.gradle no 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), mas pode oferecer melhor compactação do que JPEG ou PNG. A redução dos tamanhos dos arquivos de imagem, sem a necessidade de realizar a compactação no tempo de compilação, pode acelerar as compilações, especialmente se o app usar muitos recursos de imagem. Entretanto, você poderá perceber um pequeno aumento no uso de CPU do dispositivo ao descompactar as imagens WebP. É fácil converter as imagens para WebP usando o Android Studio.

Desativar o processamento de PNG

Mesmo que você não possa (ou não queira) converter imagens PNG em WebP, ainda será possível agilizar a compilação desativando a compactação automática de imagens sempre que você compilar o app. Se você estiver usando o plug-in do Android 3.0.0 ou versões posteriores, o processamento de PNG será desabilitado por padrão apenas para o tipo de compilação "debug" (depuração). Para desabilitar essa otimização para outros tipos de compilação, adicione o seguinte ao seu arquivo build.gradle:

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

    // If you're using an older version of the plugin, use the
    // following:
    //  aaptOptions {
    //      cruncherEnabled false
    //  }
    }
    

Como tipos de compilação ou variações de produto não definem essa propriedade, é necessário configurá-la manualmente como true ao compilar a versão de lançamento do aplicativo.

Ativar o cache de compilações

O cache de compilações armazena determinadas saídas geradas pelo plug-in do Android para Gradle ao compilar o projeto (como AARs não empacotados e dependências remotas pré-dexadas). As compilações limpas são muito mais rápidas quando você usa o cache, porque o sistema pode simplesmente reutilizar os arquivos em cache durante as compilações subsequentes em vez de recriá-los.

Novos projetos que usam o plug-in do Android versão 2.3.0 e posteriores ativam o cache de compilações por padrão (a não ser que você o desative explicitamente). Para saber mais, leia Acelerar compilações limpas com o cache de compilações.

Usar processadores de anotações incrementais

O plug-in do Android para Gradle 3.3.0 e versões posteriores melhoram a compatibilidade com o processamento de anotações incrementais. Portanto, para melhorar as velocidades de compilação incremental, atualize o plug-in do Android para Gradle e use apenas processadores de anotações incrementais sempre que possível.

Observação: esse recurso é compatível com as versões 4.10.1 e posteriores do Gradle, exceto o Gradle 5.1 (consulte o problema 8194 do Gradle, link em inglês).

Para começar, consulte a seguinte lista de processadores de anotações utilizados que são compatíveis com o processamento de anotações incrementais. Para uma lista mais completa, consulte Estado da compatibilidade em processadores de anotação utilizados (link em inglês). Alguns dos processadores de anotações podem exigir outras etapas para permitir a otimização. Portanto, leia a documentação de cada um deles.

Além disso, se você usar o Kotlin em seu aplicativo, será necessário usar o kapt 1.3.30 ou versões posteriores para compatibilidade de processadores de anotações incrementais com seu código Kotlin. Leia a documentação oficial e veja se é necessário ativar manualmente esse comportamento.

Se você precisar usar um ou mais processadores de anotações que não sejam compatíveis com compilações incrementais, o processamento de anotações não será incremental. No entanto, se seu projeto estiver usando o kapt, a compilação do Java ainda será incremental. Se você não usar kapt e quiser que a compilação Java seja incremental, considere incluir a seguinte sinalização no arquivo gradle.properties. Se você fizer isso, o plug-in do Android para Gradle executará todos os processadores de anotações em uma tarefa separada e permitirá que as tarefas de compilação Java sejam executadas de forma incremental.

    android.enableSeparateAnnotationProcessing = true
    

Criar perfil para sua compilação

Projetos maiores ou aqueles que implementem uma grande quantidade de lógica de compilação personalizada podem exigir que você examine mais detalhadamente o processo de compilação para encontrar gargalos. Você pode fazer isso analisando quanto tempo o Gradle leva para executar cada fase do ciclo e cada tarefa de compilação. Por exemplo, se o perfil da compilação mostrar que o Gradle passa tempo demais configurando o projeto, ele pode sugerir que é preciso retirar a lógica de compilação personalizada da fase de configuração. Além disso, se a tarefa mergeDevDebugResources consumir uma grande parte do tempo de compilação, isso pode indicar que você precisa converter suas imagens em WebP ou desativar o processamento de PNG.

Usar perfis para melhorar a velocidade geralmente envolve executar a compilação com os perfis ativados, fazer alguns ajustes nas configurações da compilação e criar mais perfis para observar os resultados das mudanças.

Para gerar e ver um perfil de compilação, execute as seguintes etapas:

  1. Com o projeto aberto no Android Studio, selecione View > Tool Windows > Terminal para abrir uma linha de comando na raiz do seu projeto.
  2. Execute uma compilação limpa inserindo o seguinte comando. Ao criar perfis para sua compilação, execute uma compilação limpa entre cada compilação para a qual você criar um perfil, porque o Gradle pula tarefas quando entradas para uma delas (como o código-fonte) não mudam. Dessa forma, uma segunda compilação sem mudanças nas entradas sempre será executada mais rapidamente, porque as tarefas não estão sendo repetidas. Por isso, executar a tarefa clean entre as compilações garante que o perfil seja feito para todo o processo de compilação.
        // On Mac or Linux, run the Gradle wrapper using "./gradlew".
        gradlew clean
        
  3. Execute uma compilação de depuração de uma das suas variações de produto, como a variação “dev”, com as seguintes sinalizações:
        gradlew --profile --offline --rerun-tasks assembleFlavorDebug
        
    • --profile: ativa perfis.
    • --offline: impede o Gradle de buscar dependências on-line. Isso garante que qualquer atraso causado pelo Gradle ao tentar atualizar as dependências não interfira com os dados de perfil. Você precisa ter compilado o projeto uma vez para garantir que o Gradle já tenha feito o download das dependências e as armazenado em cache.
    • --rerun-tasks: força o Gradle a executar novamente todas as tarefas e ignorar qualquer otimização de tarefa.
  4. Figura 1. Visualização do projeto indicando a localização dos relatórios de perfil.

    Após a conclusão da compilação, use a janela Project para navegar até o diretório project-root/build/reports/profile/ (como mostrado na Figura 1).

  5. Clique com o botão direito do mouse no arquivo profile-timestamp.html e selecione Open in Browser > Default. O relatório será semelhante ao mostrado na Figura 2. Você pode inspecionar cada guia no relatório para aprender sobre sua compilação, como a guia Task Execution, que mostra quanto tempo o Gradle levou para executar cada tarefa de compilação.

    Figura 2. Visualização de um relatório em um navegador.

  6. Opcional: antes de fazer qualquer alteração no projeto ou na configuração da compilação, repita o comando na etapa 3, mas omita a sinalização --rerun-tasks. Como o Gradle tenta poupar tempo evitando repetir a execução de tarefas cujas entradas não foram alteradas (indicadas como UP-TO-DATE na guia Task Execution do relatório, conforme mostrado na Figura 3), você pode identificar quais tarefas estão sendo executadas quando não deveriam estar. Por exemplo, se :app:processDevUniversalDebugManifest não estiver marcado como UP-TO-DATE, isso pode sugerir que sua configuração de compilação está atualizando dinamicamente o manifesto a cada compilação. No entanto, algumas tarefas precisam ser executadas durante cada compilação, como :app:checkDevDebugManifest.

    Figura 3. Visualização dos resultados da execução da tarefa.

Agora que você tem um relatório de perfil de compilação, pode começar a buscar oportunidades de otimização inspecionando as informações em cada guia do relatório. Algumas configurações de compilação exigem experimentação, porque os benefícios podem variar entre projetos e estações de trabalho. Por exemplo, projetos com uma grande codebase podem se beneficiar do uso do ProGuard para remover códigos não utilizados e reduzir o tamanho do APK. Entretanto, para projetos menores, talvez seja mais vantajoso desativar o ProGuard. Além disso, aumentar o tamanho de heap do Gradle (usando org.gradle.jvmargs) pode impactar negativamente o desempenho em máquinas com pouca memória.

Após fazer uma alteração na configuração da compilação, observe os resultados das suas alterações repetindo as etapas acima e gerando um novo perfil de compilação. Por exemplo, a Figura 4 mostra um relatório para o mesmo exemplo de app após aplicar algumas das otimizações básicas descritas nesta página.

Figura 4. Visualização de um novo relatório após otimizar a velocidade de compilação.

Dica: para uma ferramenta de criação de perfis mais robusta, use o criador de perfil de código aberto do Gradle (link em inglês).