Optimize platform code and resources with R8

The Android platform build system (Soong) runs the R8 compiler on targets that compile DEX bytecode, including Android apps (android_app), tests (android_test), and installable Java libraries with compile_dex: true (such as services.jar), to shrink, optimize, and strip unused code and resources. On static libraries (android_library and static java_library), Soong doesn't run R8 directly, but uses the optimize property block to attach and propagate consumer keep rules (export_proguard_flags_files: true) to downstream DEX targets that statically link them.

For platform engineers building system images or vendor packages, eliminating overly broad keep rules offers direct system health benefits:

  • Smaller system partition footprint: R8 removes dead classes, methods, and resources before packaging APKs and JARs onto /system, /system_ext, /product, and /vendor.
  • Smaller compiled artifacts: Fewer DEX methods mean smaller .odex and .vdex files generated by dex2oat during build-time or on-device compilation.
  • Lower runtime memory pressure: Because Android maps .odex, .vdex, and APK resource tables into process memory using demand-paged mmap calls, smaller binaries reduce major page faults during app startup and lower resident code and resource memory across processes. For more information about how compiled app code affects device memory, see App code is memory.
  • More effective whole-program optimization: Narrow keep constraints let R8 inline methods, devirtualize interface and virtual calls, prune unused fields, and propagate constants across classes.

Configure R8 optimization in Soong

In Android.bp files, configure R8 optimization using the optimize property block on android_app and installable java_library targets (or on android_library and static java_library modules to export consumer keep rules):

android_app {
    name: "MySystemApp",
    srcs: ["src/**/*.java"],
    optimize: {
        obfuscate: true,
        shrink_resources: true,
    },
}

The following table summarizes the most common optimize properties in Soong (defined in build/soong/java/dex.go):

Property Description
enabled Controls whether R8 runs on the target. Defaults to true for all android_app targets.
shrink Controls tree shaking to remove unreachable classes, fields, and methods. Defaults to true for android_app targets (false for standalone java_library and test modules, which pass -dontshrink unless explicitly set).
optimize Controls bytecode optimizations such as method inlining, class merging, constant propagation, and dead branch removal. Defaults to true for android_app targets (controlled by the RELEASE_R8_OPTIMIZE_BY_DEFAULT release build flag).
obfuscate Controls identifier minification and renaming. Defaults to false for historical compatibility, but set it to true wherever possible to reduce DEX size and unlock deeper optimizations (see Enable obfuscation wherever possible).
shrink_resources Removes unused resources (res/ entries) from the packaged APK after code shrinking. Defaults to false.
optimized_shrink_resources Runs the integrated R8 code and resource shrinker pipeline so R8 traces code and resource references jointly in a single pass. When shrink_resources: true is set, defaults to RELEASE_USE_OPTIMIZED_RESOURCE_SHRINKING_BY_DEFAULT (true in standard platform builds).
proguard_flags_files Lists module-specific .flags or .pro files containing custom keep rules. Avoid adding custom files when standard annotations or defaults suffice.
export_proguard_flags_files Propagates this library's proguard_flags_files to downstream modules that depend on it statically.
trace_references_from Lists Java library companion targets whose bytecode references into this target R8 automatically traces and retains.

Enable obfuscation wherever possible

In Soong, obfuscate defaults to false for historical compatibility, but you should explicitly set obfuscate: true on android_app and standalone DEX targets wherever possible:

  • Smaller DEX and .vdex footprint: Renaming packages, classes, fields, and methods to short identifiers (a, b) shrinks the DEX string pool (string_ids and string_data_item) and type descriptors. Because Android maps .vdex and .odex files into process memory, smaller symbol tables directly reduce both system partition size and runtime memory footprint.
  • Deeper whole-program optimizations: Letting R8 rename identifiers unlocks class merging, package flattening, and synthetic class deduplication across packages that R8 must otherwise skip to avoid name collisions.
  • Full stack trace symbolication: Enabling obfuscation in Soong doesn't reduce debuggability. For every R8-compiled target, Soong emits a proguard_dictionary mapping file in the module's intermediate directory, bundles all module dictionaries into the build's proguard-dict.zip artifact, embeds a mapping hash (--map-id-template) into the DEX header, and rewrites class SourceFile attributes (--source-file-template) so retrace can symbolicate stack traces automatically.

