Contrairement à la plupart des tests de l'interface utilisateur Android, les tests Macrobenchmark s'exécutent dans un processus distinct de l'application 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.
Les approches telles que Espresso ou ActivityScenario
ne fonctionnent pas pour Macrobenchmark, car elles 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 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; } ); }
Votre benchmark n'a pas besoin de faire défiler l'interface utilisateur. Il pourrait, par exemple, exécuter une animation. Il n'a pas non plus besoin d'utiliser UIAutomator en particulier. Tant que les frames sont produits par le système de vues, qui comprend les frames produits par Jetpack Compose, les métriques de performances sont recueillies.
Accéder aux sections internes de l'application
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 (identifiées par exported=false
), d'accéder à un Fragment
ou de faire disparaître une partie de l'interface utilisateur. Les benchmarks doivent accéder manuellement à ces sections de l'application, comme le ferait un utilisateur.
Pour cela, modifiez le code dans setupBlock{}
pour contenir l'effet souhaité (clic sur un bouton, balayage, etc.). 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 resourceId here to find the button, you could also use // accessibility info or button text content. val selector = By.res(packageName, "launchRecyclerActivity") 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 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 -> { // ... } ); }