Mobile Development 17 min read

Optimizing Dynamically Loaded Dex Files Across Android Versions

The article explains how dynamically loaded dex files are optimized on Android 5.0 through 12—detailing AOT compilation via dex2oat, profile‑guided filters, runtime‑filter flags, class‑loader contexts, SELinux limits, and the use of PathClassLoader or the PMS performDexOptSecondary command to achieve performance comparable to installed APKs.

NetEase Cloud Music Tech Team
NetEase Cloud Music Tech Team
NetEase Cloud Music Tech Team
Optimizing Dynamically Loaded Dex Files Across Android Versions

In hot‑fix and plugin scenarios, dynamically loaded dex files need to be optimized so that their execution speed matches that of an installed APK. Improper optimization can cause the app to start up to 50% slower after a hot‑fix.

Android 5.0

Since Android 5.0 the system uses an ahead‑of‑time (AOT) compilation mechanism (dex2oat) that converts dex files to executable OAT files at install time. The call chain that triggers dex2oat when DexFile.loadDex is used is:

DexFile.loadDex → new DexFile → DexFile.openDexFile → DexFile.openDexFileNative → DexFile_openDexFileNative → ClassLinker::OpenDexFilesFromOat → ClassLinker::CreateOatFileForDexLocation → ClassLinker::GenerateOatFile

Inside ClassLinker::GenerateOatFile the dex2oat command is invoked. The relevant source fragment (art/runtime/class_linker.cc) shows how arguments are built and the command is executed.

Android 7.0

Android 7.0 introduces profile‑guided compilation that combines AOT and JIT. The system no longer compiles all code at install time; instead it records execution profiles and performs AOT compilation for hot methods when the device is idle and charging.

The compilation filter used by dex2oat changes from speed (full AOT) to speed‑profile (profile‑guided). The four possible filters are defined in compiler_filter.h :

verify : only verification

quicken : verification + minor optimizations (removed in Android 12)

speed : full AOT

speed‑profile : profile‑guided AOT

When DexFile.loadDex is called on Android 7.0 the same call chain ends up in OatFileAssistant::GenerateOatFile , which uses the speed filter passed from OatFileAssistant::MakeUpToDate . Therefore the method still triggers dex2oat with the appropriate filter.

Android 8.0

In Android 8.0 the filter is obtained via GetRuntimeCompilerFilterOption . This function reads the runtime flag --compiler-filter (or falls back to the default quicken ) and passes it to dex2oat. The flag is influenced by system properties vold.decrypt and dalvik.vm.dex2oat‑filter .

If vold.decrypt equals trigger_restart_min_framework or 1 , the filter becomes assume‑verified ; otherwise the value of dalvik.vm.dex2oat‑filter is used. On a typical device where neither property is set, the effective filter is quicken , which does not achieve full AOT for all methods.

Android 10 and later

From Android 10 the system introduces class‑loader contexts. To have dynamically loaded dex files optimized like installed ones, developers must create a PathClassLoader , DexClassLoader or DelegateLastClassLoader . However, creating a DexFile no longer triggers dex2oat directly.

SELinux policies also restrict dex2oat execution: only processes with the dex2oat_exec domain (typically apps with targetSdkVersion ≤ 28 ) can run the tool. Apps targeting API 29+ lack this permission, so they cannot invoke dex2oat via ProcessBuilder or Runtime.exec .

To still perform secondary dex optimization, the Package Manager Service (PMS) provides the performDexOptSecondary Binder shell command. By constructing the appropriate Binder transaction (see the Kotlin snippet) and invoking the command, an app can request the system to run dex2oat on its secondary dex files with a chosen filter (e.g., speed‑profile or verify ).

Summary

The article outlines the evolution of dex optimization mechanisms from Android 5.0 to Android 12 and provides practical ways to achieve comparable performance for dynamically loaded dex files:

Android 5.0–7.0: invoke DexFile.loadDex which triggers dex2oat with the appropriate filter.

Android 8.0–9.x: create a PathClassLoader (or similar) and rely on the system’s default quicken filter, or explicitly set dalvik.vm.dex2oat‑filter .

Android 10+: use PathClassLoader together with the PMS performDexOptSecondary Binder shell command to request background dex optimization, respecting SELinux constraints and target‑SDK version.

AndroidClassLoaderdynamic loadingAOTSELinuxDex Optimization
NetEase Cloud Music Tech Team
Written by

NetEase Cloud Music Tech Team

Official account of NetEase Cloud Music Tech Team

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.