대부분의 Android UI 테스트와 달리 Macrobenchmark 테스트는 앱 자체와 분리된 프로세스에서 실행됩니다. 이는 앱 프로세스를 중지하고 DEX 바이트 코드를 기계어 코드로 컴파일하는 등의 작업을 위해 필요합니다.
UIAutomator 라이브러리를 사용하거나 테스트 프로세스에서 타겟 앱을 제어할 수 있는 다른 메커니즘을 사용하여 앱 상태를 유도할 수 있습니다.
Espresso 또는 ActivityScenario는 앱과 공유된 프로세스에서 실행되리라 예상하기 때문에 Macrobenchmark에서는 사용할 수 없습니다.
다음 예에서는 관련 리소스 ID를 사용하여 RecyclerView를 찾고 여러 번 아래로 스크롤합니다.
Kotlin
@TestfunscrollList(){benchmarkRule.measureRepeated(// ...setupBlock={// Before starting to measure, navigate to the UI to be measuredvalintent=Intent("$packageName.RECYCLER_VIEW_ACTIVITY")startActivityAndWait(intent)}){valrecycler=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 timesrepeat(3){recycler.fling(Direction.DOWN)}}}
@TestpublicvoidscrollList(){benchmarkRule.measureRepeated(// .../* setupBlock */scope->{// Before measuring, navigate to the UI to be measured.valintent=Intent("$packageName.RECYCLER_VIEW_ACTIVITY")scope.startActivityAndWait();returnUnit.INSTANCE;},/* measureBlock */scope->{UiDevicedevice=scope.getDevice();UiObject2recycler=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(inti=0;i<3;i++){recycler.fling(Direction.DOWN);}returnUnit.INSTANCE;});}
벤치마크에서는 UI를 스크롤하지 않아도 됩니다. 대신 애니메이션 등을 실행할 수 있습니다. 또한 UI Automator를 특별히 사용할 필요가 없습니다. Jetpack Compose에서 생성된 프레임을 포함하여 뷰 시스템에서 프레임이 생성되는 한 성능 측정항목이 수집됩니다.
앱의 내부로 이동
외부에서 직접 액세스할 수 없는 앱 부분을 벤치마킹하려는 경우가 있습니다. 예를 들어 exported=false로 표시된 내부 활동에 액세스하거나, Fragment로 이동하거나, 일부 UI를 스와이프하여 제거하는 것일 수 있습니다. 벤치마크는 사용자처럼 앱의 이러한 부분으로 수동으로 이동해야 합니다.
수동으로 이동하려면 setupBlock{} 내의 코드를 변경하여 버튼 탭 또는 스와이프와 같은 원하는 효과를 포함합니다. measureBlock{}에는 실제로 벤치마킹하려는 UI 조작만 포함됩니다.
Kotlin
@TestfunnonExportedActivityScrollList(){benchmarkRule.measureRepeated(// ...setupBlock=setupBenchmark()){// ...}}privatefunsetupBenchmark():MacrobenchmarkScope.()->Unit={// Before starting to measure, navigate to the UI to be measuredstartActivityAndWait()// 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.valselector=By.text("RecyclerView")if(!device.wait(Until.hasObject(selector),5_500)){fail("Could not find resource in time")}vallaunchRecyclerActivity=device.findObject(selector)launchRecyclerActivity.click()// wait until the activity is showndevice.wait(Until.hasObject(By.clazz("$packageName.NonExportedRecyclerActivity")),TimeUnit.SECONDS.toMillis(10))}
@TestpublicvoidscrollList(){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.UiObject2launchRecyclerActivity=scope.getDevice().findObject(By.res(packageName,"launchRecyclerActivity"))launchRecyclerActivity.click();// Wait until activity is shown.scope.getDevice().wait(Until.hasObject(By.clazz("$packageName.NonExportedRecyclerActivity")),10000L)returnUnit.INSTANCE;},/* measureBlock */scope->{// ...});}
이 페이지에 나와 있는 콘텐츠와 코드 샘플에는 콘텐츠 라이선스에서 설명하는 라이선스가 적용됩니다. 자바 및 OpenJDK는 Oracle 및 Oracle 계열사의 상표 또는 등록 상표입니다.
최종 업데이트: 2025-09-09(UTC)
[[["이해하기 쉬움","easyToUnderstand","thumb-up"],["문제가 해결됨","solvedMyProblem","thumb-up"],["기타","otherUp","thumb-up"]],[["필요한 정보가 없음","missingTheInformationINeed","thumb-down"],["너무 복잡함/단계 수가 너무 많음","tooComplicatedTooManySteps","thumb-down"],["오래됨","outOfDate","thumb-down"],["번역 문제","translationIssue","thumb-down"],["샘플/코드 문제","samplesCodeIssue","thumb-down"],["기타","otherDown","thumb-down"]],["최종 업데이트: 2025-09-09(UTC)"],[],[],null,["# Control your app from Macrobenchmark\n\nUnlike most Android UI tests, Macrobenchmark tests run in a separate process\nfrom the app itself. This is necessary to enable things like stopping the\napp process and compiling from DEX bytecode to machine code.\n\nYou can drive your app's state using the [UIAutomator library](/training/testing/ui-automator) or other\nmechanisms that can control the target app from the test process.\nYou can't use [Espresso](/training/testing/espresso) or [`ActivityScenario`](/reference/androidx/test/core/app/ActivityScenario) for\nMacrobenchmark because they expect to run in a shared process with the app.\n\nThe following example finds a [`RecyclerView`](/reference/androidx/recyclerview/widget/RecyclerView) using its resource ID and\nscrolls down several times: \n\n### Kotlin\n\n```kotlin\n@Test\nfun scrollList() {\n benchmarkRule.measureRepeated(\n // ...\n setupBlock = {\n // Before starting to measure, navigate to the UI to be measured\n val intent = Intent(\"$packageName.RECYCLER_VIEW_ACTIVITY\")\n startActivityAndWait(intent)\n }\n ) {\n val recycler = device.findObject(By.res(packageName, \"recycler\"))\n // Set gesture margin to avoid triggering gesture navigation\n // with input events from automation.\n recycler.setGestureMargin(device.displayWidth / 5)\n\n // Scroll down several times\n repeat(3) { recycler.fling(Direction.DOWN) }\n }\n}https://github.com/android/performance-samples/blob/570221d3f49d49e45af7970c04c1fe3a72d6df33/MacrobenchmarkSample/macrobenchmark/src/main/java/com/example/macrobenchmark/benchmark/frames/FrameTimingBenchmark.kt#L44-L70\n```\n\n### Java\n\n```java\n@Test\npublic void scrollList() {\n benchmarkRule.measureRepeated(\n // ...\n /* setupBlock */ scope -\u003e {\n // Before measuring, navigate to the UI to be measured.\n val intent = Intent(\"$packageName.RECYCLER_VIEW_ACTIVITY\")\n scope.startActivityAndWait();\n return Unit.INSTANCE;\n },\n /* measureBlock */ scope -\u003e {\n UiDevice device = scope.getDevice();\n UiObject2 recycler = device.findObject(By.res(scope.getPackageName(), \"recycler\"));\n\n // Set gesture margin to avoid triggering gesture navigation\n // with input events from automation.\n recycler.setGestureMargin(device.getDisplayWidth() / 5);\n\n // Fling the recycler several times.\n for (int i = 0; i \u003c 3; i++) {\n recycler.fling(Direction.DOWN);\n }\n\n return Unit.INSTANCE;\n }\n );\n}\n```\n\nYour benchmark doesn't have to scroll the UI. Instead, it can run an\nanimation, for example. It also doesn't need to use UI Automator\nspecifically. It collects performance metrics as long as frames are being\nproduced by the view system, including frames produced by [Jetpack Compose](/jetpack/compose).\n| **Note:** When accessing UI objects, specify the `packageName`, because the tests run in a separate process.\n\nNavigate to internal parts of the app\n-------------------------------------\n\nSometimes you want to benchmark parts of your app that aren't directly\naccessible from outside. This might be, for example, accessing inner Activities\nthat are marked with [`exported=false`](/guide/topics/manifest/activity-element#exported), navigating to a [`Fragment`](/reference/android/app/Fragment), or swiping\nsome part of your UI away. The benchmarks need to manually navigate to these\nparts of the app like a user.\n\nTo manually navigate, change the code inside `setupBlock{}` to contain the\neffect you want, such as button tap or swipe. Your `measureBlock{}` contains\nonly the UI manipulation you want to actually benchmark: \n\n### Kotlin\n\n```kotlin\n@Test\nfun nonExportedActivityScrollList() {\n benchmarkRule.measureRepeated(\n // ...\n setupBlock = setupBenchmark()\n ) {\n // ...\n }\n}\n\nprivate fun setupBenchmark(): MacrobenchmarkScope.() -\u003e Unit = {\n // Before starting to measure, navigate to the UI to be measured\n startActivityAndWait()\n\n // click a button to launch the target activity.\n // While we use button text here to find the button, you could also use\n // accessibility info or resourceId.\n val selector = By.text(\"RecyclerView\")\n if (!device.wait(Until.hasObject(selector), 5_500)) {\n fail(\"Could not find resource in time\")\n }\n val launchRecyclerActivity = device.findObject(selector)\n launchRecyclerActivity.click()\n\n // wait until the activity is shown\n device.wait(\n Until.hasObject(By.clazz(\"$packageName.NonExportedRecyclerActivity\")),\n TimeUnit.SECONDS.toMillis(10)\n )\n}https://github.com/android/performance-samples/blob/570221d3f49d49e45af7970c04c1fe3a72d6df33/MacrobenchmarkSample/macrobenchmark/src/main/java/com/example/macrobenchmark/benchmark/frames/NonExportedActivityBenchmark.kt#L48-L94\n```\n\n### Java\n\n```java\n@Test\npublic void scrollList() {\n benchmarkRule.measureRepeated(\n // ...\n /* setupBlock */ scope -\u003e {\n // Before measuring, navigate to the default activity.\n scope.startActivityAndWait();\n\n // Click a button to launch the target activity.\n // While you use resourceId here to find the button, you can also\n // use accessibility info or button text content.\n UiObject2 launchRecyclerActivity = scope.getDevice().findObject(\n By.res(packageName, \"launchRecyclerActivity\")\n )\n launchRecyclerActivity.click();\n\n // Wait until activity is shown.\n scope.getDevice().wait(\n Until.hasObject(By.clazz(\"$packageName.NonExportedRecyclerActivity\")),\n 10000L\n )\n\n return Unit.INSTANCE;\n },\n /* measureBlock */ scope -\u003e {\n // ...\n }\n );\n}\n```\n\nRecommended for you\n-------------------\n\n- Note: link text is displayed when JavaScript is off\n- [Writing a Macrobenchmark](/topic/performance/benchmarking/macrobenchmark-overview)\n- [Capture Macrobenchmark metrics](/topic/performance/benchmarking/macrobenchmark-metrics)\n- [Microbenchmark](/topic/performance/benchmarking/microbenchmark-overview)"]]