Keep obfuscate: false (or explicitly preserve public API names) only for:

  • Shared libraries on the bootclasspath or system_server classpath: Modules (java_library or java_sdk_library) that expose an external API surface linked dynamically by other modules at runtime (such as framework.jar, services.jar, or <uses-library> targets) must either keep obfuscate: false or explicitly preserve their API surface using @KeepForApi or -keep rules (along with protect_api_surface: true for bootclasspath targets) so callers compiled against their stubs can resolve class and member names at runtime.

Before enabling obfuscate: true on an application, verify the following migration prerequisites:

  • External test APK dependencies: If an external android_test APK sets instrumentation_for: "MyApp" and directly invokes internal classes or methods of MyApp, renaming those symbols causes NoSuchMethodError or NoClassDefFoundError at test runtime. Prefer structuring the test as a self-instrumenting APK linking MyApp.impl statically, annotate test hooks with @VisibleForTesting, or configure trace_references_from (see Step 2: Migrate test-driven keep rules) so internal symbols needed by tests are preserved while obfuscating the rest of the app.
  • String-based reflection and JNI: If a module looks up classes, methods, or fields by literal string names (Class.forName, getDeclaredMethod, or JNI FindClass and GetMethodID) without keep rules or annotations, obfuscation renames those targets and breaks runtime lookups. (Note that unannotated reflection also fails under shrink: true and optimize: true.) Annotate those entry points with Guide to Keep Annotations (@UsesReflection, @UsedByReflection, or @UsedByNative) so R8 preserves their names and obfuscates the rest of the module.

Follow the zero custom flags principle

The ideal Android platform module has no custom proguard.flags or keep.xml files. In the Android platform build, the vast majority of custom keep rules are redundant:

  • Platform baselines and AAPT2: Most entry points are already retained automatically by global platform baselines (such as @Keep, JNI native methods, and @VisibleForTesting) or generated by AAPT2 from AndroidManifest.xml and layout resources (see Understand default platform keep rules).
  • Targeted alternatives: Where entry points must be preserved, prefer declaration-site annotations (keepanno, @VisibleForTesting), exported library rules (export_proguard_flags_files: true), or static test linking over detached .flags files (see Audit and migrate existing keep rules).

Before adding or retaining a custom proguard.flags or keep.xml file, verify whether the rule is already handled by default baselines or can be migrated to code annotations.

Understand default platform keep rules

Soong automatically passes the following global baseline rules to every R8 invocation on DEX-compiling targets (android_app, android_test, and installable java_library modules), configured in build/soong/java/dex.go:

  • build/make/core/proguard.flags: Preserves classes and members annotated with @com.android.internal.annotations.VisibleForTesting across all packages, and with @VisibleForTesting (androidx.annotation.VisibleForTesting or com.google.common.annotations.VisibleForTesting) within android.**, com.android.**, and com.google.android.** packages. Also preserves @TestApi, @Keep (androidx.annotation, android.support.annotation, com.android.internal.annotations), @KeepForWeakReference, @WeaklyReferencedCallback, and @dalvik.annotation.optimization.**.
  • build/make/core/proguard_basic_keeps.flags: Preserves SourceFile attributes for stack traces, runtime visibility annotations (RuntimeVisible*Annotations), Exceptions and AnnotationDefault attributes, native methods, Serializable members, @JavascriptInterface methods, Throwable(String) constructors, Parcelable$CREATOR fields, and protobuf MessageLite fields.
  • build/make/core/proguard/kotlin.flags: Silences harmless warnings for specific Kotlin meta-annotations (kotlin.Metadata and kotlin.annotation.{AnnotationRetention,AnnotationTarget,Retention,Target}) and strips Kotlin DebugMetadata annotations in release builds.
  • build/make/core/proguard/checknotnull.flags: Replaces common null check helper calls (com.google.common.base.Preconditions.checkNotNull and dagger.internal.Preconditions.checkNotNull*) with concise bytecode null checks. This file intentionally omits Objects.requireNonNull to preserve explicit exception messages across framework API boundaries.
  • build/make/core/proguard/enumvalues.flags: Retains the values and valueOf methods on enum types unless disabled by the build configuration.
  • AAPT2 auto-generated rules: AAPT2 inspects merged AndroidManifest.xml, layout XML, and preference XML files to generate exact keep rules for every registered Activity, Service, BroadcastReceiver, ContentProvider, BackupAgent, Application, custom View subclass, Preference subclass, and XML android:onClick method.

