(转) The Unofficial R8 Document

Introduction

This is unofficial documentation for R8, Google’s code shrinker for Android™.
Google intends R8 to be a drop-in replacement for ProGuard, and has provided documentation in the Android Studio User Guide to help you get started with it.
However, they rely on the ProGuard Manual for detailed documentation, even though there are substantial differences between R8 and ProGuard.
This documentation is meant to supplement the Android Studio User Guide and the ProGuard Manual to fill that gap.

Assumptions

This documentation assumes that you are using the standard Gradle™ build process of an Android application or library with version 3.4 or later of the Android Gradle Plugin.
It is not suitable if you are using R8 directly in a custom build process.

Note: Known issues reflected in this document were last tested on R8 v1.6.67 using Android Gradle Plugin v3.6.1.

Obfuscation, Shrinking, Renaming, and Minification - What’s the Difference?

The Android Studio documentation and the R8 ruleset itself use the term “obfuscation” in a way that isn’t very precise.
As we make an Android Obfuscator, we think it’s important to understand the distinction between Obfuscation, Renaming, Shrinking, and Minification.

  • Obfuscation generally refers to a broad set of techniques that make code more difficult to understand and reverse engineer, but in the context of R8 rules and documentation it specifically refers to Renaming of packages, classes, methods, and fields that R8 performs.
    R8 employs renaming primarily to reduce the size of the application or library on which it is operating, not to protect it from reverse engineering.
    We avoid using the term “obfuscation” in this documentation in favor of “renaming”.
  • Code Shrinking or Tree Shaking refers to the removal of unused classes and members from your application or library, primarily to reduce its size.
  • Minification, as in minifyEnabled, is sometimes used to describe the combination of Shrinking and Renaming for the purpose of reducing the size of an application or library.

In addition to Renaming and Code Shrinking, R8 also performs Optimization, which rewrites code to improve its performance and further reduce its size.
The Android Gradle Plugin also performs Resource Shrinking, which reduces the size of resources in a similar manner to the way that Code Shrinking reduces the size of applications or libraries.
This is not a feature of R8 itself, but they are related processes; the Android Gradle Plugin requires that you enable a code shrinker to shrink resources.

General Rules

Rule Description
-allowaccessmodification Allows R8 to change access modifiers, enabling additional optimizations and additional reorganizations to packages in which classes are contained. (ProGuard docs)
-assumenosideeffects <class-spec> Informs R8 it can safely remove calls to the specified method(s) during optimization. If the method returns a value that appears to be used, the call may not be removed. Note that this rule is ignored if -dontoptimize is also configured. (ProGuard docs)
-dontobfuscate Do not apply renaming, regardless of other configuration. (ProGuard docs)
-dontoptimize Do not optimize the code, regardless of other configuration. This is part of the default configuration. (ProGuard docs)
-dontshrink Do not remove any classes, methods, or fields, regardless of other configuration. (ProGuard docs)
-include <filename> Include configuration from file with filename filename. (ProGuard docs)
-keepattributes [<filter>] Allows you to specify supported Java&trade; attributes for R8 to retain in the code. Unlike ProGuard, R8 does not respect rules regarding Synthetic, Deprecated, or MethodParameters and will remove these attributes regardless of what is configured in -keepattributes. Also, for class version 50 (Java 6), R8 will keep a StackMapTable attribute only if StackMapTable is covered by -keepattributes; it is always kept for later class versions. (ProGuard docs) (See issue)
-printconfiguration [<file>] Outputs the used configuration rules to the specified file, or to stdout if there is no file specified. Note that if you specify a file, every build of a variant using this rule will overwrite that file. (ProGuard docs)
-printseeds [<filename>] Outputs a list of the classes, methods, and fields which match the keep rules to the specified file, or to stdout if there is no file specified. Note that if you specify a file, every build of a variant using this rule will overwrite that file. Note that unlike ProGuard, R8 will not automatically output a build/outputs/mapping[/{flavorName}]/{buildType}/seeds.txt file. (ProGuard docs)
-printusage [<filename>] Outputs a list of the classes, methods, and fields which were removed during shrinking to the specified file, or to stdout if there is no file specified. Note that if you specify a file, every build of a variant using this rule will overwrite that file. Note that unlike ProGuard, R8 will not automatically output a build/outputs/mapping[/{flavorName}]/{buildType}/usage.txt file. (ProGuard docs)

