This documentation describes steps to enable DEX layout optimizations with startup profiles when building an Android application.
Requirements
- Android Gradle plugin 8.1.0-alpha10 or newer, which pulls in a newer version of R8.
- Jetpack Macrobenchmarks version 1.2.0-alpha14 or newer.
Startup profiles
Startup profiles are similar to Baseline Profiles. They describe classes and methods critical to app startup that must be ready to load first. Startup profiles also use the same human readable format (HRF) that Baseline Profiles use.
A key difference between startup profiles and Baseline Profiles is that Baseline Profiles include classes and methods that are important for optimization beyond app startup—for example, reducing jank during animations or key Critical User Journeys (CUJs) beyond app startup.
Another key difference between startup profiles and Baseline Profiles is that
startup profiles can't be provided by libraries and aren't subject to merging
by the Android Gradle plugin. This is because libraries don't necessarily have a
good understanding of what the critical classes and methods are for app startup.
These classes and methods are best derived from Jetpack Macrobenchmarks, using
the
BaselineProfileRule
with CUJs that specifically target app startup—such as
collectStableBaselineProfile
.
See the following table for a quick summary of the key differences between Baseline and startup profiles.
Profile type | Per build | Subject to merging |
---|---|---|
Baseline Profiles | N | Y |
Startup Profiles | Y | N |
Startup profiles are stored in
src/<variantName>/main/baselineProfiles/startup-prof.txt
.
DEX layout optimizations
This optimization helps reduce the number of major page faults during app startup by improving the locality of code used during startup, leading to faster startup times.
This is done by adding all code executed during startup into the primary
classes.dex
file while removing all non-startup code to outside of the primary
classes.dex
file.

Use DEX layout optimizations
This section shows how to use DEX layout optimizations.
Build startup profiles
First, you need to build your startup profiles.
Project setup
In the preceding example, the instrumentation test drives an app
module with
the package name com.example.app
.
When you use Jetpack Macrobenchmarks to generate startup profile rules, you define a new benchmark variant as part of the build. The benchmark variant is identical to the release variant, except that you turn off minification.
The following is an excerpt from the app module's build.gradle
file.
Kotlin
buildTypes { ... create("benchmark") { initWith(buildTypes.getByName("release") isMinifyEnabled = false signingConfig = signingConfigs.getByName("debug") matchingFallbacks += "release" } }
Groovy
buildTypes { ... benchmark { initWith buildTypes.release minifyEnabled false signingConfig signingConfigs.debug matchingFallbacks = ['release'] } }
Generate startup rules
Kotlin
@RunWith(AndroidJUnit4::class) class StartupProfileGenerator { @get:Rule val baselineProfileRule = BaselineProfileRule() @Test fun startup() = baselineProfileRule.collectStableBaselineProfile( packageName = "com.example.app", includeInStartupProfile = true ) { // This scenario just starts the activity and waits for it to draw // the first frame. If you have animations or async content in your // startup, wait for them with UiAutomator. startActivityAndWait() } }
Java
@RunWith(AndroidJUnit4.class) public class StartupProfileGenerator { @Rule BaselineProfileRule baselineProfileRule = new BaselineProfileRule(); @Test public void startup() { baselineProfileRule.collectStableBaselineProfile( "com.example.app", /* includeInStartupProfile = */ true, (scope -> { // This scenario just starts the activity and waits for it to // draw the first frame. If you have animations or async content // in your startup, wait for them with UiAutomator. scope.startActivityAndWait(); return null; } )); } }
Output
Running the test results in a file that looks something like this:
HSPLandroidx/compose/runtime/ComposerImpl;->updateValue(Ljava/lang/Object;)V HSPLandroidx/compose/runtime/ComposerImpl;->updatedNodeCount(I)I HLandroidx/compose/runtime/ComposerImpl;->validateNodeExpected()V PLandroidx/compose/runtime/CompositionImpl;->applyChanges()V HLandroidx/compose/runtime/ComposerKt;->findLocation(Ljava/util/List;I)I Landroidx/compose/runtime/ComposerImpl;
- Copy the contents of the output file to
src/main/baselineProfiles/startup-prof.txt
.
Enable DEX layout optimizations
To enable DEX layout optimizations, use the following Gradle snippet:
Kotlin
android { // ... experimentalProperties["android.experimental.art-profile-r8-rewriting"] = true experimentalProperties["android.experimental.r8.dex-startup-optimization"] = true }
Groovy
android { // ... experimentalProperties["android.experimental.art-profile-r8-rewriting"] = true experimentalProperties["android.experimental.r8.dex-startup-optimization"] = true }
android.experimental.art-profile-r8-rewriting
The experimental property android.experimental.art-profile-r8-rewriting
enables rewriting of baseline rules by D8 and R8.
This step is necessary because D8 and R8 perform optimizations like class merging, synthetics, and re-writing lambdas and method signature optimizations.
These optimizations can change the method signatures of an existing class and create new classes derived from the existing source symbols. When these transformations are performed, D8 and R8 perform the same set of transformations on the human readable baseline and startup profile rules.
This step is important to fully capture all the rules you need to optimize app performance. Enabling R8 rewriting of rules improves profile quality by as much as 25%.
android.experimental.r8.dex-startup-optimization
The experimental property android.experimental.r8.dex-startup-optimization
enables DEX layout optimization.
- To build the APK with DEX layout optimizations, you can use the following:
./gradlew :app:assembleRelease
Additional resources
- For a list of available R8 version SHAs, see the R8 Git repository.