Skip to content
ExponaBLOG
MODERN ANDROID & KOTLIN · DAY 051 / 100

Baseline profiles: a Android & Kotlin guide for developers

Faster startup and smoother scrolling.

Expona Admin6 min read

Introduction

Baseline profiles represent one of the most effective ways to boost Android app performance without restructuring your codebase. By precompiling critical execution paths ahead of time, you can see startup improvements of 20–30% and noticeably smoother scrolling during everyday use. In this guide on baseline profiles for Android and Kotlin developers, we'll walk through how to measure your app's hot paths, generate profiles with Macrobenchmark, and ship them in your release builds.

Understanding Baseline Profiles and Why They Matter

When your app launches or performs intensive operations like scrolling a feed, the Android runtime (ART) interprets Kotlin bytecode on the fly. This just-in-time (JIT) compilation introduces latency: users see frame drops, delayed interactions, and sluggish startup sequences. Baseline profiles tell ART which methods are critical, so the system can ahead-of-time (AOT) compile them before the user ever launches your app.

Think of it this way: instead of figuring out what's hot while your user waits, you've already precomputed which code paths matter. The result is immediate performance gains with zero runtime overhead. For e-commerce and travel apps where first-impression latency kills conversions, baseline profiles are non-negotiable.

Precompile Critical Code Paths

The first step is identifying which methods run during startup and user-critical interactions. You're looking for:

  • Application initialization and dependency injection setup
  • Initial UI composition and layout inflation
  • Database queries on app launch
  • Network request parsing for initial data
  • RecyclerView/LazyColumn rendering during scrolling

For a ride-hailing app, this might include the activity resume path, geolocation initialization, and the composable recomposition when the driver's location updates. For an e-commerce platform, it's product listing queries, image loading, and checkout form rendering.

You don't need to manually list every method. Instead, you'll use tooling to capture real user behavior and let profiling surface the hot paths automatically. The key insight is that 80% of runtime typically concentrates in 20% of your code; baseline profiles focus compilation effort where it matters most.

Generate Profiles with Macrobenchmark

Android's Macrobenchmark library instruments your app under controlled conditions and captures CPU activity, frame timing, and method execution frequency. It's the standard tool for creating baseline profiles in production-quality projects.

Start by adding Macrobenchmark to your test dependencies:

kotlin
// build.gradle.kts (or :benchmark module)
dependencies {
    androidTestImplementation("androidx.benchmark:benchmark-macro-junit4:1.2.0")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.uiautomator:uiautomator:2.2.0")
}

Next, create a benchmark test that simulates real user flow. This example scrolls through a product listing, which is a common hotspot in e-commerce:

kotlin
@RunWith(AndroidJUnit4::class)
class StartupBenchmarks {
    @get:Rule
    val baselineProfileRule = BaselineProfileRule()

    @Test
    fun startup() = baselineProfileRule.collect(
        packageName = "com.example.ecommerce"
    ) {
        pressHome()
        startActivityAndWait()
        // Navigate to product list and scroll
        device.findObject(UiSelector().text("Products")).click()
        device.waitForWindowUpdate(3_000)
        
        repeat(5) {
            device.findObject(UiSelector().className("android.widget.ListView"))
                .swipeUp(10)
        }
    }
}

@RunWith(AndroidJUnit4::class)
class ScrollBenchmarks {
    @get:Rule
    val baselineProfileRule = BaselineProfileRule()

    @Test
    fun feedScroll() = baselineProfileRule.collect(
        packageName = "com.example.travel"
    ) {
        startActivityAndWait()
        val feed = device.findObject(UiSelector().resourceId("feed_recycler"))
        
        repeat(3) {
            feed.swipeUp(5)
            device.waitForWindowUpdate(500)
        }
    }
}

The BaselineProfileRule automatically instruments your app and generates an aggregated profile of method calls and their frequency. Run this test on a real device or emulator—Macrobenchmark captures real Android runtime behavior, not synthetic metrics.

Once the test runs successfully, Macrobenchmark generates baseline-prof.txt in your src/main directory. This file lists methods and classes in a simple text format that ART understands:

kotlin
Landroid/content/res/Resources;->getQuantityString(II)Ljava/lang/String;
Landroidx/compose/runtime/Composer;->startNode()V
Lcom/example/ecommerce/data/ProductRepository;->getProducts()Lkotlin/coroutines/Job;
Lcom/example/ecommerce/ui/ProductListComposable;->invoke(Landroidx/compose/runtime/Composer;I)V

