Controlar o app usando a Macrobenchmark

Diferente da maioria dos testes de interface do Android, os testes da Macrobenchmark acontecem em um processo separado do próprio app. Isso é necessário para ativar ações como interromper o processo do app e compilar o bytecode DEX para um código de máquina.

Você pode controlar o estado do app usando a biblioteca UIAutomator ou outros mecanismos que podem controlar o app de destino do processo de teste. Não é possível usar o Espresso ou o ActivityScenario, porque geralmente são executados em um processo compartilhado com o app.

O exemplo abaixo encontra uma RecyclerView usando o próprio ID de recurso e rola a tela para baixo várias vezes:

Kotlin

@Test
fun scrollList() {
    benchmarkRule.measureRepeated(
        // ...
        setupBlock = {
            // Before starting to measure, navigate to the UI to be measured
            val intent = Intent("$packageName.RECYCLER_VIEW_ACTIVITY")
            startActivityAndWait(intent)
        }
    ) {
        val recycler = device.findObject(By.res(packageName, "recycler"))
        // Set gesture margin to avoid triggering gesture navigation
        // with input events from automation.
        recycler.setGestureMargin(device.displayWidth / 5)

        // Scroll down several times
        repeat(3) { recycler.fling(Direction.DOWN) }
    }
}

Java

@Test
public void scrollList() {
    benchmarkRule.measureRepeated(
        // ...
        /* setupBlock */ scope -> {
            // Before measuring, navigate to the UI to be measured.
            val intent = Intent("$packageName.RECYCLER_VIEW_ACTIVITY")
            scope.startActivityAndWait();
            return Unit.INSTANCE;
        },
        /* measureBlock */ scope -> {
            UiDevice device = scope.getDevice();
            UiObject2 recycler = device.findObject(By.res(scope.getPackageName(), "recycler"));

            // Set gesture margin to avoid triggering gesture navigation
            // with input events from automation.
            recycler.setGestureMargin(device.getDisplayWidth() / 5);

            // Fling the recycler several times.
            for (int i = 0; i < 3; i++) {
                recycler.fling(Direction.DOWN);
            }

            return Unit.INSTANCE;
        }
    );
}

A comparação não precisa rolar a interface. Em vez disso, ela pode gerar uma animação, por exemplo. Ela também não precisa usar a biblioteca UI Automator especificamente. As métricas de performance são coletadas, contanto que os frames estejam sendo produzidos pelo sistema de visualização, incluindo frames produzidos pelo Jetpack Compose.

Às vezes, você quer comparar partes do app que não são diretamente acessíveis de fora. Isso pode acontecer, por exemplo, ao acessar atividades internas marcadas com exported=false, navegar para um Fragment ou deslizar algumas parte da interface. As comparações precisam navegar manualmente para essas partes do app, da mesma forma como um usuário faria.

Para navegar manualmente, mude o código em setupBlock{} para conter o efeito desejado, como tocar ou deslizar um botão. O measureBlock{} contém apenas a manipulação de interface que você quer mesmo comparar:

Kotlin

@Test
fun nonExportedActivityScrollList() {
    benchmarkRule.measureRepeated(
        // ...
        setupBlock = setupBenchmark()
    ) {
        // ...
    }
}

private fun setupBenchmark(): MacrobenchmarkScope.() -> Unit = {
    // Before starting to measure, navigate to the UI to be measured
    startActivityAndWait()

    // click a button to launch the target activity.
    // While we use button text  here to find the button, you could also use
    // accessibility info or resourceId.
    val selector = By.text("RecyclerView")
    if (!device.wait(Until.hasObject(selector), 5_500)) {
        fail("Could not find resource in time")
    }
    val launchRecyclerActivity = device.findObject(selector)
    launchRecyclerActivity.click()

    // wait until the activity is shown
    device.wait(
        Until.hasObject(By.clazz("$packageName.NonExportedRecyclerActivity")),
        TimeUnit.SECONDS.toMillis(10)
    )
}

Java

@Test
public void scrollList() {
    benchmarkRule.measureRepeated(
        // ...
        /* setupBlock */ scope -> {
            // Before measuring, navigate to the default activity.
            scope.startActivityAndWait();

            // Click a button to launch the target activity.
            // While you use resourceId here to find the button, you can also
            // use accessibility info or button text content.
            UiObject2 launchRecyclerActivity = scope.getDevice().findObject(
                By.res(packageName, "launchRecyclerActivity")
            )
            launchRecyclerActivity.click();

            // Wait until activity is shown.
            scope.getDevice().wait(
                Until.hasObject(By.clazz("$packageName.NonExportedRecyclerActivity")),
                10000L
            )

            return Unit.INSTANCE;
        },
        /* measureBlock */ scope -> {
            // ...
        }
    );
}