Skip to content

Latest commit

 

History

History
199 lines (161 loc) · 8.59 KB

File metadata and controls

199 lines (161 loc) · 8.59 KB

Shrinking and Optimization with R8

This page covers how to configure code shrinking, resource shrinking, bytecode optimization, and obfuscation using R8 with rules_android.

If you're new to building Android apps with Bazel, start with the Android App Tutorial.

Overview

Android builds use R8 to reduce application size, decrease runtime memory usage, and improve performance. By eliminating unused code and resources, R8 reduces both the on-device download size and the runtime memory footprint of your application.

R8 performs four core functions during the build process:

  • Code shrinking (tree shaking): Detects and safely removes unused classes, fields, methods, and attributes from your app and its library dependencies, reducing DEX size and runtime memory consumption.
  • Resource shrinking: Removes unused resources (such as drawables, layouts, and strings) packaged in your app. Resource shrinking works in tandem with code shrinking to reduce on-disk and in-memory asset overhead.
  • Bytecode optimization: Analyzes and optimizes bytecode instructions to reduce DEX code size and improve runtime efficiency on the Android Runtime (ART).
  • Obfuscation (name minification): Renames classes, fields, and methods with short, obfuscated names (such as a, b, c), reducing DEX file size and making reverse engineering more difficult.

Note that while R8 is the modern tool used under the hood (replacing ProGuard), configuration attributes in android_binary and android_application rules still retain the proguard_ prefix (such as proguard_specs and proguard_generate_mapping) for historical compatibility with ProGuard configuration rule syntax.

Configuring R8 in android_binary and android_application

R8 is enabled and configured using attributes on the android_binary rule (to produce an APK) or the android_application rule (to produce an Android App Bundle / AAB). Both rules accept the same R8 optimization attributes.

Key attributes

  • proguard_specs: A list of labels pointing to ProGuard/R8 configuration files containing keep rules and optimization directives. Specifying this attribute enables R8 code shrinking and optimization. Typically, this includes:
  • proguard-android-optimize.txt: Contains standard recommended Android app optimizations and default keep rules (equivalent to the default configuration provided by the Android Gradle Plugin). You can download it from the example repository and place it in your project.
  • proguard-rules.pro: An empty file where you add custom keep rules specific to your app, following the guide on adding keep rules.
  • shrink_resources: A boolean indicating whether to enable resource shrinking. When set to True, unused Android resources are removed from the packaged APK or AAB. Note: Resource shrinking requires proguard_specs to be enabled.
  • proguard_generate_mapping: A boolean indicating whether Bazel should generate a mapping file (_proguard.map) that maps obfuscated class and method names back to their original source names. This is essential for de-obfuscating crash stack traces in production.

Recommended target structure

Because R8 optimization increases build times, a best practice is to declare a separate optimized target for release builds while using an unoptimized target during daily iterative development.

The following example BUILD configuration can be added directly to the Android App Tutorial project in src/main/BUILD:

load("@rules_android//rules:rules.bzl", "android_binary")

# Unoptimized target for faster local build time and testing
android_binary(
    name = "app",
    manifest = "//src/main/java/com/example/bazel:AndroidManifest.xml",
    deps = ["//src/main/java/com/example/bazel:greeter_activity"],
)

# Optimized target for release and performance testing
android_binary(
    name = "r8-optimized-app",
    manifest = "//src/main/java/com/example/bazel:AndroidManifest.xml",
    proguard_generate_mapping = True,
    proguard_specs = [
        "proguard-android-optimize.txt",
        "proguard-rules.pro",
    ],
    shrink_resources = True,
    deps = ["//src/main/java/com/example/bazel:greeter_activity"],
)

Configuring keep rules

R8 inspects all reachable entry points in your application. However, code or resources accessed dynamically at runtime (such as via reflection, JNI native methods, or XML layout references) might appear unused to static analysis and could be stripped or renamed inadvertently.

To prevent R8 from removing or obfuscating required code, define keep rules in your proguard-rules.pro file (initially created as an empty file alongside your BUILD file). For detailed instructions and best practices, see the official Android guide on how to add keep rules.

Common keep rule examples

# Preserve a class and all its public/protected methods and fields
-keep class com.example.bazel.model.** {
    public protected *;
}

# Preserve class members accessed via reflection
-keepclassmembers class com.example.bazel.data.UserData {
    <fields>;
}

# Preserve native JNI methods
-keepclasseswithmembernames class * {
    native <methods>;
}

# Suppress warnings from third-party dependencies with incomplete references
-dontwarn com.example.thirdparty.**

Building and inspecting outputs

Run the following command to build the optimized binary:

bazel build //path/to:r8-optimized-app

Build outputs

Bazel places build artifacts in the bazel-bin output directory:

  • Optimized APK or AAB: bazel-bin/path/to/r8-optimized-app.apk (or .aab when using android_application), containing the shrunk and optimized app.
  • ProGuard Mapping File: bazel-bin/path/to/r8-optimized-app_proguard.map (generated when proguard_generate_mapping = True), containing mapping data for stack trace de-obfuscation.
  • Optional diagnostic files: Diagnostic files such as seeds and usage lists indicating which classes and members were kept or removed can optionally be configured via flags in proguard-rules.pro, e.g. -printseeds <file> and -printusage <file>.

App size impact

For small sample applications, the difference in APK or AAB size between unoptimized and optimized builds may be minimal. However, as an application grows and incorporates larger third-party dependencies (such as Guava, AndroidX, or gRPC), R8's tree shaking and resource shrinking can significantly reduce the final download and install size.

Troubleshooting & testing

If you encounter issues or unexpected behavior when running your R8-optimized app, refer to the following R8 guides:

Further reading