Keep Rules

Application of shrinking and renaming is configured by using the -keep* rules.
These rules are configured by proving a class specification and optional modifiers.

Rule (and Arguments) Description
-keep[,<modifier>[...]] <class-spec> Exclude matching classes, and matching members if specified, from shrinking, optimization, and renaming. Shrinking exclusion on the class means that members will not be removed but does not prevent members from being renamed. Specifying members will prevent them from being renamed if present. (ProGuard docs)
-keepclassmembers[,<modifier>[...]] <class-spec> Exclude matching members in matching classes from shrinking, optimization, and renaming. (ProGuard docs)
-keepclasseswithmembers[,<modifier>[...]] <class-spec> Exclude matching classes and matching members from shrinking, optimization, and renaming if the corresponding class has all of the specified members. (ProGuard docs)
-keepnames[,<modifier>[...]] <class-spec> Prevent matching classes, and matching members if specified, from being renamed. (ProGuard docs)
-keepclassmembernames[,<modifier>[...]] <class-spec> Prevent any matching members from being renamed in matching classes. (ProGuard docs)
-keepclasseswithmembernames[,<modifier>[...]] <class-spec> Prevent matching classes and matching members from being renamed if the corresponding class contains all of the specified members. This does not prevent matching members from being removed by shrinking (ProGuard would also prevent the specified members from being removed). (ProGuard docs)
-whyareyoukeeping <class-spec> Log details about why particular classes and members were maintained in the output. (ProGuard docs)
-if <class-spec> <one-keep-rule> Conditionally apply one keep rule. If class members are specified, the class and all specified members must match. Otherwise, only the class need match. Class specification in the keep rule can contain back references to wildcards in the -if class specification. (ProGuard docs)

Keep rule modifiers:

Modifier Effect
allowshrinking Allow the target(s) of the rule to be removed by shrinking. (ProGuard docs)
allowoptimization Allow the target(s) of the rule to be optimized. (ProGuard docs)
allowobfuscation Allow the target(s) of the rule to be renamed. Adding this modifier to one of the -keep*names rules causes that rule to have no effect. (ProGuard docs)
includedescriptorclasses Prevent specified field types, method return types, and method parameter types from being renamed. This preserves field and method signatures (post type-erasure, e.g. this does not preserve generic types). (ProGuard docs)

Note: It is not clear what optimization R8 does, or how much control over that process is provided through the -keep* rules and the allowoptimization modifier.

Class Specification

Several of the rules accept a class specification (class-spec) which is a specification of classes and members that has a Java-like syntax.
For example:

-keepclassmembernames public class some.path.to.MyClass {
    int intField;
    android.content.Context getApplicationContext();
    public static String *;
}

The syntax has strong support for filtering classes, methods, and fields.
The syntax supports class (classes), interface (interfaces), enum (enumerations), and @interface (annotations).
The special symbol <init> is used to represent the name of a class’s constructor.

Wildcards and Special Characters

The syntax also supports wildcards and negation using special characters:

  • ! negates the condition described by the subsequent specification. Can be used with modifiers and with the class, interface, enum, and @interface keywords.
  • * a sequence of zero or more characters, other than package separators (.), when used with other symbols in a pattern. Matches any reference type when used alone (this is not supported in all contexts in ProGuard).
  • ** a sequence of zero or more characters, including package separators (.), when used with other symbols in a pattern. Matches any reference type when used alone (does not match primitive types or void).
  • *** a sequence of zero or more characters, including package separators (.), when used with other symbols in a pattern. Matches any reference type, primitive type, or void when used alone.
  • % matches any primitive type (does not match void) when used alone.
  • ? matches any one character.
  • <integer> integer (starting at 1) referencing the value that matched a wildcard used earlier in the specification.
    For -if-predicated -keep* rules, the index can reference any earlier wildcard match in the specification for either part.
    Neither R8 nor ProGuard seem to handle back references in the presence of wildcards in both the class name and class member names.
    R8 does not appear to handle back references within member specifications.
  • ... matches any number of arguments when used within parentheses (( and )) of a method specification.

