Comparar o código do app

A biblioteca Jetpack Benchmark permite que você avalie rapidamente seu código baseado em Kotlin ou Java no Android Studio. A biblioteca lida com o aquecimento, mede o desempenho do seu código e gera resultados comparativos para o console do Android Studio.

Os casos de uso incluem a rolagem de RecyclerView, o aumento de uma hierarquia de View não trivial e a realização de consultas ao banco de dados.

Se você ainda não adotou o AndroidX em um projeto que quer comparar, consulte Migrar um projeto existente usando o Android Studio.

Início rápido

Esta seção apresenta etapas rápidas para testar a comparação sem exigir que você transfira o código para os módulos. Como as etapas envolvem desativar a depuração de resultados de desempenho precisos, você não fará alterações no sistema de controle de origem, mas elas ainda poderão ser úteis quando quiser executar medições únicas.

Para realizar uma comparação única rapidamente, faça o seguinte:

  1. Adicione a biblioteca ao arquivo build.gradle do seu módulo:

    project_root/module_dir/build.gradle

        dependencies {
            androidTestImplementation "androidx.benchmark:benchmark-junit4:1.0.0-alpha04"
        }
        
  2. Para desativar a depuração no manifesto de teste, atualize seu elemento <application> para forçar a desativação temporária da depuração da seguinte maneira:

    project_root/module_dir/src/androidTest/AndroidManifest.xml

    <!-- Important: disable debuggable for accurate performance results -->
        <application
            android:debuggable="false"
            tools:ignore="HardcodedDebugMode"
            tools:replace="android:debuggable"/>
        
  3. Para adicionar sua comparação, inclua uma instância do BenchmarkRule em um arquivo de teste no diretório androidTest. Para mais informações sobre como programar comparações, consulte Como programar uma comparação.

    O snippet de código a seguir mostra como adicionar uma comparação a um teste do JUnit:

    Kotlin

        @RunWith(AndroidJUnit4::class)
        class MyBenchmark {
            @get:Rule
            val benchmarkRule = BenchmarkRule()
    
            @Test
            fun benchmarkSomeWork() = benchmarkRule.measureRepeated {
                doSomeWork()
            }
        }
        

    Java

        @RunWith(AndroidJUnit4.class)
        class MyBenchmark {
            @Rule
            public BenchmarkRule benchmarkRule = new BenchmarkRule();
    
            @Test
            public void myBenchmark() {
                final BenchmarkState state = benchmarkRule.getState();
                while (state.keepRunning()) {
                    doSomeWork();
                }
            }
        }
        

Em que usar a comparação

As comparações são mais úteis para trabalhos da CPU que são executados muitas vezes no app. Bons exemplos são a rolagem de RecyclerView, conversões/processamento de dados e fragmentos de código que são usados repetidamente.

É mais difícil medir outros tipos de código com uma comparação. Como as comparações são executadas em loop, qualquer código que não seja executado com frequência ou que apresente desempenhos diferentes quando chamado várias vezes pode não funcionar para a comparação.

Armazenamento em cache

Tente evitar medir apenas o cache. Por exemplo, uma comparação para o layout de uma visualização personalizada pode medir apenas o desempenho do cache de layout. Para que isso seja evitado, é possível transmitir diferentes parâmetros de layout em cada loop. Isso pode ser difícil em outros casos, como ao medir o desempenho do sistema de arquivos, porque o SO armazena o sistema de arquivos em cache durante o loop.

Código pouco executado

É pouco provável que a compilação JIT pelo Android Runtime (ART) ocorra para um código que é executado somente uma vez durante a inicialização do aplicativo. Por esse motivo, fazer a comparação desse código enquanto ele é executado em loop não é uma forma realista de medir o desempenho.

Para esse tipo de código, recomendamos o rastreamento ou a criação de perfil do código no seu app. Isso não significa que não é possível fazer a comparação do código no caminho de inicialização, e sim que é recomendável escolher um código que seja executado em loop e que provavelmente será compilado em JIT.

Configuração completa do projeto