Audit and migrate existing keep rules

When auditing existing proguard.flags or keep.xml files in a platform repository, evaluate each rule in order against the following four-step hierarchy:

Step 1: Delete redundant or obsolete rules

Delete rules that are already covered by global baselines, AAPT2 manifest and layout rules, or R8 defaults, as well as rules referencing classes or packages that no longer exist.

If all rules in a .flags file are redundant, delete the file and remove proguard_flags_files from Android.bp. If the remaining optimize block only restates default settings, remove the redundant block and format the build file with bpfmt -w Android.bp. When cleaning up a package, check whether companion modules in subdirectories (such as Kotlin target variants) reference the same flags file and update them together.

Step 2: Migrate test-driven keep rules

Move test-driven keep rules out of custom production .flags files using one of the following patterns:

  • Link the implementation library into tests (static_libs): When an android_test exercises package-private or internal implementation details of an app, the recommended platform architecture is to place the app source files in an android_library (MyApp.impl) and statically link MyApp.impl into both MyApp and MyAppTests:

    android_library {
        name: "MyApp.impl",
        srcs: ["src/**/*.java"],
        manifest: "AndroidManifest.xml",
    }
    
    android_app {
        name: "MyApp",
        static_libs: ["MyApp.impl"],
        optimize: {
            obfuscate: true,
            shrink_resources: true,
        },
    }
    
    android_test {
        name: "MyAppTests",
        srcs: ["tests/**/*.java"],
        static_libs: ["MyApp.impl"],
    }
    

    This 3-module pattern lets MyAppTests run as a self-instrumenting test with full access to internal classes, lets MyApp enable obfuscate: true freely without breaking tests, avoids shipping test-only entry points in the production APK, and eliminates rebuilding MyApp when test code changes.

  • Annotate test hooks with @VisibleForTesting: If an external test APK calls a small number of internal methods or constructors on an android_app target and restructuring into an .impl library is impractical, annotate those declarations with @VisibleForTesting. The global build/make/core/proguard.flags baseline retains @VisibleForTesting items in android.**, com.android.**, and com.google.android.** packages automatically without custom .flags files. (For vendor modules outside these namespaces, use @UsedByReflection or exported library rules.)

  • Use trace_references_from across platform library boundaries: When tests can't statically link the target implementation (for example, tests exercising system server services or platform JARs like framework-connectivity), configure trace_references_from on the target module pointing to a Java library companion module containing the test sources. R8 traces and retains all classes and members referenced by the companion bytecode.

Step 3: Export rules from the owning library

If a shared java_library or android_library requires keep rules for its own internal reflection or JNI callbacks, define the rules on the library target and set export_proguard_flags_files: true:

java_library {
    name: "my-shared-library",
    srcs: ["src/**/*.java"],
    optimize: {
        proguard_flags_files: ["proguard.flags"],
        export_proguard_flags_files: true,
    },
}

All downstream android_app and library targets that statically link my-shared-library inherit those rules automatically, so downstream apps don't need to duplicate them. When multiple modules across different directories must share a rule set that isn't tied to a single code library, publish the .flags file through an explicit filegroup in Android.bp so modules can reference :my-shared-flags cleanly across package boundaries. For general guidance on authoring library consumer keep rules without restricting downstream app optimization, see Optimization for library authors.

Step 4: Annotate declarations in source code

