๐Ÿงช Testing, Debugging, and Performance

Baseline Profiles and App Performance

Baseline Profiles tell Android which parts of your code are critical, so they're compiled ahead of time from the very first launch. They're one of the most effective performance improvements available, especially for Compose apps, and they take about an hour to set up.

โœ๏ธ Yunok Tech ๐Ÿ“… Updated โฑ 3 min read ๐ŸŽฏ Advanced
On this page
  1. How it works
  2. Setup with the Gradle plugin
  3. Write the generator
  4. Verify the improvement
  5. Choosing journeys
  6. Libraries ship profiles too
  7. Keeping profiles fresh
  8. Where they help most
  9. Frequently asked questions
  10. Conclusion

On a fresh install, ART hasn't yet learned which code your app runs most. It interprets or JIT-compiles hot paths, so the first launches and first scrolls are slower than later ones. A Baseline Profile ships that knowledge with your app.

How it works

Baseline Profile lifecycle A generator test runs critical user journeys and records the classes and methods used into baseline-prof.txt. The file is packaged with the app. Google Play and ProfileInstaller use it so ART compiles those methods ahead of time on install, making startup and key interactions faster from first launch. Generator testcritical journeys baseline-prof.txthot classes/methods App bundleprofile packaged ART on installAOT-compiles hot code Result: less interpretation and JIT work during startup and first interactions. Measure the gain with Macrobenchmark (CompilationMode.None vs Partial).
The profile describes what to compile, and the platform does the compiling.

Setup with the Gradle plugin

In Android Studio, add a module from the Baseline Profile Generator template. It creates a test module and applies the androidx.baselineprofile plugin. The resulting configuration looks like this:

app/build.gradle.ktsGradle KTS
plugins {
    id("com.android.application")
    id("androidx.baselineprofile")
}
dependencies {
    implementation("androidx.profileinstaller:profileinstaller:<version>")   // installs profiles for sideloaded/non-Play installs
    "baselineProfile"(project(":baselineprofile"))
}

Write the generator

baselineprofile/src/main/java/.../BaselineProfileGenerator.ktKotlin
@RunWith(AndroidJUnit4::class)
@LargeTest
class BaselineProfileGenerator {
    @get:Rule val rule = BaselineProfileRule()

    @Test
    fun generate() = rule.collect(
        packageName = "com.example.app",
        includeInStartupProfile = true,          // also generate a startup profile for DEX layout
    ) {
        // 1. Startup
        pressHome()
        startActivityAndWait()

        // 2. Most important journey: scroll the feed, open an item
        val feed = device.findObject(By.res("feed_list"))
        feed.setGestureMargin(device.displayWidth / 5)
        feed.fling(Direction.DOWN)
        device.waitForIdle()
        device.findObject(By.res("feed_item_0"))?.click()
        device.waitForIdle()
    }
}
Expose Compose test tags as resource IDsKotlin
Scaffold(modifier = Modifier.semantics { testTagsAsResourceId = true }) { /* ... */ }
LazyColumn(Modifier.testTag("feed_list")) { /* ... */ }
GenerateShell
./gradlew :app:generateBaselineProfile
# Writes the profile into the app module's source set; commit it to version control.

Verify the improvement

StartupBenchmark.ktKotlin
@Test fun startupNoProfile() = startup(CompilationMode.None())
@Test fun startupWithProfile() = startup(CompilationMode.Partial(BaselineProfileMode.Require))

private fun startup(mode: CompilationMode) = benchmarkRule.measureRepeated(
    packageName = "com.example.app",
    metrics = listOf(StartupTimingMetric()),
    compilationMode = mode,
    startupMode = StartupMode.COLD,
    iterations = 10,
) { pressHome(); startActivityAndWait() }

Compare the median timeToInitialDisplayMs between the two. Add a FrameTimingMetric benchmark for scrolling journeys too.

Choosing journeys

  • Startup, always.
  • The first screen's main interaction: scrolling the feed, opening search.
  • High-value flows where jank hurts: checkout, the player screen.

Keep journeys deterministic. Use test accounts or fake data so they don't depend on live content.

Libraries ship profiles too

Jetpack libraries such as Compose include their own Baseline Profile rules, which are merged into your app. Your profile adds your code paths on top.

Keeping profiles fresh

  • Regenerate when critical journeys or major code paths change, for example before each release.
  • Automate generation on CI with Gradle Managed Devices where feasible.
  • Track startup benchmarks over time to catch regressions.

Where they help most

Compose-heavy apps

Compose is a large library executed at startup. Profiles reduce first-frame and first-scroll cost.

Apps with frequent updates

Each update resets learned profiles on devices, and shipping a profile restores fast performance immediately.

Mid-range devices

The slower the device, the more noticeable the difference.

Frequently asked questions

Do Baseline Profiles work outside Google Play?

With the profileinstaller library, profiles can also be installed for sideloaded builds and other distribution channels, as described in the documentation.

Do they increase APK size?

Only slightly: the profile is a text-based list compiled into a compact binary format.

Baseline Profile vs startup profile?

Baseline Profiles guide AOT compilation. Startup profiles guide DEX layout so startup code is packed together. The plugin can generate both from the same journeys.

Conclusion

Baseline Profiles pre-compile your critical code paths so users get fast startup and smooth first interactions from the first launch. Generate them from deterministic journeys, commit them, verify the gain with Macrobenchmark, and regenerate as the app evolves.

Next step: shrink and optimise your release build with ProGuard and R8 Explained.

Official references

๐Ÿ“ฑ Apps built by Yunok Tech

Real Android apps on Google Play, free to download.

See all our apps โ†’

Ready to build real Android applications?

Performance work starts with understanding how Android apps are built and how they behave at runtime. Strengthen those fundamentals and the profiling tools start to make sense.

Move beyond theory and strengthen your Android development skills with Yunoktech's Android course. Learn core concepts, practical implementation, modern Android development techniques, and the reasoning behind building maintainable applications.

Explore the Android course and take your next step as an Android developer.

Share: LinkedIn X / Twitter WhatsApp