Para configurar a comparação como habitual em vez de única, isole as comparações no módulo delas. Isso garante que a configuração fique separada dos testes comuns, como a configuração debuggable para false.

Para fazer isso, você precisa seguir as etapas a seguir:

  • Coloque o código e os recursos que você quer avaliar em um módulo de biblioteca, se eles ainda não estiverem em um.

  • Adicione um novo módulo de biblioteca para guardar as comparações.

Nossas amostras apresentam exemplos de como configurar um projeto dessa forma.

Configurar as propriedades do Android Studio

A biblioteca Jetpack Benchmark atualmente está em Alfa, e é necessário configurar manualmente as propriedades do Android Studio para ativar a compatibilidade com o módulo de comparação.

Para habilitar o modelo do Android Studio para uma comparação, faça o seguinte:

  1. Faça o download do Android Studio 3.5 Beta 1 ou versão mais recente.

  2. No Android Studio, clique em Help > Edit Custom Properties.

  3. Adicione a seguinte linha ao arquivo que será aberto:

    npw.benchmark.template.module=true

  4. Salve e feche o arquivo.

  5. Reinicie o Android Studio.

Criar um novo módulo

O modelo do módulo de comparação define automaticamente as configurações da comparação.

Para usar o modelo do módulo para criar um novo módulo, siga as seguintes etapas:

  1. Clique com o botão direito do mouse no seu projeto ou módulo e selecione New > Module.

  2. Selecione Benchmark Module e clique em Next.

    Figura 1. Módulo de comparação.

  3. Insira um nome para o módulo, escolha o idioma e clique em Finish.

    Um módulo pré-configurado para comparação será criado, com um diretório de comparação adicionado e debuggable definido como false.

Escrever uma comparação

As comparações são testes de instrumentação padrão. Para criar uma comparação, use a classe BenchmarkRule oferecida pela biblioteca. Para avaliar atividades, use ActivityTestRule ou ActivityScenarioRule. Para fazer a comparação do código da IU, use @UiThreadTest.

O código a seguir apresenta uma amostra de comparação:

Kotlin

    @RunWith(AndroidJUnit4::class)
    class ViewBenchmark {
        @get:Rule
        val benchmarkRule = BenchmarkRule()

        @Test
        fun simpleViewInflate() {
            val context = ApplicationProvider.getApplicationContext()
            val inflater = LayoutInflater.from(context)
            val root = FrameLayout(context)

            benchmarkRule.keepRunning {
                inflater.inflate(R.layout.test_simple_view, root, false)
            }
        }
    }
    

Java

    @RunWith(AndroidJUnit4::class)
    public class ViewBenchmark {
        @Rule
        public BenchmarkRule benchmarkRule = new BenchmarkRule();

        @Test
        public void simpleViewInflate() {
            Context context = ApplicationProvider.getApplicationContext();
            final BenchmarkState state = benchmarkRule.getState();
            LayoutInflater inflater = LayoutInflater.from(context);
            FrameLayout root = new FrameLayout(context);

            while (state.keepRunning()) {
                inflater.inflate(R.layout.test_simple_view, root, false);
            }
        }
    }
    

Você pode desativar a medição de tempo em seções de código que você não queira avaliar, conforme apresentado na amostra de código a seguir:

Kotlin

    @Test
    fun bitmapProcessing() = benchmarkRule.measureRepeated {
        val input: Bitmap = runWithTimingDisabled { constructTestBitmap() }
        processBitmap(input)
    }
    

Java

    @Test
    public void bitmapProcessing() {
        final BenchmarkState state = benchmarkRule.getState();
        while (state.keepRunning()) {
            state.pauseTiming();
            Bitmap input = constructTestBitmap();
            state.resumeTiming();

            processBitmap(input);
        }
    }
    

Para saber mais sobre como executar uma comparação, consulte Executar a comparação.

Executar a comparação

No Android Studio, execute a comparação da mesma forma que faria com qualquer @Test. No Android Studio 3.4 e versões posteriores, é possível ver a saída enviada para o console.

Para executar a comparação, vá até benchmark/src/androidTest no módulo e pressione Control+Shift+F10 (Command+Shift+R no Mac). Os resultados da comparação aparecem no console, conforme mostrado na Figura 2:

