Controla tu app desde Macrobenchmark

A diferencia de la mayoría de las pruebas de IU de Android, las pruebas de Macrobenchmark se ejecutan en un proceso independiente de la app. Esto es necesario para habilitar acciones como detener el proceso de la app y compilar desde el código de bytes DEX hasta el código máquina.

Puedes controlar el estado de tu app con la biblioteca de UIAutomator o algún otro mecanismo que pueda controlar la app objetivo desde el proceso de prueba. No puedes usar Espresso ni ActivityScenario para Macrobenchmark porque pretenden ejecutarse en un proceso compartido con la app.

En el siguiente ejemplo, se encuentra un elemento RecyclerView que usa su ID de recurso y se desplaza hacia abajo varias veces:

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

No es necesario que las comparativas se desplacen por la IU. En cambio, pueden ejecutar una animación, por ejemplo. Tampoco es necesario que usen específicamente UIAutomator. Recopilan métricas de rendimiento, siempre y cuando el sistema de vistas produzca los fotogramas, incluidos los que genera Jetpack Compose.

A veces, quieres comparar partes de tu app a las que no se puede acceder directamente desde el exterior. Puede ser, por ejemplo, el acceso a actividades internas marcadas con exported=false, la navegación a un elemento Fragment o el deslizamiento de algunos elementos parte de la IU. Las comparativas deben navegar a estas partes de la app de forma manual como un usuario.

Para navegar de forma manual, cambia el código dentro de setupBlock{} para que contenga el efecto que deseas, como presionar un botón o deslizar el dedo. Tu measureBlock{} contiene solo la manipulación de la IU que realmente quieres 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 -> {
            // ...
        }
    );
}