When a class, method, constructor, or field is accessed through reflection or JNI and isn't covered by AAPT2 or global baselines, replace detached -keep rules in .flags files with source annotations from the Guide to Keep Annotations (see the keepanno Javadoc reference):

  • @UsesReflection: Prefer this annotation when you control the code performing the reflection. Place it at the reflection call site to declare which target classes, methods, or fields are accessed dynamically. Because @UsesReflection automatically encodes a precondition that the annotated call site itself is reachable, R8 prunes both the caller and the reflective target if the call site is unused.
  • @UsedByReflection: Place on classes, methods, fields, or constructors that are instantiated or invoked reflectively by external code or libraries, such as classes loaded with Class.forName from Bundle or Settings keys, or plugin classes loaded across dynamic classloaders. Specify kind (such as KeepItemKind.CLASS_AND_METHODS), preconditions (@KeepCondition), and parameter constraints so R8 keeps only the exact reflective contract and can still optimize or prune unused members of the class. Don't add @UsedByReflection to manifest-registered components like JobService or BroadcastReceiver, because AAPT2 already keeps them.
  • @UsedByNative: Place on methods or fields accessed from C or C++ JNI code using GetMethodID, GetStaticMethodID, or GetFieldID.
  • @KeepForApi: Place on library API classes or members that must remain intact when a library itself is shrunk before distribution.
  • @Keep (androidx.annotation.Keep): Use as a fallback when keepanno isn't applicable. Applying @Keep to a class retains the class and all its members unconditionally. Applying @Keep to a method or field acts as an unconditional entry point that retains the member and its containing class even if the class is never instantiated, whereas keepanno expresses conditional reachability.

To use R8 keepanno annotations in a Soong module:

  1. Add "keepanno-annotations" to libs in Android.bp:

    libs: [
        "keepanno-annotations",
    ],
    
  2. Import com.android.tools.r8.keepanno.annotations.* and annotate the declaration or call site in Java or Kotlin source code:

    import com.android.tools.r8.keepanno.annotations.KeepItemKind;
    import com.android.tools.r8.keepanno.annotations.UsedByReflection;
    
    public final class CustomPluginController {
        @UsedByReflection(
            description = "Instantiated via Class.forName from plugin config",
            kind = KeepItemKind.CLASS_AND_METHODS)
        public CustomPluginController(Context context) {
            // ...
        }
    }
    

    R8 translates keepanno annotations directly into its internal keep rule model and strips the annotations from the final DEX output, adding zero runtime bytecode overhead.

Avoid common pitfalls

The following sections describe frequent proguard.flags and keep.xml pitfalls in platform code and how to fix them.

Broad component keep rules

# Don't do this:
-keep class * extends android.app.Activity
-keep class * extends android.app.Service
-keep class * extends android.content.BroadcastReceiver
  • Why it hurts performance: This rule is completely redundant with AAPT2. Because it uses a wildcard without checking whether the component is registered in the final merged AndroidManifest.xml, it forces R8 to retain every Activity, Service, or BroadcastReceiver subclass found anywhere on the classpath, including unused library components and disabled debug activities.
  • Recommended fix: Delete the rule. AAPT2 inspects the merged manifest and generates exact -keep rules for registered components.

Broad XML click handler keep rules

# Don't do this:
-keepclassmembers class * {
    public void *(android.view.View);
}
  • Why it hurts performance: Retaining every public void *(View) method across every class in the module prevents R8 from stripping or inlining any method that happens to accept a single View parameter.
  • Recommended fix: Delete the rule. AAPT2 scans layout XML files and generates targeted keep rules for methods referenced by android:onClick attributes.

Broad View getter and setter keep rules

# Don't do this:
-keep public class * extends android.view.View {
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
    public void set*(...);
    public *** get*();
}
  • Why it hurts performance: This rule forces R8 to retain every getter and setter across every View subclass in the app and all linked libraries (including AndroidX and Material libraries), blocking dead method removal and inlining across UI code.
  • Recommended fix: Delete the rule. AAPT2 already preserves constructors for custom View classes inflated from layout XML files. If code animates a view property using reflective string names such as ObjectAnimator.ofFloat(view, "translationZ", ...), replace the string name with a typed property reference (View.TRANSLATION_Z or a custom FloatProperty or IntProperty implementation) to avoid reflection entirely. If reflective property access can't be avoided, annotate the specific getter or setter with @UsedByReflection.

Blanket optimization or obfuscation disables

