从 Macrobenchmark 控制您的应用

与大多数 Android UI 测试不同,Macrobenchmark 测试在与应用本身不同的单独进程中运行。这是为了启用停止应用进程和从 DEX 字节码编译为机器码等功能。

您可以使用 UIAutomator 库 或其他可以从测试进程控制目标应用的机制来驱动应用的状态。您不能使用 EspressoActivityScenario 来执行 Macrobenchmark,因为它们期望在与应用共享的进程中运行。

以下示例使用资源 ID 查找 RecyclerView 并向下滚动几次

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

您的基准测试不必滚动 UI。相反,它可以运行动画,例如。它也不需要专门使用 UI Automator。只要视图系统正在生成帧,它就会收集性能指标,包括由 Jetpack Compose 生成的帧。

有时您想对应用程序中无法从外部直接访问的部分进行基准测试。例如,这可能是访问标记为 exported=false 的内部 Activity,导航到 Fragment 或滑动 UI 的一部分。基准测试需要像用户一样手动导航到应用程序的这些部分。

要手动导航,请更改 setupBlock{} 中的代码以包含您想要的效果,例如按钮点击或滑动。您的 measureBlock{} 仅包含您想实际衡量的 UI 操作

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