An Android application can work correctly and still feel slow when users open it for the first time. The launch screen may remain visible for several seconds, navigation may feel delayed, or the first interaction with a screen may produce noticeable stuttering.
These problems are not always caused by inefficient application code. They can also occur because Android has not yet optimized the code paths that the application executes most frequently.
Android Baseline Profiles help address this problem by identifying important code paths that should be optimized during installation. Instead of waiting for the application to learn which methods are frequently used through normal execution, a Baseline Profile gives the Android runtime information about code that matters early in the user experience.
For Android developers, Baseline Profiles provide a practical way to improve startup performance and reduce delays during common interactions without rewriting the entire application.
This article explains how Baseline Profiles work, how to configure them in an Android project, how to generate and verify them, and which mistakes to avoid when introducing them into a production release.
What Are Android Baseline Profiles?
Android Baseline Profiles are files that describe important code paths in an application. The Android runtime can use this information to perform ahead-of-time compilation and other optimizations for those paths.
Android normally uses a combination of compilation and runtime optimization techniques. Some code may initially run through the interpreter or just-in-time compilation, while frequently executed code can be optimized as the application runs.
This adaptive approach works well over time, but it can leave users waiting during early interactions because the runtime has not yet observed enough execution to optimize every important path.
A Baseline Profile helps the runtime identify code that should receive optimization earlier.
For example, a shopping application might include the following important code paths:
- Application startup and initial screen rendering.
- Opening a product category.
- Loading a product detail screen.
- Navigating to the shopping cart.
- Opening the checkout flow.
If these paths are included in an appropriate Baseline Profile, Android can optimize relevant code before users repeatedly execute those operations.
How Baseline Profiles Work
A typical Baseline Profile workflow involves three main stages.
- A developer identifies important user journeys and executes them in a controlled environment.
- A profile-generation process records the relevant code paths and produces a profile file.
- The application packages the profile with its release artifact so Android can use it for compilation and optimization.
The generated profile does not contain precompiled machine code for the entire application. Instead, it provides information about which code paths deserve optimization.
The Android runtime still manages compilation and execution according to the device's capabilities and system behavior.
Baseline Profiles vs Runtime Profiling
Baseline Profiles and runtime profiling are related, but they serve different purposes.
| Aspect | Baseline Profiles | Runtime Profiling |
|---|
| Primary purpose | Identify important code paths for early optimization | Observe application behavior during execution |
| When information becomes available | Packaged with the application when configured correctly | Collected as the application runs |
| Main benefit | Improve performance during startup and early interactions | Help the runtime optimize frequently executed code over time |
| Developer involvement | Requires profile generation and maintenance | Runtime behavior is largely automatic |
| Typical use | Startup, navigation, and common user journeys | Frequently executed code discovered through normal use |
Baseline Profiles complement the runtime's existing optimization mechanisms rather than replacing them.
Why Baseline Profiles Matter for App Performance
Users often judge an application by how quickly it becomes usable after launch. A long startup delay can make an application feel unresponsive even when its screens eventually render correctly.
Baseline Profiles can help reduce this delay by enabling earlier optimization of code that runs during startup and other important interactions.
1. Faster Application Startup
Application startup can involve several operations, including initializing dependencies, creating the application process, loading classes, inflating layouts, and rendering the first screen.
Some of these operations execute code paths that may benefit from earlier compilation.
A Baseline Profile can identify relevant methods in these paths, potentially reducing the execution overhead associated with interpretation and just-in-time compilation.
However, it cannot eliminate delays caused by unrelated bottlenecks such as slow disk operations, synchronous network requests, excessive dependency initialization, or expensive database queries.
2. Smoother First-Time Navigation
An application may launch quickly but still experience delays when users open a screen for the first time.
For example, a screen might initialize a complex list, load image-processing code, or execute several transformation functions when the user opens it.
If these code paths are included in the Baseline Profile, Android can optimize relevant methods before the interaction occurs.
The result may be a smoother first experience, provided the delay is caused by code execution that benefits from compilation.
3. Better Performance Before Runtime Learning
Without a suitable Baseline Profile, the runtime may need to observe execution before it can optimize frequently used methods through its normal adaptive mechanisms.
This means the first few interactions can behave differently from later interactions.
Baseline Profiles provide advance information about important code paths, reducing dependence on runtime learning for those paths.
This is especially useful for applications where startup and the first few interactions strongly influence the user experience.
Setting Up Baseline Profiles in an Android Project
The exact Gradle configuration depends on the Android Gradle Plugin version, the project's module structure, and the profile-generation approach.
A common setup uses the Baseline Profile Gradle plugin and a dedicated benchmark module.
Before changing the build configuration, verify that your project uses compatible versions of the Android Gradle Plugin, Kotlin, Gradle, and the AndroidX Benchmark libraries.
Step 1: Configure the Gradle Plugin
In a typical Android project, the Baseline Profile plugin is declared in the root project's plugin configuration.
For example:
plugins {
id("com.android.application") version "YOUR_AGP_VERSION" apply false
id("com.android.test") version "YOUR_AGP_VERSION" apply false
id("androidx.baselineprofile") version "YOUR_PROFILE_PLUGIN_VERSION" apply false
}
Replace the placeholders with versions compatible with your project.
The Android application plugin builds the app, the Android test plugin supports instrumented tests in a separate module, and the Baseline Profile plugin provides the build integration for profile generation.
Do not copy version numbers from an unrelated project without checking compatibility. An incompatible plugin combination can cause Gradle synchronization or task execution failures.
Step 2: Configure the Application Module
Apply the Baseline Profile plugin to the application module and configure the module to receive generated profiles.
A simplified Kotlin DSL example looks like this:
plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("androidx.baselineprofile")
}
android {
namespace = "com.example.shop"
compileSdk = 35
defaultConfig {
applicationId = "com.example.shop"
minSdk = 23
targetSdk = 35
versionCode = 1
versionName = "1.0"
}
buildTypes {
release {
isMinifyEnabled = true
}
}
}
baselineProfile {
automaticGenerationDuringBuild = true
}
This is an illustrative configuration, not a complete project that can be pasted unchanged into every application.
The SDK levels are examples, and the build settings should match your project's requirements. The profile-generation option should also be configured according to the plugin version and the workflow used by your team.
For an existing project, preserve its current signing configuration, dependency declarations, build types, and other required settings.
The important point is that the application module must be configured to consume the generated Baseline Profile correctly.
Step 3: Create a Benchmark Module
A dedicated benchmark module can execute representative application journeys on an Android device or emulator.
A simplified module configuration might look like this:
plugins {
id("com.android.test")
id("org.jetbrains.kotlin.android")
id("androidx.baselineprofile")
}
android {
namespace = "com.example.shop.benchmark"
compileSdk = 35
defaultConfig {
minSdk = 28
targetSdk = 35
testInstrumentationRunner =
"androidx.test.runner.AndroidJUnitRunner"
}
targetProjectPath = ":app"
}
dependencies {
implementation("androidx.test.ext:junit:YOUR_TEST_VERSION")
implementation("androidx.test.uiautomator:uiautomator:YOUR_UIAUTOMATOR_VERSION")
implementation("androidx.benchmark:benchmark-macro-junit:YOUR_BENCHMARK_VERSION")
}
The dependency versions must be selected to match the AndroidX libraries and build tools used by your project.
The benchmark module targets the application module and provides the environment for running instrumentation-based profile-generation tests.
The actual module may require additional configuration, depending on whether the project uses product flavors, multiple application variants, or a customized test setup.
Creating a Baseline Profile for Important User Journeys
A Baseline Profile is only as useful as the code paths it covers.
Generating a profile from a test that opens the application and immediately exits may miss the screens and interactions that matter most to users.
Instead, profile representative journeys.
Example: Profile Application Startup
The following example uses the AndroidX Macrobenchmark APIs to define a startup journey.
@RunWith(AndroidJUnit4::class)
class StartupBaselineProfile {
@get:Rule
val baselineProfileRule = BaselineProfileRule()
@Test
fun generateStartupProfile() {
baselineProfileRule.collect(
packageName = "com.example.shop"
) {
pressHome()
startActivityAndWait()
device.waitForIdle()
}
}
}
This example illustrates the structure of a profile-generation test. The exact imports, helper APIs, and configuration may vary with the AndroidX Benchmark version in use.
The test starts from the home screen, launches the target application, and waits for the interface to become idle.
For reliable results, ensure the target application is installed, the device is configured correctly, and the profile-generation environment meets the requirements of the benchmark library.
The test should also avoid unnecessary interactions that do not represent the user journey being profiled.
Example: Include a Common Navigation Flow
Suppose a shopping application receives a large proportion of its traffic on product detail screens.
You can extend the profile-generation journey to navigate from the home screen to a product.
@Test
fun generateProductNavigationProfile() {
baselineProfileRule.collect(
packageName = "com.example.shop"
) {
pressHome()
startActivityAndWait()
device.findObject(
By.res("com.example.shop:id/product_item")
).click()
device.waitForIdle()
}
}
This is illustrative UI Automator code. The resource identifier must exist in the actual application, and the test must account for the real navigation structure.
For production profile generation, ensure that the test reliably reaches the intended screen. Avoid relying on arbitrary sleep intervals when a UI condition can be used instead.
You should include the important user journeys that justify the profile, but do not attempt to exercise every possible screen combination. Focus on frequently used paths and interactions where early optimization is valuable.
Generating and Packaging the Profile
After configuring the application and benchmark modules, run the appropriate Baseline Profile generation task for your project.
The exact Gradle task name depends on the plugin configuration and application variants. Check the available tasks in Gradle or your IDE rather than assuming that a task name is identical across every project.
A typical workflow is:
- Install the application and prepare the benchmark device.
- Execute the Baseline Profile generation test.
- Inspect the generated profile output.
- Build the intended release artifact.
- Confirm that the profile is packaged or otherwise delivered through the configured release workflow.
Generated profiles are often stored under the application's source tree so they can be reviewed and version-controlled. The precise output location depends on the plugin and module configuration.
Do not assume that running the generation test automatically means the profile is included in every release artifact. Verify the generated file, build configuration, and resulting APK or App Bundle.
For an application distributed through Google Play, ensure the chosen delivery process is compatible with the application's publishing configuration.
Measuring the Impact of Baseline Profiles
A Baseline Profile should be evaluated through measurement, not assumption.
Before introducing it, establish a performance baseline for important user journeys. After generating and packaging the profile, repeat the measurements under comparable conditions.
Measure Startup Performance
Macrobenchmark can measure startup behavior and other user interactions on a device.
For example, an application might track:
- Time to initial display.
- Time to fully drawn.
- Startup duration across repeated launches.
- Navigation latency for common screens.
- Frame timing during scrolling or transitions.
The specific metric that matters depends on the application. A fast initial display does not necessarily mean that the application is fully interactive, so distinguish between the first rendered frame and the point at which the user can meaningfully interact with the screen.
Compare Results Consistently
Use the same device model, application version, test data, and benchmark conditions whenever possible.
Measure both startup and relevant first-use interactions. Repeat the tests enough times to understand normal variation.
Avoid comparing a profile-enabled build on a high-end device with a profile-disabled build on a slower device. Such a comparison cannot isolate the effect of the profile.
Also distinguish between cold startup, warm startup, and hot startup. These launch conditions involve different amounts of process initialization and can produce different performance results.
Understand the Limitations
Baseline Profiles optimize code paths; they do not automatically optimize every operation that contributes to latency.
If startup is slow because the main thread waits for a network response, the profile may have little impact on that bottleneck.
If rendering is delayed because a screen inflates a very large layout, the underlying layout and rendering work may still need attention.
Use profiling tools and system traces to identify the actual cause of a performance issue before deciding whether a Baseline Profile is likely to help.
Common Mistakes to Avoid
Profiling Only Application Startup
Startup is important, but it is not the only performance-sensitive part of an application.
If users spend most of their time navigating a catalog, editing documents, or opening a complex dashboard, a startup-only profile may leave important code paths uncovered.
Include representative interactions based on actual usage patterns.
Using Unreliable UI Tests
A profile-generation test that sometimes fails to reach its target screen can produce incomplete or inconsistent results.
Use stable resource identifiers, wait for meaningful UI conditions, and ensure that test data and application state are predictable.
Assuming Every Method Should Be Included
A larger profile is not automatically better. Profile quality depends on identifying relevant code paths, not maximizing the number of recorded methods.
Focus on startup, frequently used flows, and operations where early compilation can improve the user experience.
Ignoring Release Configuration
A profile generated for one application variant may not automatically apply to another variant.
Verify that the correct profile is included in the artifact you intend to distribute. Pay particular attention to product flavors, release variants, and build configurations that change the code included in the application.
Skipping Performance Validation
A successful build does not demonstrate that startup performance has improved.
Measure the relevant metrics before and after the change. If the results do not improve, investigate whether the profile covers the expected code paths and whether the main bottleneck lies elsewhere.
Best Practices for Production Applications
Follow these practices to maintain useful Baseline Profiles over time.
- Prioritize common user journeys using application analytics and product requirements.
- Keep profile-generation tests deterministic and independent of unstable external services.
- Run performance benchmarks on representative devices, including lower-powered devices where appropriate.
- Regenerate profiles when significant application flows or code paths change.
- Validate profiles for the release variants that you actually distribute.
- Track startup and interaction performance as part of your release process.
- Investigate main-thread blocking, database access, network dependencies, and rendering costs separately from compilation behavior.
- Keep benchmark dependencies and Gradle plugins compatible with the project's Android toolchain.
Baseline Profiles are most effective when they are part of a broader performance strategy rather than a one-time build optimization.
Advantages and Disadvantages of Baseline Profiles
| Advantages | Disadvantages |
|---|
| Can improve startup and early interaction performance | Requires a profile-generation workflow |
| Identifies important code paths before normal runtime learning | Benefits depend on the quality of the selected journeys |
| Works alongside Android's existing runtime optimization mechanisms | Does not fix every startup or rendering bottleneck |
| Can be integrated into an automated build process | Requires maintenance as application behavior changes |
| Helps improve performance consistency for important flows | Requires careful measurement to demonstrate its impact |
For many Android applications, the benefits justify the additional setup, especially when startup and first-use responsiveness are important product requirements.
Conclusion
Android Baseline Profiles help the runtime optimize important application code paths earlier, reducing dependence on normal runtime learning during the first user interactions.
They are particularly useful for application startup, common navigation flows, and other performance-sensitive journeys. However, their effectiveness depends on selecting meaningful code paths, generating profiles reliably, packaging them correctly, and measuring the resulting behavior.
Start by identifying the interactions that matter most to your users. Create profile-generation tests for those journeys, verify the release artifact, and compare performance under consistent conditions.
If the measurements reveal improvements, integrate profile generation and validation into your release process. If they do not, investigate the underlying bottlenecks instead of assuming that compilation optimization alone will solve the problem.
Join the conversation! Your thoughts help the community grow.