Saída de comparação no Android Studio

Figura 2. Saída de comparação no Android Studio.

Na linha de comando, execute o connectedCheck comum:

./gradlew benchmark:connectedCheck
    

Coletar dados

Um relatório de comparação completo com mais informações sobre as métricas e o dispositivos está disponível em formato JSON.

O plug-in para Gradle androidx.benchmark ativa a saída JSON por padrão. Para ativar a saída JSON manualmente em ambientes de criação que não sejam o Gradle, passe um argumento de instrumentação, androidx.benchmark.output.enable, definido como true.

Veja a seguir um exemplo usando o comando adb shell am instrument:

    adb shell am instrument -w -e "androidx.benchmark.output.enable" "true" com.android.foo/androidx.benchmark.junit.AndroidBenchmarkRunner
    

Por padrão, o relatório JSON é gravado no disco do dispositivo na pasta de downloads compartilhados externos do APK de teste, que normalmente fica localizada em:

    /storage/emulated/0/Download/app_id-benchmarkData.json
    

Você pode configurar o local em que os relatórios de comparação serão salvos no dispositivo usando o argumento de instrumentação additionalTestOutputDir.

    adb shell am instrument -w -e "androidx.benchmark.output.enable" "true" -e "additionalTestOutputDir" "/path_to_a_directory_on_device_test_has_write_permissions_to/" com.android.foo/androidx.benchmark.AndroidBenchmarkRunner
    

Plug-in do Android para Gradle 3.6 e versões posteriores

Ao executar comparações a partir da linha de comando com o Gradle, os projetos que usam o Plug-in do Android para Gradle 3.6 e versões posteriores podem adicionar a seguinte sinalização ao arquivo gradle.properties do projeto:

    android.enableAdditionalTestOutput=true
    

Isso permitirá que o recurso experimental do Plug-in do Android para Gradle extraia relatórios de comparação de dispositivos com API 16 e versões posteriores para o seguinte diretório na máquina host:

    project_root/module/build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/device_id/app_id-benchmarkData.json
    

Plug-in do Android para Gradle 3.5 e versões anteriores

O plug-in para Gradle androidx.benchmark copia o relatório JSON do dispositivo para o host. Eles são gravados na máquina host em:

    project_root/module/build/benchmark_reports/device_id/app_id-benchmarkData.json
    

Para copiar dados usando o AGP 3.5 ou versões anteriores, você precisa adicionar uma sinalização ao arquivo manifest do Android no diretório androidTest das comparações para ativar o comportamento de armazenamento externo legado. Consulte Desativar visualização filtrada para ver mais informações sobre como desativar o armazenamento com escopo definido.

    <manifest ... >
      <!-- This attribute is "false" by default on apps targeting Android Q. -->
      <application android:requestLegacyExternalStorage="true" ... >
        ...
      </application>
    </manifest>
    

Estabilidade do relógio

Os relógios em dispositivos móveis mudam dinamicamente do estado alto (para maior desempenho) para o estado baixo (para economizar energia ou quando o dispositivo esquenta). Esses relógios variados podem fazer com que os números de comparação variem muito. Por esse motivo, a biblioteca oferece maneiras de lidar com esse problema.

Bloquear os relógios (acesso root necessário)

Bloquear os relógios é a melhor maneira de alcançar um desempenho estável. Isso garante que os relógios nunca cheguem a um estado alto o suficiente para aquecer o dispositivo ou um estado baixo caso uma comparação não esteja utilizando a CPU totalmente. Embora essa seja a melhor forma de garantir um desempenho estável, ela não é compatível com a maior parte dos dispositivos, porque exige o acesso root adb.

Para bloquear seus relógios, adicione o plug-in de ajuda fornecido ao caminho de classe de nível mais alto do projeto no arquivo build.gradle principal:

buildscript {
        ...
        dependencies {
            ...
            classpath "androidx.benchmark:benchmark-gradle-plugin:1.0.0-alpha04"
        }
    }
    