# Don't do this in proguard.flags:
-dontoptimize
-dontshrink
-dontobfuscate
  • Why it hurts performance: Placing -dontoptimize or -dontshrink inside a .flags file silently overrides the module's Android.bp settings and disables optimization passes across the entire target. Worse, if a library exports a .flags file containing -dontoptimize, it disables R8 optimization for every downstream android_app that links the library.
  • Recommended fix: Remove -dontoptimize, -dontshrink, and -dontobfuscate from .flags files. Control optimization behavior explicitly in Android.bp using the optimize block (shrink, optimize, obfuscate) on the leaf target.

Diagnostic flags in committed rules

# Don't do this in proguard.flags:
-verbose
-printmapping proguard.map
-printusage usage.txt
-printconfiguration config.txt
  • Why it hurts the build: Diagnostic flags flood build logs or attempt to write output files to local paths during sandboxed Soong builds.
  • Recommended fix: Delete these flags from committed .flags files. Soong automatically writes R8 mapping and usage outputs, such as proguard_dictionary and proguard_usage.zip, to the module's intermediate directory under out/soong/.intermediates/.

Production keep rules for test code

  • Why it hurts performance: Adding custom -keep rules solely for test access forces that code to remain un-obfuscated and retained in production builds. While annotating methods with @VisibleForTesting or configuring trace_references_from avoids maintaining manual -keep rules, those symbols still ship in the production binary.
  • Recommended fix: To keep test-only entry points completely out of the production APK, structure the application into a .impl library and link it into a self-instrumenting test APK using static_libs, as described in Step 2: Migrate test-driven keep rules.

Blanket resource wildcards in keep.xml

