Android Baseline Profiles: What ART Actually Does With Them
Shipping Android baseline profiles is not the same thing as having them. I have watched a release go out with baseline.prof sitting at /assets/dexopt/baseline.prof inside the APK, only for dumpsys package dexopt to report status = verify on a fresh install — no compiled profile, and the first launch still running interpreted.
Android baseline profiles are a list of classes and methods that ART should compile ahead of time instead of leaving to the interpreter and the JIT. They are profile-guided optimization applied at install time rather than accumulated on the device.
Here is what this post gives you:
- The exact asset path ART reads and the compiler filter it triggers.
- Why a profile already inside the APK can sit there unused.
- Two
adbcommands that tell you whether yours actually compiled. - A cheat-sheet table of the
dumpsysstatus and reason values.
What are Android baseline profiles?
Android baseline profiles start life as a human-readable text file, baseline-prof.txt, listing class and method rules in the same syntax ART's own profiler uses. At build time D8 and R8 turn those rules into a binary profile, baseline.prof, and rewrite the names so the rules still match your obfuscated, optimized release DEX. The binary lands in the APK at /assets/dexopt/baseline.prof, with a metadata sibling at /assets/dexopt/baseline.profm.
The point of Android baseline profiles is coverage, not magic. Without one, every method starts life interpreted; ART JIT-compiles hot methods in memory, and only later, when the device is idle, does it write a .odex file for the code it actually saw run. That makes your first launches after an install or update the slowest launches you will ever ship. Google's own numbers put the gain from a baseline profile at roughly 30% on first-launch code execution speed, because the included paths skip interpretation and JIT steps entirely.
A baseline profile is a launch-time answer to a problem ART normally solves after the fact. That is the whole mechanism in one line.
What does ART do with the profile?
ART does not read baseline.prof directly out of your APK at runtime. The profile is inert data until the platform runs a DEX optimization pass — dex2oat — against it, and only when that pass is invoked with a profile-aware compiler filter.
The filter that matters is speed-profile. When dex2oat runs with speed-profile, it compiles ahead of time exactly the methods the profile names, plus verification of everything else, instead of compiling the whole app (speed) or nothing beyond verification (verify). ART never touches baseline.prof at runtime; the profile only does anything if a dex2oat pass runs with the speed-profile filter. That is the difference between a profile that exists and a profile that is in effect.
How the profile gets in front of dex2oat depends on the install path:
- Android 5–6 (API 21–23): full AOT. The entire app is compiled at install, no profile needed — and none helps, because there is nothing left to skip.
- Android 7–8.1 (API 24–27): partial AOT. The Jetpack
ProfileInstallerlibrary copies the embedded profile into ART's AOT directory on first run, and ART compiles from it on the next idle pass. - Android 9+ (API 28+): partial AOT with two sources. Play merges your baseline profile with a cloud profile at install time, when one exists.
On a Play install on API 28+, compilation happens during installation. On a sideload, an MDM push, or an OEM app-store install, ProfileInstaller merely enqueues the profile for the next background bg-dexopt — and nothing runs until the device is idle, on charger, or otherwise decides to run that job.
Why is a profile in the APK still not enough?
This is where the "I shipped it, why is startup still slow?" tickets come from. There are three independent reasons Android baseline profiles inside an APK fail to buy you anything.
First, the profile can be present but never compiled. ProfileInstaller only hands the profile to ART once per install or upgrade, tracked by a skip file keyed on the package's lastUpdateTime. If ProfileInstallerInitializer is disabled, or the job never runs before your first benchmark, dumpsys reports status = verify even though the file is sitting in the APK. The profile is not active until the compile completes — and until then your app runs exactly as if you had shipped nothing.
Second, the delivered profile can be a non-matching one. Install through Google Play and the platform may compile against the merged Play profile rather than your exact embedded rules. ProfileVerifier has a result code for this: RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING. The docs are blunt about what it means — only the methods still shared between the cloud profile and your app get compiled, so fewer methods land in the .odex than your baseline profile requested.
Third, cloud profiles do not exist for half your install base. Cloud profiles are aggregated by Google Play from real usage and pushed at install time, but they only cover Android 9+ and only scale for apps with a large enough user base, and they take hours to days to distribute after a release. A sideloaded build, an enterprise app deployed through MDM, or a third-party store install never touches that pipeline at all. On those devices the embedded baseline profile is the entire optimization story — and it is still gated on a background compile pass actually firing.
I hit all three in one release. The benchmark on a fresh adb install showed no improvement, dumpsys package dexopt said verify, and the root cause was that the profile had been enqueued but the background job had not run. Forcing it fixed the numbers immediately:
adb shell cmd package compile -r bg-dexopt com.example.app
The -r recompiles from scratch; the bg-dexopt reason mirrors what the idle job would have done.
How do you verify the profile actually compiled?
Do not trust the presence of the file. Trust what the package manager recorded about the .odex:
adb shell dumpsys package dexopt | grep -A 2 com.example.app
You get a line like this on a working install:
arm64: [ status = speed-profile ] [ reason = bg-dexopt ] [ primary-abi ]
status = speed-profile is the only value that proves the profile is in effect; status = verify means no compiled profile exists regardless of what is in the APK. The reason tells you who did it:
| Value | Meaning |
|---|---|
speed-profile |
A compiled profile exists and is being used. |
verify |
No compiled profile exists. The APK may still contain one. |
install-dm |
Compiled at install time, by Play or manually. |
bg-dexopt |
Compiled while the device was idle. |
cmdline |
Compiled because you ran an adb compile command. |
In code, ProfileVerifier.getCompilationStatusAsync() gives you the same signal plus richer result codes. RESULT_CODE_COMPILED_WITH_PROFILE is the one you want; RESULT_CODE_NO_PROFILE and RESULT_CODE_ERROR_NO_PROFILE_EMBEDDED are the two that mean your build is wrong, not your device.
Where baseline profiles still fail
The mechanism has real edges, and they are not exotic.
- Obfuscation mismatch. You must generate the profile from an unminified build and let R8 rewrite the rules. Generate from an obfuscated build, or hand-edit
baseline-prof.txtwith wrong names, and the rules match nothing. - API floor. The profile install path is only meaningful on Android 7+ (API 24+), and
ProfileVerifieritself supports API 28 and higher. On a 6.x device you are back in full-AOT land and the profile is irrelevant. - OEM interference. OnePlus devices with "Disable permission monitoring" turned on in developer options are a documented cause of profile generation failing.
- Emulators. Performance benchmarks on an emulator are unreliable in general; for profile collection you can get away with it, but you should not treat emulator timings as proof of anything.
The profile is a hint, not a guarantee — ART is free to ignore it on any device where the compile pass does not run. Plan your instrumentation around speed-profile being absent, because on some of your fleet it will be.
The one question I have not seen a good answer to: for apps distributed outside Play, is there a reliable way to force profile compilation on first launch without burning a foreground cmd package compile on the user's time? If you have solved that, the comment box is the place.
For more on how ART's optimization levels interact with install paths, see the Android Development archive and the app tooling index.
Need an Android Developer or a full-stack website developer?
I specialize in Kotlin, Jetpack Compose, and Material Design 3. For websites, I use modern web technologies to create responsive and user-friendly experiences. Check out my portfolio or get in touch to discuss your project.



Comments (0)
All comments are reviewed before they are published.
Be the first to comment!