Contrôler votre application depuis Macrobenchmark

Contrairement à la plupart des tests de l'interface utilisateur Android, les tests Macrobenchmark s'exécutent dans un processus distinct de l'appli elle-même. Cela est nécessaire pour permettre, entre autres, d'arrêter le processus de l'application et de compiler du bytecode DEX en code machine.

Vous pouvez contrôler l'état de votre application à l'aide de la bibliothèque UIAutomator ou d'autres mécanismes pouvant contrôler l'application cible à partir du processus de test. Vous ne pouvez pas utiliser Espresso ni ActivityScenario pour Macrobenchmark, car ils doivent s'exécuter dans un processus partagé avec l'application.

L'exemple suivant trouve une RecyclerView à l'aide de son ID de ressource et fait défiler plusieurs fois vers le bas :

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;
        }
    );
}

Votre benchmark n'a pas besoin de faire défiler l'interface utilisateur. À la place, il peut par exemple exécuter une animation. Il n'a pas non plus besoin d'utiliser UIAutomator en particulier. Il collecte des métriques de performances tant que les frames sont produits par le système de vues, qui comprend les frames produits par Jetpack Compose.

Vous pouvez parfois comparer des parties de votre application qui ne sont pas directement accessibles de l'extérieur. Il peut s'agir, par exemple, d'accéder à des activités internes marquées de exported=false, d'accéder à un Fragment ou de balayer une partie de votre interface utilisateur pour la faire disparaître. Les benchmarks doivent accéder manuellement à ces parties de l'application, comme le ferait un utilisateur.

Pour naviguer manuellement, modifiez le code dans setupBlock{} pour contenir l'effet souhaité, par exemple un appui sur un bouton ou un balayage. Votre measureBlock{} ne contient que la manipulation de l'interface utilisateur que vous souhaitez comparer :

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 -> {
            // ...
        }
    );
}