Baseline Profiles are a list of classes and methods included in an APK used by Android Runtime (ART) during installation to pre-compile critical paths to machine code. This is a form of profile guided optimization (PGO) that lets apps optimize startup, reduce jank, and improve performance for end users.
How Baseline Profiles work
Profile rules are compiled into a binary form in the APK, in
assets/dexopt/baseline.prof
.
During installation, ART performs Ahead-of-time (AOT) compilation of methods in the profile, resulting in those methods executing faster. If the profile contains methods used in app launch or during frame rendering, the user experiences faster launch times and/or reduced jank.
While developing your app or library, consider defining Baseline Profiles to cover specific hot paths during critical user journeys where rendering time or latency are important, such as startup, transitions, or scrolling.
Baseline Profiles are then shipped directly to users (through Google Play) along with the APK.

Reasons for using Baseline Profiles
Startup time is a critical component to improve user engagement with your application. Increasing the speed and responsiveness of an app leads to more daily active users and a higher average return visit rate.
Cloud Profiles also optimize these same interactions, but are only available to users a day or more after an update is released and don't support Android 7 (API 24) up to Android 8 (API 26).
Compilation behavior across Android versions
Android Platform versions have used different app compilation approaches, each with a corresponding performance tradeoff. Baseline profiles improve upon the previous compilation methods by providing a profile for all installs.
Android version | Compilation method | Optimization approach |
---|---|---|
Android 5 (API level 21) up to Android 6 (API level 23) | Full AOT | The entire app is optimized during install, resulting in long wait times to use the app, increased RAM and disk space usage, and longer times to load code from disk, potentially increasing cold startup times. |
Android 7 (API level 24) up to Android 8.1 (API level 27) | Partial AOT (Baseline Profile) | Baseline Profiles are
installed by
androidx.profileinstaller
on the first run, when the
app module defines this
dependency. ART may improve
this further by adding
additional profile rules
during the app's use and
compiling them when the
device is idle. This
optimizes for disk space
and time to load code from
the disk, thereby reducing
wait time for the app. |
Android 9 (API level 28) and higher | Partial AOT (Baseline + Cloud Profile) | Play uses Baseline Profiles during app installs to optimize the APK and Cloud profiles (if available). After installation, ART profiles are uploaded to Play and aggregated, then provided as Cloud Profiles to other users when they install/update the app. |
Create Baseline Profiles
Create profile rules automatically using BaselineProfileRule
As an app developer, you can automatically generate profiles for every app release by using the Jetpack Macrobenchmark library.
To create Baseline Profiles using the Macrobenchmark library:
Add a dependency to the ProfileInstaller library in your app's
build.gradle
to enable local and Play Store Baseline Profile compilation. This is the only way to sideload a Baseline Profile locally.dependencies { implementation("androidx.profileinstaller:profileinstaller:1.2.0-beta01") }
Set up a Macrobenchmark module in your gradle project.
Define a new test called
BaselineProfileGenerator
that looks something like:@ExperimentalBaselineProfilesApi @RunWith(AndroidJUnit4::class) class BaselineProfileGenerator { @get:Rule val baselineProfileRule = BaselineProfileRule() @Test fun startup() = baselineProfileRule.collectBaselineProfile(packageName = "com.example.app") { pressHome() // This block defines the app's critical user journey. Here we are interested in // optimizing for app startup. But you can also navigate and scroll // through your most important UI. startActivityAndWait() } }
Connect a
userdebug
or rooted Android Open Source Project (AOSP) emulator running Android 9 or higher.Run
adb root
command from terminal to ensure the adb daemon is running with root permissions.Run the test and wait for its completion.
Find the generated profile location in logcat. Search for the log tag
Benchmark
.com.example.app D/Benchmark: Usable output directory: /storage/emulated/0/Android/media/com.example.app
# List the output baseline profile ls /storage/emulated/0/Android/media/com.example.app SampleStartupBenchmark_startup-baseline-prof.txt
Pull the generated file from your device.
adb pull storage/emulated/0/Android/media/com.example.app/SampleStartupBenchmark_startup-baseline-prof.txt .
Rename the generated file to
baseline-prof.txt
and copy it to thesrc/main
directory of your app module.
Define profile rules manually
You can define profile rules manually in an app or a library module by creating
a file called baseline-prof.txt
located in the src/main
directory. This is
the same folder that contains the AndroidManifest.xml
file.
The file specifies one rule per line. Each rule represents a pattern for matching methods or classes in the app or library that needs to be optimized.
The syntax for these rules is a superset of the human-readable ART profile
format (HRF) when using adb shell profman --dump-classes-and-methods
. The
syntax is very similar to the
syntax for descriptors and signatures,
but also allows wildcards to simplify the rule-writing process.
The following examples shows a few Baseline Profile rules included in the Jetpack Compose library:
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;
Rule syntax
These rules take one of two forms to target either methods or classes:
[FLAGS][CLASS_DESCRIPTOR]->[METHOD_SIGNATURE]
A class rule uses the following pattern:
[CLASS_DESCRIPTOR]
Syntax | Description |
---|---|
FLAGS |
Represents one or more of the characters H , S , and P to indicate whether this method should be flagged as Hot , Startup , or Post Startup in regards to the startup type. A method with the H flag indicates that it is a "hot" method, meaning it is called many times during the lifetime of the app. A method with the S flag indicates that it is a method called at startup. A method with the P flag indicates that it is a hot method not related to startup. A class present in this file indicates that it is used during startup and should be pre-allocated in the heap to avoid the cost of class loading. ART compiler employs various optimization strategies, such as AOT compilation of these methods and performing layout optimizations in the generated AOT file. |
CLASS_DESCRIPTOR |
Descriptor for the targeted method's class. For instance, androidx.compose.runtime.SlotTable would have a descriptor of Landroidx/compose/runtime/SlotTable; . Note: L is prepended here per the Dalvik Executable (DEX) format. |
METHOD_SIGNATURE |
Signature of the method, including the name, parameter types, and return types of the method. For example, the method // LayoutNode.kt fun isPlaced():Boolean { // ... } on LayoutNode has the signature isPlaced()Z . |
These patterns can have wildcards in order to have a single rule encompass multiple methods or classes. For guided assistance when writing with rule syntax in Android Studio, take a look at the Android Baseline Profiles plugin.
An example of a wildcard rule might look something like this:
HSPLandroidx/compose/ui/layout/**->**(**)**
Supported types in baseline profile rules
Baseline Profile rules support the following types. For details on these types, see the Dalvik Executable (DEX) format.
Character | Type | Description |
---|---|---|
B |
byte | Signed byte |
C |
char | Unicode character code point encoded in UTF-16 |
D |
double | Double-precision floating point value |
F |
float | Single-precision floating point value |
I |
int | Integer |
J |
long | Long integer |
S |
short | Signed short |
V |
void | Void |
Z |
boolean | True or false |
L (class name) |
reference | An instance of a class name |
Additionally, libraries can define rules that will be packaged in AAR artifacts. When you build an APK to include these artifacts, the rules are merged together (similar to how manifest merging is done) and compiled to a compact binary ART profile that is specific to the APK.
ART leverages this profile when the APK is used on devices to AOT compile a specific subset of the application at install-time on Android 9 (API level 28), or Android 7 (API level 24) when using ProfileInstaller.
Additional notes
When creating Baseline Profiles, there are some additional things to note:
Android 5 up to Android 6 (API levels 21 and 23) already AOT compile the APK at install time.
Debuggable applications are never AOT compiled to help with troubleshooting.
Rule files must be named
baseline-prof.txt
and placed in the root directory of your main source set (it should be a sibling file to yourAndroidManifest.xml
file).These files will only be utilized if you are using Android Gradle Plugin
7.1.0-alpha05
or above (Android Studio Bumblebee Canary 5).Bazel does not currently support reading and merging Baseline Profiles into an APK.
Baseline Profiles cannot be larger than 1.5 MB compressed. Therefore, libraries and applications should strive to define a small set of profile rules which maximize impact.
Broad rules that compile too much of the application can slow down startup due to increased disk access. You should test the performance of your Baseline Profiles.
Measure improvements
Automate measurement with the Macrobenchmark library
Macrobenchmarks let you control pre-measurement compilation via the
CompilationMode
API, including BaselineProfile
usage.
If you've already set up a BaselineProfileRule
test in a Macrobenchmark
module, you can define a new test in that module to evaluate its performance:
@RunWith(AndroidJUnit4::class)
class BaselineProfileBenchmark {
@get:Rule
val benchmarkRule = MacrobenchmarkRule()
@Test
fun startupNoCompilation() {
startup(CompilationMode.None())
}
@Test
fun startupBaselineProfile() {
startup(CompilationMode.Partial(
baselineProfileMode = BaselineProfileMode.Require
))
}
private fun startup(compilationMode: CompilationMode) {
benchmarkRule.measureRepeated(
packageName = "com.example.app",
metrics = listOf(StartupTimingMetric()),
iterations = 10,
startupMode = StartupMode.COLD,
compilationMode = compilationMode
) { // this = MacrobenchmarkScope
pressHome()
startActivityAndWait()
}
}
}
An example of passing test results are seen here:
Note, while the example above looks at StartupTimingMetric
, there are other
important metrics worth considering, like
Jank (Frame metrics),
which can be measured using Jetpack Macrobenchmark.
Manually measure app improvements
First, let's measure the unoptimized app startup for reference.
PACKAGE_NAME=com.example.app
# Force Stop App adb shell am force-stop $PACKAGE_NAME # Reset compiled state adb shell cmd package compile --reset $PACKAGE_NAME
# Measure App startup # This corresponds to `Time to initial display` metric # For additional info https://developer.android.com/topic/performance/vitals/launch-time#time-initial adb shell am start-activity -W -n $PACKAGE_NAME/.ExampleActivity \ | grep "TotalTime"
Next, let's sideload the Baseline Profile.
# Unzip the Release APK first unzip release.apk
# Create a ZIP archive # Note: The name should match the name of the APK # Note: Copy baseline.prof{m} and rename it to primary.prof{m} cp assets/dexopt/baseline.prof primary.prof cp assets/dexopt/baseline.profm primary.profm
# Create an archive zip -r release.dm primary.prof primary.profm
# Confirm that release.dm only contains the two profile files: unzip -l release.dm # Archive: release.dm # Length Date Time Name # --------- ---------- ----- ---- # 3885 1980-12-31 17:01 primary.prof # 1024 1980-12-31 17:01 primary.profm # --------- ------- # 2 files
# Install APK + Profile together adb install-multiple release.apk release.dm
To verify that the package was optimized on install, run the following command:
# Check dexopt state
adb shell dumpsys package dexopt | grep -A 1 $PACKAGE_NAME
The output should state that the package was compiled.
[com.example.app]
path: /data/app/~~YvNxUxuP2e5xA6EGtM5i9A==/com.example.app-zQ0tkJN8tDrEZXTlrDUSBg==/base.apk
arm64: [status=speed-profile] [reason=install-dm]
Now, we can measure app startup performance like we did before, but without resetting compiled state.
# Force Stop App adb shell am force-stop $PACKAGE_NAME
# Measure App startup adb shell am start-activity -W -n $PACKAGE_NAME/.ExampleActivity \ | grep "TotalTime"
Known Issues
Currently, using Baseline Profiles has several known issues:
Baseline profiles are not packaged correctly when building the APK from an app bundle. To resolve this issue, apply
com.android.tools.build:gradle:7.3.0-beta01
and higher (issue).Baseline profiles are only correctly packaged for the primary
classes.dex
file. This affects apps with more than one.dex
file. To resolve this issue, applycom.android.tools.build:gradle:7.3.0-beta01
and higher (issue).Macrobenchmark is incompatible with Baseline Profiles on Android 12L (API 32) (issue) and Android 13 (API 33) (issue).
Resetting ART profile caches is not allowed on
user
(non-rooted) builds. To work around this,androidx.benchmark:benchmark-macro-junit4:1.1.0-rc02
includes a fix that reinstalls the app during the benchmark (issue).Android Studio Profilers don't install Baseline Profiles when profiling the app (issue).
Non Gradle build systems (Bazel, Buck, etc.) don't support compiling Baseline Profiles into output APKs.