R8 uses the same configuration specification language as ProGuard, and tries to be compatible with ProGuard. However as R8 has different optimizations it can be necessary to change the configuration when switching to R8. R8 provides two modes, R8 compatibility mode and R8 full mode. R8 compatibility mode is default in Android Studio and is meant to make the transition to R8 from ProGuard easier by limiting the optimizations performed by R8.
In non-compat mode, also called “full mode”, R8 performs more aggressive
optimizations, meaning additional ProGuard configuration rules may be required.
Full mode can be enabled by adding android.enableR8.fullMode=true
in the
gradle.properties
file. The main differences compared to R8 compatibility mode
are:
- The default constructor (
<init>()
) is not implicitly kept when a class is kept. - The default constructor (
<init>()
) is not implicitly kept for types which are only used withldc
,instanceof
orcheckcast
. - The enclosing classes of fields or methods that are matched by a
-keepclassmembers
rule are not implicitly considered to be instantiated. Classes that are only instantiated using reflection should be kept explicitly with a-keep
rule. - Default methods are not implicitly kept as abstract methods.
- Attributes (such as
Signature
) and annotations are only kept for classes, methods and fields which are matched by keep rules even when-keepattributes
is specified. The weakest rule that will keep annotations and attributes is-keep[classmembers],allowshrinking,allowoptimization,allowobfuscation,allowaccessmodification class-specification
Additionally, for attributes describing a relationship such asInnerClass
andEnclosingMethod
, non-compat mode requires both endpoints being kept.
When compiling with R8, a mapping file can be produced to support mapping stack traces of the R8 optimized program back to the original source information.
Starting with R8 version 8.2, the compiler will retain full original source file
names in the mapping information without the need to specify
-keepattributes SourceFile
.
In addition, builds that target API level 26 or above (and don't specify custom
source-file information) will also retain mapping information for all lines
without the need to specify -keepattributes LineNumberTable
.
More information on R8 mapping files can be found in the retrace doc.
The rest of this document describes known issues with libraries that use reflection.
For data classes used for serialization all fields that are used in the
serialization must be kept by the configuration. R8 can decide to replace
instances of types that are never instantiated with null
. So if instances of a
given class are only created through deserialization from JSON, R8 will not see
that class as instantiated leaving it as always null
.
If the @SerializedName
annotation is used consistently for data classes the
following keep rule can be used:
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
This will ensure that all fields annotated with SerializedName
will be
kept. These fields can still be renamed during obfuscation as the
SerializedName
annotation (not the source field name) controls the name in the
JSON serialization.
If the @SerializedName
annotation is not used the following conservative
rule can be used for each data class:
-keepclassmembers class MyDataClass {
!transient <fields>;
}
This will ensure that all fields are kept and not renamed for these
classes. Fields with modifier transient
are never serialized and therefore
keeping these is not needed.
Error java.lang.IllegalArgumentException: class <class name> declares multiple JSON fields named <name>
This can be caused by obfuscation selecting the same name for private fields in several classes in a class hierarchy. Consider the following example:
class A {
private String fieldInA;
}
class B extends A {
private String fieldInB;
}
Here R8 can choose to rename both fieldInA
and fieldInB
to the same name,
e.g. a
. This creates a conflict when GSON is used to either serialize an
instance of class B
to JSON or create an instance of class B
from JSON. If
the fields should not be serialized they should be marked transient
so that
they will be ignored by GSON:
class A {
private transient String fieldInA;
}
class B extends A {
private transient String fieldInB;
}
If the fields are to be serialized, the annotation SerializedName
can be
used to fix the IllegalArgumentException
together with the rule to keep fields
annotated with SerializedName
class A {
@SerializedName("fieldInA")
private String fieldInA;
}
class B extends A {
@SerializedName("fieldInB")
private String fieldInB;
}
-keepclassmembers,allowobfuscation class * {
@com.google.gson.annotations.SerializedName <fields>;
}
Both the use of transient
and the use of the annotation SerializedName
allow
the fields to be renamed by R8 to the same name, but GSON serialization will
work as expected.
GSON uses type tokens to serialize and deserialize generic types.
TypeToken<List<String>> listOfStrings = new TypeToken<List<String>>() {};
The anonymous class will have a generic signature argument of List<String>
to
the super type TypeToken
that is reflective read for serialization. It
is therefore necessary to keep both the Signature
attribute, the
com.google.gson.reflect.TypeToken
class and all sub-types.
-keepattributes Signature
-keep class com.google.gson.reflect.TypeToken { *; }
-keep class * extends com.google.gson.reflect.TypeToken
This is also needed for R8 in compat mode since multiple optimizations will remove the generic signature such as class merging and argument removal.
This happens because Retrofit uses reflection to return an object that implements a given interface. The issue can be resolved by using the most recent keep rules from the Retrofit library.
See also square/retrofit#3005 ("Insufficient keep rules for R8 in full mode").
Consider a service as the following:
interface Api {
@GET("<uri>")
fun getData(): Observable<Data>
}
Retrofit instantiate a return type by inspecting the generic signature, here
Observable
from io.reactivex.rxjava3.core
. Those classes are not guarded by
keep rules prior to square/retrofit#3886 so one has to
manually add keep rules to prevent R8 in full mode stripping the generic
signature. The proposed rule in square/retrofit#3886 is:
-if interface * { @retrofit2.http.* public *** *(...); }
-keep,allowoptimization,allowshrinking,allowobfuscation class <3>
After square/retrofit#3886 is merged, the above rule is automatically included in your build. You can add the rule to your build until then.
Note, the Data
class probably also needs to be kept if used since this is
also constructed reflectively.
For Kotlin suspend functions the generic signature is reflectively read.
Therefore keeping the Signature
attribute is necessary. Full mode only keeps
the signature for kept classes thus a keep on kotlin.coroutines.Continuation
in
addition to a keep on the api classes is needed:
-keepattributes Signature
-keep class kotlin.coroutines.Continuation
This should be included automatically from versions built after the pull-request square/retrofit#3563