blob: cb330771f9d826fbd4645254436ec3e8b78cc413 [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
Ian Zerny42b4baf2023-04-26 11:55:47 +020033## Stack traces and retracing
34When compiling with R8, a mapping file can be produced to support mapping stack
35traces of the R8 optimized program back to the original source information.
36
37Starting with R8 version 8.2, the compiler will retain full original source file
38names in the mapping information without the need to specify
39`-keepattributes SourceFile`.
40
41In addition, builds that target API level 26 or above (and don't specify custom
42source-file information) will also retain mapping information for all lines
43without the need to specify `-keepattributes LineNumberTable`.
44
45More information on R8 mapping files can be found in the [retrace doc](doc/retrace.md).
46
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +020047# Troubleshooting
48
49The rest of this document describes known issues with libraries that use
50reflection.
Søren Gjessece1f4272019-02-28 10:28:49 +010051
52## GSON
53
54### Member in a data object is always `null`
55
56For data classes used for serialization all fields that are used in the
57serialization must be kept by the configuration. R8 can decide to replace
58instances of types that are never instantiated with `null`. So if instances of a
59given class are only created through deserialization from JSON, R8 will not see
60that class as instantiated leaving it as always `null`.
61
62If the `@SerializedName` annotation is used consistently for data classes the
63following keep rule can be used:
64
65```
66-keepclassmembers,allowobfuscation class * {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +020067 @com.google.gson.annotations.SerializedName <fields>;
Søren Gjessece1f4272019-02-28 10:28:49 +010068}
69```
70
71This will ensure that all fields annotated with `SerializedName` will be
72kept. These fields can still be renamed during obfuscation as the
73`SerializedName` annotation (not the source field name) controls the name in the
74JSON serialization.
75
76If the `@SerializedName` annotation is _not_ used the following conservative
77rule can be used for each data class:
78
79```
80-keepclassmembers class MyDataClass {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +020081 !transient <fields>;
Søren Gjessece1f4272019-02-28 10:28:49 +010082}
83```
84
85This will ensure that all fields are kept and not renamed for these
86classes. Fields with modifier `transient` are never serialized and therefore
87keeping these is not needed.
88
89### Error `java.lang.IllegalArgumentException: class <class name> declares multiple JSON fields named <name>`
90
91This can be caused by obfuscation selecting the same name for private fields in
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +020092several classes in a class hierarchy. Consider the following example:
Søren Gjessece1f4272019-02-28 10:28:49 +010093
94```
95class A {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +020096 private String fieldInA;
Søren Gjessece1f4272019-02-28 10:28:49 +010097}
98
99class B extends A {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200100 private String fieldInB;
Søren Gjessece1f4272019-02-28 10:28:49 +0100101}
102```
103
104Here R8 can choose to rename both `fieldInA` and `fieldInB` to the same name,
105e.g. `a`. This creates a conflict when GSON is used to either serialize an
106instance of class `B` to JSON or create an instance of class `B` from JSON. If
107the fields should _not_ be serialized they should be marked `transient` so that
108they will be ignored by GSON:
109
110```
111class A {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200112 private transient String fieldInA;
Søren Gjessece1f4272019-02-28 10:28:49 +0100113}
114
115class B extends A {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200116 private transient String fieldInB;
Søren Gjessece1f4272019-02-28 10:28:49 +0100117}
118```
119
120If the fields _are_ to be serialized, the annotation `SerializedName` can be
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200121used to fix the `IllegalArgumentException` together with the rule to keep fields
Søren Gjessece1f4272019-02-28 10:28:49 +0100122annotated with `SerializedName`
123
124```
125class A {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200126 @SerializedName("fieldInA")
127 private String fieldInA;
Søren Gjessece1f4272019-02-28 10:28:49 +0100128}
129
130class B extends A {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200131 @SerializedName("fieldInB")
132 private String fieldInB;
Søren Gjessece1f4272019-02-28 10:28:49 +0100133}
134```
135
136```
137-keepclassmembers,allowobfuscation class * {
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200138 @com.google.gson.annotations.SerializedName <fields>;
Søren Gjessece1f4272019-02-28 10:28:49 +0100139}
140```
141
142
143Both the use of `transient` and the use of the annotation `SerializedName` allow
144the fields to be renamed by R8 to the same name, but GSON serialization will
145work as expected.
146
Morten Krogh-Jespersen72d73102022-09-28 13:51:53 +0200147### GSON
Christoffer Quist Adamsend96a2cf2019-03-07 09:58:51 +0100148
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200149GSON uses type tokens to serialize and deserialize generic types.
150
151```TypeToken<List<String>> listOfStrings = new TypeToken<List<String>>() {};```
152
153The anonymous class will have a generic signature argument of `List<String>` to
154the super type `TypeToken` that is reflective read for serialization. It
155is therefore necessary to keep both the `Signature` attribute, the
Morten Krogh-Jespersen72d73102022-09-28 13:51:53 +0200156`com.google.gson.reflect.TypeToken` class and all sub-types.
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200157
158```
159-keepattributes Signature
160-keep class com.google.gson.reflect.TypeToken { *; }
161-keep class * extends com.google.gson.reflect.TypeToken
162```
Christoffer Quist Adamsend96a2cf2019-03-07 09:58:51 +0100163
Morten Krogh-Jespersen72d73102022-09-28 13:51:53 +0200164This is also needed for R8 in compat mode since multiple optimizations will
165remove the generic signature such as class merging and argument removal.
166
Christoffer Quist Adamsend96a2cf2019-03-07 09:58:51 +0100167## Retrofit
168
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200169### Objects instantiated with Retrofit's `create()` method are always replaced with `null`
Christoffer Quist Adamsend96a2cf2019-03-07 09:58:51 +0100170
171This happens because Retrofit uses reflection to return an object that
172implements a given interface. The issue can be resolved by using the most recent
173keep rules from the Retrofit library.
174
175See also https://github.com/square/retrofit/issues/3005 ("Insufficient keep
176rules for R8 in full mode").
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200177
Morten Krogh-Jespersen573f37b2023-05-03 10:42:23 +0200178### Return type must be parameterized
179
180Consider a service as the following:
181```
182interface Api {
183
184 @GET("<uri>")
185 fun getData(): Observable<Data>
186
187}
188```
189
190Retrofit instantiate a return type by inspecting the generic signature, here
191`Observable` from `io.reactivex.rxjava3.core`. Those classes are not guarded by
192keep rules prior to https://github.com/square/retrofit/pull/3886 so one has to
193manually add keep rules to prevent R8 in full mode stripping the generic
194signature. The proposed rule in https://github.com/square/retrofit/pull/3886 is:
195```
196-if interface * { @retrofit2.http.* public *** *(...); }
197-keep,allowoptimization,allowshrinking,allowobfuscation class <3>
198```
199After https://github.com/square/retrofit/pull/3886 is merged, the above rule
200is automatically included in your build. You can add the rule to your build
201until then.
202
203Note, the `Data` class probably also needs to be kept if used since this is
204also constructed reflectively.
205
Morten Krogh-Jespersen47229a42021-06-02 11:47:56 +0200206### Kotlin suspend functions and generic signatures
207
208For Kotlin suspend functions the generic signature is reflectively read.
209Therefore keeping the `Signature` attribute is necessary. Full mode only keeps
210the signature for kept classes thus a keep on `kotlin.coroutines.Continuation` in
211addition to a keep on the api classes is needed:
212```
213-keepattributes Signature
214-keep class kotlin.coroutines.Continuation
215```
216
217This should be included automatically from versions built after the pull-request
218https://github.com/square/retrofit/pull/3563
219