blob: 5614f6f220a8c98aaaa9cb0612de7a2d0a2d027b [file] [log] [blame] [view]
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +02001# R8 FAQ
Søren Gjessece1f4272019-02-28 10:28:49 +01002
3R8 uses the same configuration specification language as ProGuard, and tries to
4be compatible with ProGuard. However as R8 has different optimizations it can be
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +02005necessary to change the configuration when switching to R8. R8 provides two
6modes, R8 compatibility mode and R8 full mode. R8 compatibility mode is default
7in Android Studio and is meant to make the transition to R8 from ProGuard easier
8by limiting the optimizations performed by R8.
Søren Gjessece1f4272019-02-28 10:28:49 +01009
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +020010## R8 full mode
11In non-compat mode, also called full mode”, R8 performs more aggressive
12optimizations, meaning additional ProGuard configuration rules may be required.
13Full mode can be enabled by adding `android.enableR8.fullMode=true` in the
14`gradle.properties` file. The main differences compared to R8 compatibility mode
15are:
16
17- The default constructor (`<init>()`) is not implicitly kept when a class is
18kept.
19- The default constructor (`<init>()`) is not implicitly kept for types which
20are only used with `ldc`, `instanceof` or `checkcast`.
21- The enclosing classes of fields or methods that are matched by a
22`-keepclassmembers` rule are not implicitly considered to be instantiated.
23Classes that are only instantiated using reflection should be kept explicitly
24with a `-keep` rule.
25- Default methods are not implicitly kept as abstract methods.
26- Attributes (such as `Signature`) and annotations are only kept for classes,
27methods and fields which are matched by keep rules even when `-keepattributes`
Morten Krogh-Jespersen04752e12022-10-11 12:55:57 +020028is specified. The weakest rule that will keep annotations and attributes is
29`-keep[classmembers],allowshrinking,allowoptimization,allowobfuscation,allowaccessmodification class-specification`
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +020030Additionally, for attributes describing a relationship such as `InnerClass` and
31`EnclosingMethod`, non-compat mode requires both endpoints being kept.
32
33# Troubleshooting
34
35The rest of this document describes known issues with libraries that use
36reflection.
Søren Gjessece1f4272019-02-28 10:28:49 +010037
38## GSON
39
40### Member in a data object is always `null`
41
42For data classes used for serialization all fields that are used in the
43serialization must be kept by the configuration. R8 can decide to replace
44instances of types that are never instantiated with `null`. So if instances of a
45given class are only created through deserialization from JSON, R8 will not see
46that class as instantiated leaving it as always `null`.
47
48If the `@SerializedName` annotation is used consistently for data classes the
49following keep rule can be used:
50
51```
52-keepclassmembers,allowobfuscation class * {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +020053 @com.google.gson.annotations.SerializedName <fields>;
Søren Gjessece1f4272019-02-28 10:28:49 +010054}
55```
56
57This will ensure that all fields annotated with `SerializedName` will be
58kept. These fields can still be renamed during obfuscation as the
59`SerializedName` annotation (not the source field name) controls the name in the
60JSON serialization.
61
62If the `@SerializedName` annotation is _not_ used the following conservative
63rule can be used for each data class:
64
65```
66-keepclassmembers class MyDataClass {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +020067 !transient <fields>;
Søren Gjessece1f4272019-02-28 10:28:49 +010068}
69```
70
71This will ensure that all fields are kept and not renamed for these
72classes. Fields with modifier `transient` are never serialized and therefore
73keeping these is not needed.
74
75### Error `java.lang.IllegalArgumentException: class <class name> declares multiple JSON fields named <name>`
76
77This can be caused by obfuscation selecting the same name for private fields in
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +020078several classes in a class hierarchy. Consider the following example:
Søren Gjessece1f4272019-02-28 10:28:49 +010079
80```
81class A {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +020082 private String fieldInA;
Søren Gjessece1f4272019-02-28 10:28:49 +010083}
84
85class B extends A {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +020086 private String fieldInB;
Søren Gjessece1f4272019-02-28 10:28:49 +010087}
88```
89
90Here R8 can choose to rename both `fieldInA` and `fieldInB` to the same name,
91e.g. `a`. This creates a conflict when GSON is used to either serialize an
92instance of class `B` to JSON or create an instance of class `B` from JSON. If
93the fields should _not_ be serialized they should be marked `transient` so that
94they will be ignored by GSON:
95
96```
97class A {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +020098 private transient String fieldInA;
Søren Gjessece1f4272019-02-28 10:28:49 +010099}
100
101class B extends A {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200102 private transient String fieldInB;
Søren Gjessece1f4272019-02-28 10:28:49 +0100103}
104```
105
106If the fields _are_ to be serialized, the annotation `SerializedName` can be
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200107used to fix the `IllegalArgumentException` together with the rule to keep fields
Søren Gjessece1f4272019-02-28 10:28:49 +0100108annotated with `SerializedName`
109
110```
111class A {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200112 @SerializedName("fieldInA")
113 private String fieldInA;
Søren Gjessece1f4272019-02-28 10:28:49 +0100114}
115
116class B extends A {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200117 @SerializedName("fieldInB")
118 private String fieldInB;
Søren Gjessece1f4272019-02-28 10:28:49 +0100119}
120```
121
122```
123-keepclassmembers,allowobfuscation class * {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200124 @com.google.gson.annotations.SerializedName <fields>;
Søren Gjessece1f4272019-02-28 10:28:49 +0100125}
126```
127
128
129Both the use of `transient` and the use of the annotation `SerializedName` allow
130the fields to be renamed by R8 to the same name, but GSON serialization will
131work as expected.
132
Morten Krogh-Jespersen72d73102022-09-28 13:51:53 +0200133### GSON
Christoffer Quist Adamsend96a2cf2019-03-07 09:58:51 +0100134
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200135GSON uses type tokens to serialize and deserialize generic types.
136
137```TypeToken<List<String>> listOfStrings = new TypeToken<List<String>>() {};```
138
139The anonymous class will have a generic signature argument of `List<String>` to
140the super type `TypeToken` that is reflective read for serialization. It
141is therefore necessary to keep both the `Signature` attribute, the
Morten Krogh-Jespersen72d73102022-09-28 13:51:53 +0200142`com.google.gson.reflect.TypeToken` class and all sub-types.
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200143
144```
145-keepattributes Signature
146-keep class com.google.gson.reflect.TypeToken { *; }
147-keep class * extends com.google.gson.reflect.TypeToken
148```
Christoffer Quist Adamsend96a2cf2019-03-07 09:58:51 +0100149
Morten Krogh-Jespersen72d73102022-09-28 13:51:53 +0200150This is also needed for R8 in compat mode since multiple optimizations will
151remove the generic signature such as class merging and argument removal.
152
Christoffer Quist Adamsend96a2cf2019-03-07 09:58:51 +0100153## Retrofit
154
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200155### Objects instantiated with Retrofit's `create()` method are always replaced with `null`
Christoffer Quist Adamsend96a2cf2019-03-07 09:58:51 +0100156
157This happens because Retrofit uses reflection to return an object that
158implements a given interface. The issue can be resolved by using the most recent
159keep rules from the Retrofit library.
160
161See also https://github.com/square/retrofit/issues/3005 ("Insufficient keep
162rules for R8 in full mode").
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200163
164### Kotlin suspend functions and generic signatures
165
166For Kotlin suspend functions the generic signature is reflectively read.
167Therefore keeping the `Signature` attribute is necessary. Full mode only keeps
168the signature for kept classes thus a keep on `kotlin.coroutines.Continuation` in
169addition to a keep on the api classes is needed:
170```
171-keepattributes Signature
172-keep class kotlin.coroutines.Continuation
173```
174
175This should be included automatically from versions built after the pull-request
176https://github.com/square/retrofit/pull/3563
177