通过微基准测试控制应用

与大多数 Android 界面测试不同,微基准测试在独立于应用本身的进程中运行。必须这样做才能实现停止应用进程和从 DEX 字节码编译到机器代码等目的。

您可以使用 UIAutomator 库或其他可以在测试进程中控制目标应用的机制来驱动应用的状态。EspressoActivityScenario 等方法对微基准测试不起作用,因为它们需要在与应用共享的进程中运行。

以下示例会使用资源 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 starting to measure, 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 Automator;只要帧是由视图系统生成的(其中包括 Jetpack Compose 生成的帧),系统就会收集性能指标。

有时,您需要对应用中无法直接从外部访问的部分进行基准测试。例如,您可能需要访问内部 activity(标有 exported=false)、转到 Fragment,或者滑开界面的某个部分。基准测试需要像用户一样“手动”导航到应用的这些部分。

这可以通过更改 setupBlock{} 内的代码以包含所需的效果(按钮点击、滑动等)来实现。您的 measureBlock{} 仅包含您要实际进行基准测试的界面操作。

Kotlin

@Test
fun nonExportedActivityScrollList() {
    benchmarkRule.measureRepeated(
        // ...
        setupBlock = {
            // Before starting to measure, navigate to the UI to be measured
            startActivityAndWait()

            // click a button to launch the target activity.
            // While we use resourceId here to find the button, you could also use
            // accessibility info or button text content.
            val launchRecyclerActivity = device.findObject(
                By.res(packageName, "launchRecyclerActivity")
            )
            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 starting to measure, navigate to the default activity
            scope.startActivityAndWait();

            // click a button to launch the target activity.
            // While we use resourceId here to find the button, you could 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 -> {
            // ...
        }
    );
}