What You'll Learn
Macrobenchmark (app startup time measurement, scroll performance, automatic Baseline Profile generation, CI measurement) explained.
Setup
// benchmark/build.gradle.kts
plugins {
id("com.android.test")
id("org.jetbrains.kotlin.android")
}
android {
namespace = "com.example.benchmark"
compileSdk = 35
defaultConfig {
minSdk = 28
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
targetProjectPath = ":app"
experimentalProperties["android.experimental.self-instrumenting"] = true
}
dependencies {
implementation("androidx.benchmark:benchmark-macro-junit4:1.3.3")
}
Startup Time Benchmark
@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
@Test
fun startupCold() = benchmarkRule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(StartupTimingMetric()),
iterations = 5,
startupMode = StartupMode.COLD
) {
pressHome()
startActivityAndWait()
}
@Test
fun startupWarm() = benchmarkRule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(StartupTimingMetric()),
iterations = 5,
startupMode = StartupMode.WARM
) {
pressHome()
startActivityAndWait()
}
}
Scroll Benchmark
@RunWith(AndroidJUnit4::class)
class ScrollBenchmark {
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
@Test
fun scrollList() = benchmarkRule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(FrameTimingMetric()),
iterations = 5,
startupMode = StartupMode.COLD
) {
startActivityAndWait()
val list = device.findObject(By.res("item_list"))
list.setGestureMargin(device.displayWidth / 5)
repeat(3) {
list.fling(Direction.DOWN)
device.waitForIdle()
}
}
}
Baseline Profile Generation
@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
@get:Rule
val rule = BaselineProfileRule()
@Test
fun generateBaselineProfile() = rule.collect(
packageName = "com.example.app"
) {
// Startup
pressHome()
startActivityAndWait()
// Main user flow
device.findObject(By.text("Home")).click()
device.waitForIdle()
// List scroll
val list = device.findObject(By.res("item_list"))
list?.fling(Direction.DOWN)
device.waitForIdle()
// Detail screen
device.findObject(By.res("list_item"))?.click()
device.waitForIdle()
// Settings
device.pressBack()
device.findObject(By.text("Settings")).click()
device.waitForIdle()
}
}
CI Integration
# .github/workflows/benchmark.yml
name: Benchmark
on:
pull_request:
paths: ['app/**']
jobs:
benchmark:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
- uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 34
arch: x86_64
script: ./gradlew :benchmark:connectedCheck
- uses: actions/upload-artifact@v4
with:
name: benchmark-results
path: benchmark/build/outputs/connected_android_test_additional_output/
Summary
| Metric | Measures |
|---|---|
StartupTimingMetric |
Startup time |
FrameTimingMetric |
Frame timing |
TraceSectionMetric |
Custom section |
BaselineProfileRule |
Profile generation |
-
MacrobenchmarkRulefor device benchmark - Cold/Warm/Hot startup measurement
-
FrameTimingMetricdetects jank -
BaselineProfileRuleauto-generates profile
Ready-Made Android App Templates
8 production-ready Android app templates with Jetpack Compose, MVVM, Hilt, and Material 3.
Browse templates → Gumroad
Top comments (0)