Often 20–30% Faster Startup

How much improvement should you expect? Real-world benchmarks from Google's own apps show consistent gains:

Scenario Typical Improvement
Cold startup (full recompilation) 20–30%
Warm startup (partial JIT) 10–15%
Frame rate during initial scroll 15–40% fewer janks
Time-to-interactive (first touch response) 25–35%

These aren't outliers; they're typical across different app categories. A SaaS platform cutting startup by 800ms sees measurable improvements in user retention. A CRM app shaving 500ms off login-to-dashboard means field reps stay productive instead of watching loading spinners.

Measure your app's baseline before and after with Perfetto tracing or Android Profiler. Capture startup time with shell commands:

kotlin
// Measure cold startup
adb shell cmd package dump-profiles --clear-cache
adb shell am start -W com.example.app/.MainActivity
adb logcat | grep "TotalTime"

Run this ten times and average the results. Then apply your baseline profile, clean the app cache, and repeat. The delta is your real-world gain.

Ship in Release Builds

Baseline profiles only work when packaged as part of your APK or AAB. The Android build system looks for src/main/baseline-prof.txt during compilation and embeds it into the app bundle. No additional configuration required—just ensure the file exists before building release variants.

If you're using Play Console delivery, baseline profiles ship with every build by default. For direct APK distribution or side-loading, verify the profile is in your final package:

kotlin
// Verify profile is packaged
unzip -l app-release.apk | grep baseline

You should see baseline-prof.txt in the root of the APK. If it's missing, the build system may have skipped it due to a path issue or naming mismatch.

For continuous improvement, regenerate profiles periodically—quarterly or after major features. User behavior shifts; your profiles should reflect current hotspots. Integrate Macrobenchmark tests into CI/CD so profile generation is reproducible and version-controlled.

Integrating Baseline Profiles into Your Gradle Build

Modern Android Gradle Plugin handles baseline profiles automatically, but here's how to ensure proper configuration:

kotlin
// build.gradle.kts (app module)
android {
    namespace = "com.example.ecommerce"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.ecommerce"
        minSdk = 21
        targetSdk = 34
    }

    buildTypes {
        release {
            // Baseline profiles are always used in release builds
            isMinifyEnabled = true
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"))
        }
    }

    // Benchmark module links to baseline profiles
}

// In :benchmark module build.gradle.kts
android {
    targetProjectPath = ":app"
}

Place your generated baseline-prof.txt in app/src/main/baseline-prof.txt. The build system automatically applies it during release builds. No manual configuration needed.

Common Mistakes

Generating profiles on emulator instead of real hardware: Emulator performance diverges significantly from device behavior. Generate profiles on a representative device (mid-range Android phone, similar specs to your target audience).

Profiles that are too aggressive: If your profile includes every method in your app, you'll defeat the purpose and bloat the APK. Macrobenchmark naturally filters to the hottest paths; trust the data.

Not validating improvements: Some teams generate profiles and ship without measuring before/after. Use Perfetto traces or systrace to confirm your specific app improved. Not all apps benefit equally; extremely simple apps may see minimal gains.

Stale profiles in CI/CD: If your CI regenerates profiles with every build, you might include noise from test environments. Generate once per release cycle with controlled hardware and check the result into version control.

Ignoring obfuscation: If you minify with ProGuard or R8, method names in your profile must match obfuscated names. Most tools handle this automatically, but double-check your release build actually includes the profile file.

Key Takeaways

  • Baseline profiles precompile hot code paths, delivering 20–30% startup improvements with no runtime cost.
  • Use Macrobenchmark to automatically detect and capture real user-critical flows instead of guessing.
  • Generate profiles on actual hardware under controlled conditions, not on emulators or staging environments.
  • Place baseline-prof.txt in src/main/ and let the build system embed it in release APKs and AABs automatically.
  • Measure cold startup before and after profiles to validate improvements in your specific app context.
  • Regenerate profiles quarterly or after major features to stay aligned with evolving user behavior.
  • Combine baseline profiles with other optimizations (lazy initialization, efficient layouts, smaller images) for compounding gains.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles

Baseline profiles | Android & Kotlin Guide | Expona