For example:

-keepclassmembernames class * { long *UUID; } # don't rename long-valued fields ending with UUID in classes

Several other useful constructs are recognized in the class specification:

  • <fields>; is a special string representing all fields
  • <methods>; is a special string representing all methods

Note: There are some differences between how the filter syntax is interpreted by R8 and ProGuard.
For example, *; represents all fields and methods in both, but only R8 recognizes * *; (all fields) and * *(...); (all methods).

Modifiers

You can use the following modifier keywords to narrow down wildcards used in class specifications:

Name Class Method Field
abstract
final
native
private
protected
public
static
strictfp
synchronized
transient
volatile

If multiple modifiers are used together on a single expression, then in most cases only classes, methods, or fields that match all of the applied modifiers will be matched.
However, if mutually exclusive modifiers are applied (e.g., private and protected), classes, method, and fields that match either of the mutually exclusive modifiers may be matched.

For example:

-keep public class * { # All public classes
    public static *; # All public static fields in those classes
    public protected abstract *(...); # All public or protected abstract methods in those classes
}

Subtype Matching and Annotated Matching

There are two powerful constructs that can be used with class filtering: subtype matching and annotated matching.

Specify either extends <type-name> or implements <interface-name> to match types that either extend or implement another type.
For example, -keep class * implements some.particular.SpecialInterface will match all classes that implement SpecialInterface.
Note that extends and implements can be used interchangeably.

Specify an annotation on the type filter to indicate that only types that are annotated with that annotation should match the filter.
For example, -keep @some.package.SomeAnnotation interface * will match all interfaces that are annotated with @SomeAnnotation.

Renaming Configuration

There are several rules which control the naming of classes, methods, and fields:

Rule Description
-keeppackagenames [<filter>] Don’t rename packages which match the filter. (ProGuard docs)
-flattenpackagehierarchy [<name>] When renaming a class, move the package containing the class to a common base package with the specified name, or to the default package if no name is specified. Using -allowaccessmodification increases the number of classes which can be moved to a new package. (ProGuard docs) (See note)
-repackageclasses [<name>] When renaming a class, move it to the named package, or to the default package if no package is named. (Overrides -flattenpackagehierarchy) Using -allowaccessmodification increases the number of classes which can be moved to a new package. (ProGuard docs) (See note)
-overloadaggressively Use the same name as much as possible, even if it may not be allowed by the source language. (ProGuard docs)
-adaptclassstrings [<filter>] Update strings containing class names to use the new names. This can be filtered to only look for strings in certain classes. (ProGuard docs)
-adaptresourcefilenames [<filter>] Rename Java resource files to match renamed classes. This can be filtered to look at particular files. (ProGuard docs)
-adaptresourcefilecontents [<filter>] Update Java resource file contents to match renamed classes. This can be filtered to look at particular files. (ProGuard docs)

Flatten vs. Repackage

There is a subtle difference between -flattenpackagehierarchy and -repackageclasses.
-repackageclasses moves the classes into a single package.
-flattenpackagehierarchy renames the packages to be based on the name, keeping classes in their own package.

Given three classes:

  • com.example.packageOne.ClassOne
  • com.example.packageOne.subPackageOne.ClassTwo
  • com.example.packageTwo.ClassThree

-repackageclasses "go.here" will result in:

com.example.packageOne.ClassOne -> go.here.a:
com.example.packageOne.subPackageOne.ClassTwo -> go.here.b:
com.example.packageTwo.ClassThree -> go.here.c:

-flattenpackagehierarchy "go.here" will result in:

com.example.packageOne.ClassOne -> go.here.a.a:
com.example.packageOne.subPackageOne.ClassTwo -> go.here.b.a:
com.example.packageTwo.ClassThree -> go.here.c.a:

Dictionaries