Aplique o plug-in no build.gradle do módulo que está sendo avaliado.

apply plugin: com.android.app
    apply plugin: androidx.benchmark
    ...
    

Isso adiciona as tarefas de comparação do Gradle ao seu projeto, incluindo ./gradlew lockClocks e ./gradlew unlockClocks. Use essas tarefas para bloquear e desbloquear a CPU do dispositivo usando o adb.

Se você tiver vários dispositivos visíveis para o adb, use a variável de ambiente ANDROID_SERIAL para especificar em qual dispositivo a tarefa Gradle deve operar:

    ANDROID_SERIAL=device-id-from-adb-devices ./gradlew lockClocks
    

Modo de desempenho sustentado

Window.setSustainedPerformanceMode() é um recurso compatível com alguns dispositivos e que permite que um app opte por uma frequência máxima de CPU mais baixa. Quando executada em dispositivos compatíveis, a biblioteca Benchmark usa uma combinação dessa API e inicia a própria atividade para impedir a limitação térmica e estabilizar os resultados.

Essa funcionalidade é ativada por padrão pelo testInstrumentationRunner configurado pelo plug-in para Gradle. Se você quiser usar um executador personalizado, é possível criar uma subclasse para o AndroidBenchmarkRunner e usá-lo como testInstrumentationRunner.

O executador lança uma atividade opaca em tela cheia para garantir que a comparação seja executada em primeiro plano e sem qualquer outro app drenando a bateria.

Pausar a execução automática

Se nem o bloqueio de relógio nem o desempenho sustentado forem usados, a biblioteca executa a detecção de limitação térmica automaticamente. Quando ativada, a comparação interna é executada periodicamente para determinar quando a temperatura do dispositivo ficou alta o suficiente para diminuir o desempenho da CPU. Quando um baixo desempenho da CPU for detectado, a biblioteca pausará a execução para permitir que o dispositivo resfrie e repetirá a comparação atual.

Erros de configuração

A biblioteca detecta as condições a seguir para garantir que o projeto e o ambiente estejam configurados de modo a apresentar um desempenho preciso para a versão:

  • Debuggable está definido como false.
  • Um dispositivo físico está sendo utilizado, e não um emulador.
  • Os relógios estão bloqueados, caso o dispositivo tenha acesso root.
  • O dispositivo está com um nível de bateria suficiente.

Se qualquer uma das verificações acima apresentar uma falha, a comparação gerará um erro para evitar medições imprecisas.

Para suprimir esses erros no formato de avisos e evitar que a comparação seja interrompida, passe o tipo de erro que você quer suprimir em uma lista separada por vírgulas para o argumento de instrumentação androidx.benchmark.suppressErrors:

    adb shell am instrument -w -e "androidx.benchmark.suppressErrors" "DEBUGGABLE" com.android.foo/androidx.benchmark.junit.AndroidBenchmarkRunner
    

Você pode definir essa lista no Gradle da seguinte forma:

    android {
        defaultConfig {
            testInstrumentationRunnerArgument 'androidx.benchmark.suppressErrors', 'DEBUGGABLE'
        }
    }
    

Suprimir erros permite que a comparação seja executada em uma configuração incorreta, mas a saída da comparação será corrompida intencionalmente incluindo os erros como prefixos dos nomes de testes. Ou seja, quando uma comparação depurável for executada com a supressão acima, os nomes de testes receberão DEBUGGABLE_ como prefixo.

Amostras de comparação

Amostras de código de comparação estão disponíveis nos seguintes projetos:

As amostras de projetos incluem:

  • BenchmarkSample: essa é a amostra independente, que apresenta como usar um módulo de comparação para medir o código e a IU.

  • PagingWithNetworkSample: amostra dos Componentes de arquitetura do Android, que mostra como fazer uma comparação do desempenho do RecyclerView.

  • WorkManagerSample: amostra dos Componentes de arquitetura do Android, que mostra como fazer uma comparação de workers do WorkManager.

Enviar feedback

Para comunicar problemas ou enviar solicitações de recursos usando a comparação, consulte o rastreador de problemas público (link em inglês).