ProGuard Workflow
ProGuard operates through four distinct phases: shrink, optimize, obfuscate, and preverify. All four phases are optional, but their execution order remains fixed.
shrink: Identifies and removes unused classes, fields, methods, and attributes from the project.
optimize: Optimizes bytecode by removing unnecessary instructions or performing instruction-level optimizations. (R8 does not provide an option to disable optimization, nor does it support customization of optimization behavior. Attempting to use -dontoptimize in the configuration will have no effect—it won't be recognized or executed by R8.)
obfuscate: Renames classes, fields, methods, and attributes to meaningless identifiers, reducing the readability of decompiled code.
preverify: Performs pre-verification for Java 1.6 and above by validating StackMap/StackMapTable attributes. This step can be disabled during compilation to speed up the build process.
R8 Workflow
The R8 compilation flow is illustrated below:
- R8 consolidates desugaring, shrinking, optimization, obfuscation, and dex conversion (D8 compiler) into a single step
- R8 executes shrinking, optimization, and obfuscation on .class files
- The D8 compiler handles desugaring and converts .class files to .dex format
Comparing ProGuard and R8:
Similarities:
- Both are open source
- R8 supports all existing ProGuard rule files
- Both provide four core functionalities: shrinking, optimization, obfuscation, and preverification
Differences:
- ProGuard can be used for Java projects, while R8 is specifically designed for Android projects
- R8 integrates desugaring, shrinking, optimization, obfuscation, and dex compilation (D8 compiler) into one step, significantly improving build performance
Android Build and Packaging Process
Overall Build Flow
Key Build Steps
The complete build process consists of seven steps:
- Package resource files and generate R.java
- Process AIDL files to generate corresponding .java files
- Compile project source code to generate .class files
- Convert all .class files to generate classes.dex
- Package to create the APK file
- Sign the APK file
- Align the signed APK file
AAPT2
AAPT2 (Android Asset Packaging Tool 2) is a build tool used by Android Studio and the Android Gradle Plugin to compile and package application resources. AAPT2 parses resources, creates resource indexes, and compiles them into a binary format optimized for the Android platform.
After AGP 3.0.0, AAPT2 is enabled by default for resource compilation, supporting incremental compilation for faster resource processing. This is achieved by splitting resource processing in to two phases:
-
Compile: Converts resource files to binary format. All Android resource files are parsed and converted to binary files with the
.flatextension. For example, PNG images are processed and compressed with the.png.flatextension. The generated intermediate outputs can be found inbuild/intermediates/merged_res/. -
Link: Merges all compiled files and packages them into a single distribution. This phase first generates auxiliary files like R.java and resources.arsc. The R file serves as a resource index—resources are typically referenced through
R.syntax. The resources.arsc file is the resource index table used at runtime to look up resources by ID. Finally, the R file, resources.arsc, and the previously generated binary files are packaged together.
This split approach enhances incremental build performance. For instance, if a file is modified, only that specific file needs recompilation.
AAPT2 generates retention rules based on references to classes, layouts, and other application resources found in the manifest. For example, AAPT2 automatically adds retention rules for each activity registered as an entry point in your application manifest.
Obfuscation in Modular Projects
In modular architectures, you must account for behavioral differences between application Modules and Library Modules, along with the resource aggregation rules. Key points to remember:
- During compilation, each Library Module layer is compiled sequentially, with the bottommost Base Module compiled to an aar file first. When the next layer compiles, it extracts the dependent Module's output aar/jar files into the corresponding folders within the module's build directory
- The App Module layer only begins actual compilation after aggregating all aar files
- Modules compiled later will overwrite resources with the same name from previously compiled Modules
Using newer versions of the Android Gradle Plugin does not place aggregated resources in the exploded-aar folder. Nevertheless, the resource aggregation rules from Library Modules to the App Module remain consistent.
Obfuscation is enabled by the App Module and has no relation to Lib Modules. Adding obfuscation rules to a Lib Module's proguard-rules.pro file will not take effect. To make rules effective, add the following configuration to the Lib Module's build.gradle:
android {
defaultConfig {
consumerProguardFiles 'consumer-rules.pro'
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
The consumer-rules.pro file should contain:
-keep class com.example.library.internal.**
Obfuscation File Reference
Code obfuscation transforms source code into a form that is difficult to read and understand, protecting intellectual property and reducing application size. Below is a reference for commonly used obfuscation configuration files in Android development:
- proguard-android.txt: The default rule set, located in
ANDROID_SDK\tools\proguard. Provides basic obfuscation and optimization settings. - proguard-android-optimize.txt: Contains rules for further code size reduction. While more effective at shrinking applications, processing time is longer. Also located in
ANDROID_SDK\tools\proguard. - proguard-rules.pro: User-defined obfuscation rules file, customizable based on project requirements.
- usage.txt: Records classes, methods, and fields removed during obfuscation.
- mapping.txt: Stores the mapping between original and obfuscated class, method, and field names. This is essential for error tracing and debugging.
- seeds.txt: Lists classes, methods, and fields retained by keep rules, used to verify that keep rules are functioning correctly.
- configuration.txt: Contains all configured obfuscation rules, located at
app/build/outputs/mapping/release/configuration.txt. - consumer-rules.pro: Specifies obfuscation rules bundled with AAR (Android Archive) library dependencies when published.
- aapt_rules.txt: Generated in
<module-dir>/build/intermediates/proguard-rules/debug/aapt_rules.txtwhen building withminifyEnabled true. AAPT2 generates retention rules based on references to classes, layouts, and other application resources in the manifest. For instance, AAPT2 automatically adds retention rules for each activity registered as an entry point.
Sample Configuration Files
consumer-rules.pro
consumerProguardFiles Configuration: In Android project build configuration, consumerProguardFiles is a key setting that specifies ProGuard rules to bundle with published AAR libraries. Key aspects of this configuration:
-
AAR Embedded Rules: This setting allows library developers to embed dedicated ProGuard rule files with in AAR libraries. These rules are included directly in the published AAR package, ensuring the library's obfuscation rules are distributed alongside the library itself.
-
Automatic Rule Inheritance: When other application projects depend on this AAR and have ProGuard or R8 enabled, they automatically inherit and apply these preconfigured ProGuard rules. This ensures the AAR library is obfuscated as intended in the final application.
-
Customization and Exclusion: Through
consumerProguardFiles, library developers can precisely specify which code should be preserved (such as public APIs or components that need to be accessible to other applications) and which code can be safely removed or obfuscated. This provides a higher level of customization and control for library distribution. -
Project Type Applicability: Note that
consumerProguardFilesapplies only to library projects (AAR or JAR), not to regular application projects. In application projects, this configuration is ignored, and developers must manage obfuscation rules directly in the application's build configuration.
defaultConfig {
minSdk 21
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
proguard-android.txt
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-verbose
-dontoptimize
-dontpreverify
-keepattributes *Annotation*
-keep public class com.google.vending.licensing.ILicensingService
-keep public class com.android.vending.licensing.ILicensingService
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclassmembers public class * extends android.view.View {
void set*(***);
*** get*();
}
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
-keepclassmembers class **.R$* {
public static <fields>;
}
-dontwarn android.support.**
-keep class android.support.annotation.Keep
-keep @android.support.annotation.Keep class * {*;}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <methods>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <fields>;
}
-keepclasseswithmembers class * {
@android.support.annotation.Keep <init>(...);
}
proguard-android-optimize.txt
proguard-android-optimize.txt differs only sllightly from proguard-android.txt.
-optimizations !code/simplification/arithmetic,!code/simplification/cast,!field/*,!class/merging/*
-optimizationpasses 5
-allowaccessmodification
-dontpreverify
ProGuard Mapping Configuration
Mapping files are generated in the project/module/build/outputs/mapping/debug_or_release directory. When distributing software, always retain the mapping.txt file to map error reports back to original source code.
####################Basic Obfuscation Directives####################
# Preserve specified package names from obfuscation. Multiple packages can be separated by commas.
# Supports ?, *, ** wildcards and ! negation prefix.
# Required when using code like mypackage.MyClass.class.getResource("")
-keeppackagenames com.example.myapp
# Specify obfuscation dictionary for class, method, and field names
-obfuscationdictionary dictionary.txt
# Output tool usage information to usage.txt
-printusage ../mapping/usage.txt
# Output obfuscation mapping to mapping.txt
-printmapping ../mapping/mapping.txt
# Output seed file to seeds.txt
-printseeds ../mapping/seeds.txt
# Output configuration to configuration.txt
-printconfiguration ../mapping/configuration
# Enable stack trace deobfuscation
-renamesourcefileattribute SourceFile
# Disable optimization (enabled by default)
-dontoptimize
# Disable shrinking (enabled by default)
#-dontshrink
# Obfuscation compression ratio between 0-7, default is 5
-optimizationpasses 5
# Avoid mixed-case class names
-dontusemixedcaseclassnames
# Allow access modifier modification during optimization
-allowaccessmodification
# Don't skip non-public library classes
-dontskipnonpubliclibraryclasses
# Don't skip non-public library class members
-dontskipnonpubliclibraryclassmembers
# Enable verbose logging for mapping file generation
-verbose
# Ignore warnings to prevent build failures
-ignorewarnings
# Disable preverification to speed up obfuscation
-dontpreverify
# Preserve annotations
-keepattributes *Annotation*,InnerClasses
# Preserve generics
-keepattributes Signature
# Preserve line numbers in exception stack traces
-keepattributes SourceFile,LineNumberTable
# Google-recommended optimization filter
-optimizations !code/simplification/cast,!field/*,!class/merging/*
####################Android Common Preservations####################
# Preserve support library classes
-keep class android.support.** {*;}
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**
# AndroidX obfuscation
-keep class com.google.android.material.** {*;}
-keep class androidx.** {*;}
-keep public class * extends androidx.**
-keep interface androidx.** {*;}
-dontwarn com.google.android.material.**
-dontnote com.google.android.material.**
-dontwarn androidx.**
# Preserve all ViewBinding classes
-keep class * implements androidx.viewbinding.ViewBinding {
*;
}
-keep class * extends androidx.viewbinding.ViewBinding {
public static final ** inflate(**);
public static * bind(android.view.View);
}
# Preserve R class resources
-keep class **.R$* {*;}
# Preserve native methods
-keepclasseswithmembernames class * {
native <methods>;
}
# Preserve Activity methods accepting View parameters
-keepclassmembers class * extends android.app.Activity {
public void *(android.view.View);
}
# Preserve enums
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# Preserve Parcelable implementations
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}
# Preserve Serializable implementations
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
private static final java.io.ObjectStreamField[] serialPersistentFields;
!static !transient <fields>;
!private <fields>;
!private <methods>;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# Preserve custom View subclasses
-keep public class * extends android.view.View {
*** get*();
void set*(***);
public <init>(android.content.Context);
public <init>(android.content.Context, android.util.AttributeSet);
public <init>(android.content.Context, android.util.AttributeSet, int);
}
# Preserve callback methods and listeners
-keepclassmembers class * {
void *(**On*Event);
void *(**On*Listener);
}
# WebView obfuscation handling
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
public *;
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.WebViewClient {
public void *(android.webkit.WebView, java.lang.String);
}
#############################################
#
# Project-Specific Obfuscation Rules
#
#############################################
# assumenosideeffects allows removal of method calls without side effects
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(java.lang.String, int);
public static int d(...);
}
#############################################
#
# Third-Party Library Obfuscation Rules
#
#############################################
# Gson
-keepattributes *Annotation*
-keep class sun.misc.Unsafe { *; }
-keep class com.example.data.model.**
-keep class com.google.gson.stream.** { *; }
# OkHttp3
-dontwarn okhttp3.logging.**
-keep class okhttp3.internal.** {*;}
-dontwarn okio.**
# Retrofit
-dontwarn retrofit2.**
-keep class retrofit2.** { *; }
-keepattributes Signature
-keepattributes Exceptions
# RxJava RxAndroid
-dontwarn sun.misc.**
-keepclassmembers class rx.internal.util.unsafe.*ArrayQueue*Field* {
long producerIndex;
long consumerIndex;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueProducerNodeRef {
rx.internal.util.atomic.LinkedQueueNode producerNode;
}
-keepclassmembers class rx.internal.util.unsafe.BaseLinkedQueueConsumerNodeRef {
rx.internal.util.atomic.LinkedQueueNode consumerNode;
}
# Glide
-keep class com.bumptech.glide.** {*;}
# Library obfuscation for makeJar task
-keep class com.example.myapp.data.model.** { *; }
Important Notes
-allowaccessmodification: This directive allows the obfuscator to modify access modifiers on classes, fields, and methods during code optimization. The obfuscator can change access modifiers from private to protected or public, or make other necessary modifications to enable further optimization and size reduction.
When this option is enabled, methods or member variables without modifiers can be transformed into members with modifiers, as demonstrated below:
Without -allowaccessmodification:
void test() {
}
With -allowaccessmodification:
public void test() {
}
The public modifier will be automatically added.