R8 will provide new names by cycling through the English alphabet.
By using dictionaries, it is possible to control, to a degree, how R8 will determine the new names for classes, methods, and fields.

Rule Description
-classobfuscationdictionary <filename> Use the specified file to find new names for classes. (ProGuard docs)
-obfuscationdictionary <filename> Use the specified file to find new names for methods and fields. (ProGuard docs)
-packageobfuscationdictionary <filename> Use the specified file to find new names for packages. (ProGuard docs)

Dictionary Files

The dictionary files contain lists of unique names separated by whitespace or punctuation.
A # can be used to specify a comment.
The filename specified should be relative to the directory containing the rules file.
The names must consist of characters allowed for Java identifiers.

a1, a2, a3 #A few identifiers
class package for while do if else switch goto this null #Reserved word identifiers
#Identifiers on their own lines
q
w
e
r
t
y

Mapping Files

Map files contain direct links between the original and new names of classes, methods, and fields.

Rule Description
-applymapping <filename> Use the specified map for renaming. (ProGuard docs)
-printmapping [<filename>] Print a mapping from the original to the new names to the specified file, or to stdout if there is no file specified. (ProGuard docs) (See note)

-printmapping

Regardless of the -printmapping rule, maps will always be output to a variant specific file (e.g.build/outputs/mapping[/r8][/{flavorName}]/{buildType}/mapping.txt).
If -printmapping is configured to print to a file in a configuration that is used by more than one variant, the configured file will be overwritten to reflect whichever variant built last.

Unsupported Rules

Some ProGuard rules are unsupported by R8 and will not be honored.

The following rules will cause R8 to issue an error:

  • -microedition
  • -skipnonpubliclibraryclasses
  • includecode (modifier used with -keep* rules) (See issue)

The following rules will cause R8 to issue a warning message:

  • -optimizationpasses (enabled by proguard-android-optimize.txt)
  • -optimizations

The following rules are ignored:

  • -addconfigurationdebugging
  • -android
  • -assumenoescapingparameters
  • -assumenoexternalreturnvalues
  • -assumenoexternalsideefffects
  • -dontpreverify
  • -dontskipnonpubliclibraryclasses
  • -dontskipnonpubliclibraryclassmembers
  • -dontusemixedcaseclassnames
  • -dump
  • -forceprocessing
  • -keepparameternames
  • -mergeinterfacesaggressively
  • -outjars
  • -target
  • -useuniqueclassmembernames
  • -verbose

Not Applicable

R8 is designed for use with Android projects.
However, some rules it supports are not applicable in Android projects:

Rule Description
-keepdirectories [<filter>] Keep directory entries in the output jar or zip file. (ProGuard rules)

Troubleshooting

This section describes some issues that you might encounter when trying to use R8.
You might also find details about your issue in the Android Studio User Guide, the R8 Compatibility FAQ, or ProGuard’s Troubleshooting page.

If you cannot find a solution to your problem, or if you otherwise encounter incorrect behavior, you can report a bug with the Google team.

ProGuard appears to be running instead of R8

Make sure that you do not have android.enableR8=false in your gradle.properties file.

Note: If you are using a version of the Android Gradle Plugin prior to 3.4, you will need to set android.enableR8=true in your gradle.properties file to enable R8.

D8: Unsupported option: -skipnonpubliclibraryclasses

This rule is unsupported in R8.
Remove it from your configuration.

Custom rules don’t appear to be used

Make sure that any rule file that you want to use is properly configured in your Gradle build script with proguardFiles.
Relative paths configured in your Gradle build script should be relative to the application or library module for which you would like the rules to apply.
See the Android Studio User Guide for details.

Note: The Android Gradle Plugin will not error or warn you if it cannot locate the specified file.

The rule [some rule] uses extends but actually matches implements

R8 issues this warning if you use an extends rule to match descendents of an interface rather than implements, regardless of whether the descendents you’re trying to match are classes or interfaces.
If the specified rule is a custom rule that you have created, you can update the rule to use implements rather than extends.
However, some libraries, including Android support libraries, contain rules that will produce this warning.
Unfortunately, there is no easy way to resolve or suppress the warning in that case.