<!-- Don't do this in res/raw/keep.xml: -->
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@raw/*,@drawable/*,@string/*" />
  • Why it hurts performance: Blanket wildcards retain every resource in the matching type across the module and its dependencies, defeating resource shrinking (shrink_resources: true) and inflating the APK and mapped resources.arsc table.
  • Recommended fix:
    • Prefer static resource references over keep.xml: Avoid using the Resources.getIdentifier method to look up a bounded set of resources dynamically, such as numbered experiment strings or themed drawables. Instead, use a compile-time switch statement or map over static R.id, R.string, or R.drawable constants. Static references let R8 and AAPT2 trace the exact live resources, eliminate runtime string lookup overhead, and remove the need for keep.xml altogether.
    • List specific resource names: If dynamic lookup is required, such as by a third-party license reader, list the exact resource identifiers in tools:keep (for example, tools:keep="@raw/third_party_licenses").
    • Delete redundant keep.xml files: If the listed resources are already referenced statically in code (R.raw.foo) or XML (@raw/foo), the shrinker retains them automatically, so you can delete res/raw/keep.xml.

Verify and audit rule changes

Whenever you remove or narrow keep rules in proguard.flags or keep.xml, verify that the module builds cleanly, passes unit and instrumentation tests, and retains all required entry points.

Build and test modules

  1. Build the module cleanly to verify that R8 and AAPT2 complete without missing reference warnings:

    m <MODULE_NAME>
    
  2. Run unit and instrumentation tests for the module using atest:

    atest <TEST_MODULE_NAME>
    
  3. For system apps, privileged services, or hardware-dependent modules, run instrumentation and UI tests on target physical devices or in your device test lab to exercise runtime reflection paths, IPC bindings, and resource inflation.

Inspect DEX and resource diffs

Compare the compiled APK or JAR before and after rule changes to confirm that R8 removes dead code and resources without stripping expected entry points:

  • Use apkanalyzer or dexdump to inspect retained classes, methods, and fields in the output APK or DEX files:

    apkanalyzer dex packages $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    
  • When modifying keep.xml or enabling shrink_resources, use aapt2 dump resources to verify that the build strips unused resources and retains required ones:

    aapt2 dump resources $OUT/system/priv-app/<APP_NAME>/<APP_NAME>.apk
    

Analyze keep radius and rule subsumption with R8

The open-source R8 compiler includes a Keep Radius analyzer (also exposed in Android Studio and Gradle as the R8 Configuration Analyzer) that measures the exact impact of every keep rule during compilation. Soong integrates this analyzer directly into the Android platform build (configured in build/soong/java/dex.go).

To analyze keep rules for a platform module or across an entire build:

  1. Run the build with the R8_DUMP_KEEP_RADIUS=true environment variable:

    R8_DUMP_KEEP_RADIUS=true m <MODULE_NAME>
    

    When R8_DUMP_KEEP_RADIUS=true is set, Soong instructs R8 to record keep rule metrics in an intermediate r8keepradius.pb file for each compiled module under out/soong/.intermediates/. For each keep rule and keepanno annotation, R8 records:

    • Immediate keep radius: The exact classes, fields, and methods retained by the rule, along with the specific constraints it enforces against shrinking, optimization, or obfuscation.
    • Rule subsumption: Which other keep rules or annotations already retain the same items. If a custom rule is completely subsumed by an AAPT2 rule or a global baseline rule, you can safely delete it.
    • Package-wide and global rules: Rules that use broad package-wide wildcards or apply global configuration directives.
  2. Convert the r8keepradius.pb output into an interactive HTML report using KeepRadiusHtmlReportGenerator (bundled in prebuilts/r8/r8.jar):

    # Generate an HTML report for a single module
    INTERMEDIATES=out/soong/.intermediates/packages/apps/<APP_NAME>/<APP_NAME>
    java -cp prebuilts/r8/r8.jar \
        com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \
        $INTERMEDIATES/android_common/r8keepradius.pb \
        keep_radius_report.html
    
    # Scan all built modules under out/soong/.intermediates and generate
    # per-module HTML reports plus an aggregate keepradius.html summary
    java -cp prebuilts/r8/r8.jar \
        com.android.tools.r8.keepradius.KeepRadiusHtmlReportGenerator \
        out/soong/.intermediates \
        out/keep_radius_reports
    

    When given a directory, KeepRadiusHtmlReportGenerator walks all *keepradius*.pb files, generates an HTML report for each module, and creates out/keep_radius_reports/keepradius.html summarizing live item counts, kept item counts, and the highest-radius keep rules across the build. For more information about interpreting shrinking, optimization, and obfuscation scores in the generated report, see Use R8 Configuration Analyzer.

Soong also supports two additional R8 diagnostic environment variables in build/soong/java/dex.go:

  • R8_DUMP_INPUT=true: Writes r8inputs.zip to the module's intermediate directory containing all input JARs, library JARs, and merged ProGuard configurations for standalone R8 reproduction.
  • R8_DUMP_PERFETTO_TRACE=true: Writes r8trace.ptrace to the module's intermediate directory for inspecting R8 compilation passes in Perfetto.

Validate library consumer rules

When a java_library or android_library exports keep rules to downstream consumers (export_proguard_flags_files: true), those rules mustn't include global flags that alter or disable optimization for the consuming app.

R8 provides an open-source keep rule analyzer (ProcessKeepRules), exposed in the Android platform build as the process-keep-rules host tool (defined in prebuilts/r8/Android.bp):

# Build the R8 keep rules validator host binary
m process-keep-rules

# Validate one or more ProGuard configuration files
out/host/linux-x86/bin/process-keep-rules <PROGUARD_FLAGS_PATH>

The process-keep-rules tool parses each configuration file and fails with file and line diagnostics if it encounters directives that are disallowed in library consumer rules. Disallowed directives include global optimization, shrinking, or obfuscation disables (such as -dontoptimize or -dontshrink), package repackaging and access modification flags, diagnostic or mapping flags, and app-level -keepattributes.

Audit source trees with pgaudit.py

To audit unbuilt source directories alongside R8's build-time analyzers, the Android platform tree includes the pgaudit.py script under build/make/core/proguard/tools/. This static analysis tool scans Android.bp, proguard.flags, and keep.xml files across a repository to flag rules already covered by AAPT2 or global platform baselines, broad wildcards, -dontoptimize overrides, and test-only keep rules:

# Audit a single package directory
./build/make/core/proguard/tools/pgaudit.py packages/apps/Provision/

# Scan a subsystem tree and write a structured JSON report
./build/make/core/proguard/tools/pgaudit.py packages/ --json=audit_report.json

Platform and OEM teams can combine pgaudit.py for fast source-tree triage, process-keep-rules to validate exported library rules, and R8_DUMP_KEEP_RADIUS=true with KeepRadiusHtmlReportGenerator to measure the exact class, field, and method retention radius of every rule in a build.