At a high level, a keep rule specifies a class (or subclass or implementation), and then members—methods, constructors, or fields—within that class to preserve.
The general syntax for a keep rule is as follows, however
certain keep options don't accept the optional keep_option_modfier
.
-<keep_option>[,<keep_option_modifier_1>,<keep_option_modifier_2>,...] <class_specification>
The following is an example of a keep rule that uses keepclassmembers
as the
keep option, allowoptimization
as the modifier, and keeps
someSpecificMethod()
from com.example.MyClass
:
-keepclassmembers,allowoptimization class com.example.MyClass {
void someSpecificMethod();
}
Keep option
The keep option is the first part of your keep rule. It specifies what aspects
of a class to preserve. There are six different keep options, namely keep
,
keepclassmembers
, keepclasseswithmembers
, keepnames
,
keepclassmembernames
, keepclasseswithmembernames
.
The following table describes these keep options:
Keep option | Description |
---|---|
keepclassmembers |
Preserves specified members only if the class exists after optimization. |
keep |
Preserves specified classes and the specified members (fields and methods), preventing them from being optimized. Note: keep should generally only be used with keep option modifiers because keep by itself prevents optimizations of any kind from happening on matched classes. |
keepclasseswithmembers |
Preserves a class and its specified members only if the class has all of the members from the class specification. |
keepclassmembernames |
Prevents the renaming of specified class members, but does not prevent the class or its members from being removed. Note: The meaning of this option is often misunderstood; consider using the equivalent -keepclassmembers,allowshrinking instead. |
keepnames |
Prevents the renaming of classes and their members, but it does not prevent them from being removed entirely if they are deemed unused. Note: The meaning of this option is often misunderstood; consider using the equivalent -keep,allowshrinking instead. |
keepclasseswithmembernames |
Prevents the renaming of classes and their specified members, but only if the members exist in the final code. It does not prevent the removal of code. Note: The meaning of this option is often misunderstood; consider using the equivalent -keepclasseswithmembers,allowshrinking instead. |
Choose the right keep option
Picking the right keep option is crucial in determining the right optimization for your app. Certain keep options shrink code, a process by which unreferenced code is removed, while others obfuscate, or rename, code. The following table indicates the actions of the various keep options:
Keep option | Shrinks classes | Obfuscates classes | Shrinks members | Obfuscates members |
---|---|---|---|---|
keep |
||||
keepclassmembers |
||||
keepclasseswithmembers |
||||
keepnames |
||||
keepclassmembernames |
||||
keepclasseswithmembernames |
Keep option modifier
A keep option modifier is used to control the scope and behavior of a keep rule. You can add 0 or more keep option modifiers to your keep rule.
The possible values for a keep option modifier are described in the following table:
Value | Description |
---|---|
allowoptimization |
Allows optimization of the specified elements. However, the specified elements are not renamed or removed. |
allowobfucastion |
Allows renaming of the specified elements. However, the elements are not be removed or otherwise optimized. |
allowshrinking |
Allows removal of the specified elements if R8 finds no references to them. However, the elements are not renamed or otherwise optimized. |
includedescriptorclasses |
Instructs R8 to keep all classes that appear in the descriptors of the methods (parameter types and return types) and fields (field types) being kept. |
allowaccessmodification |
Allows R8 to change (typically widen) the access modifiers (public , private , protected ) of classes, methods, and fields during the optimization process. |
allowrepackage |
Allows R8 to move classes into different packages, including the default (root) package. |
Class specification
You must specify a class, superclass, or implemented interface as part of a keep
rule. All classes, including classes from the java.lang
namespace like
java.lang.String
, must be specified using their fully qualified Java name. To
understand the names that should be used, inspect the bytecode using the tools
described in Get generated Java names.
The following example shows how you should specify the MaterialButton
class:
- Correct:
com.google.android.material.button.MaterialButton
- Incorrect:
MaterialButton
Class specifications also specify the members within a class that
should be kept. The following rule keeps the MaterialButton
class and all its
members:
-keep class com.google.android.material.button.MaterialButton { *; }
Subclasses and implementations
To target a subclass or class that implements an interface, use extend
and
implements
, respectively.
For example, if you have class Bar
with subclass Foo
as follows:
class Foo : Bar()
The following keep rule preserves all the subclasses of Bar
. Note that the
keep rule doesn't include the superclass Bar
itself.
-keep class * extends Bar
If you have class Foo
that implements Bar
:
class Foo : Bar
The following keep rule preserves all classes that implement Bar
. Note that
the keep rule doesn't include the interface Bar
itself.
-keep class * implements Bar
Access modifier
You can specify access modifiers such as public
, private
, static
, and
final
to make your keep rules more precise.
For example, the following rule keeps all the public
classes within the api
package and its sub-packages, and all the public and protected members in these
classes.
-keep public class com.example.api.** { public protected *; }
You can also use modifiers for the members within a class. For example, the
following rule keeps only the public static
methods of a Utils
class:
-keep class com.example.Utils {
public static void *(...);
}
Kotlin-specific modifiers
R8 doesn't support Kotlin-specific modifiers such as internal
and suspend
.
Use the following guidelines to keep such fields.
To keep an
internal
class, method or field, treat it as public. For example, consider the following Kotlin source:package com.example internal class ImportantInternalClass { internal f: Int internal fun m() {} }
The
internal
classes, methods and fields arepublic
in the.class
files produced by the Kotlin compiler, so you must use thepublic
keyword as shown in the following example:-keepclassmembers public class com.example.ImportantInternalClass { public int f; public void m(); }
When a
suspend
member is compiled, match its compiled signature in the keep rule.For example, if you have the function
fetchUser
defined as shown in the following snippet:suspend fun fetchUser(id: String): User
When compiled, its signature in the bytecode looks like the following:
public final Object fetchUser(String id, Continuation<? super User> continuation);
To write a keep rule for this function, you must match this compiled signature, or use
...
.An example of using the compiled signature is as follows:
-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(java.lang.String, kotlin.coroutines.Continuation); }
An example using
...
is as follows:-keepclassmembers class com.example.repository.UserRepository { public java.lang.Object fetchUser(...); }
Member specification
The class specification optionally includes the class members to be preserved. If you specify one or more members for a class, the rule only applies to those members.
For example, to preserve a specific class and all its members, use the following:
-keep class com.myapp.MyClass { *; }
To preserve only the class and not its members, use the following:
-keep class com.myapp.MyClass
Most of the time, you'll want to specify some members. For example, the
following example keeps the public field text
and the public method
updateText()
within the class MyClass
.
-keep class com.myapp.MyClass {
public java.lang.String text;
public void updateText(java.lang.String);
}
To keep all public fields and public methods, see the following example:
-keep public class com.example.api.ApiClient {
public *;
}
Methods
The syntax for specifying a method in the member specification for a keep rule is as follows:
[<access_modifier>] [<return_type>] <method_name>(<parameter_types>);
For example, the following keep rule keeps a public method called setLabel()
that returns void and takes a String
.
-keep class com.example.MyView {
public void setLabel(java.lang.String);
}
You can use <methods>
as a shortcut to match all methods in a class as
follows:
-keep class com.example.MyView {
<methods>;
}
To learn more about how to specify types for return types and parameter types, see Types.
Constructors
To specify a constructor, use <init>
. The syntax for specifying a constructor
in the member specification for a keep rule is as follows:
[<access_modifier>] <init>(parameter_types);
For example, the following keep rule keeps a custom View
constructor that
takes a Context
and an AttributeSet
.
-keep class com.example.ui.MyCustomView {
public <init>(android.content.Context, android.util.AttributeSet);
}
To keep all public constructors, use the following example as a reference:
-keep class com.example.ui.MyCustomView {
public <init>(...);
}
Fields
The syntax for specifying a field in the member specification for a keep rule is as follows:
[<access_modifier>...] [<type>] <field_name>;
For example, the following keep rule keeps a private string field called
userId
and a public static integer field called STATUS_ACTIVE
:
-keep class com.example.models.User {
private java.lang.String userId;
public static int STATUS_ACTIVE;
}
You can use <fields>
as a shortcut to match all the fields in a class as
follows:
-keep class com.example.models.User {
<fields>;
}
Package-level functions
To reference a Kotlin function that's defined outside of a class (commonly
called top level functions), make sure to use the generated Java name for
the class implicitly added by the Kotlin compiler. The class name is the Kotlin
filename with Kt
appended. For example, if you have a Kotlin file called
MyClass.kt
defined as follows:
package com.example.myapp.utils
// A top-level function not inside a class
fun isEmailValid(email: String): Boolean {
return email.contains("@")
}
To write a keep rule for the isEmailValid
function, the class specification
needs to target the generated class MyClassKt
:
-keep class com.example.myapp.utils.MyClassKt {
public static boolean isEmailValid(java.lang.String);
}
Types
This section describes how to specify return types, parameter types, and field types in keep rule member specifications. Remember to use the generated Java names to specify types if they're different from the Kotlin source code.
Primitive types
To specify a primitive type, use its Java keyword. R8 recognizes the following
primitive types: boolean
, byte
, short
, char
, int
, long
, float
,
double
.
An example rule with a primitive type is as follows:
# Keeps a method that takes an int and a float as parameters.
-keepclassmembers class com.example.Calculator {
public void setValues(int, float);
}
Generic types
During compilation, the Kotlin/Java compiler erases generic type information, so when you write keep rules that involve generic types you must target the compiled representation of your code, and not the original source code. To learn more about how generic types are changed, see Type erasure.
For example, if you have the following code with an unbounded generic type
defined in Box.kt
:
package com.myapp.data
class Box<T>(val item: T) {
fun getItem(): T {
return item
}
}
After type erasure, the T
is replaced by Object
. To keep the class
constructor and method, your rule must use java.lang.Object
in place of the
generic T
.
An example keep rule would be as follows:
# Keep the constructor and methods of the Box class.
-keep class com.myapp.data.Box {
public init(java.lang.Object);
public java.lang.Object getItem();
}
If you have the following code with a bounded generic type in NumberBox.kt
:
package com.myapp.data
// T is constrained to be a subtype of Number
class NumberBox<T : Number>(val number: T)
In this case, type erasure replaces T
with its bound, java.lang.Number
.
An example keep rule would be as follows:
-keep class com.myapp.data.NumberBox {
public init(java.lang.Number);
}
When using app-specific generic types as a base class, it's necessary to include keep rules for the base classes as well.
For example, for the following code:
package com.myapp.data
data class UnpackOptions(val useHighPriority: Boolean)
// The generic Box class with UnpackOptions as the bounded type
class Box<T: UnpackOptions>(val item: T) {
}
You can use a keep rule with includedescriptorclasses
to preserve both the
UnpackOptions
class and Box
class method with a single rule as follows:
-keep,includedescriptorclasses class com.myapp.data.Box {
public <init>(com.myapp.data.UnpackOptions);
}
To keep a specific function that processes a list of objects, you need to write
a rule that precisely matches the function's signature. Note that because generic
types are erased, a parameter like List<Product>
is seen as
java.util.List
.
For example, if you have a utility class with a function that processes a list
of Product
objects as follows:
package com.myapp.utils
import com.myapp.data.Product
import android.util.Log
class DataProcessor {
// This is the function we want to keep
fun processProducts(products: List<Product>) {
Log.d("DataProcessor", "Processing ${products.size} products.")
// Business logic ...
}
}
// The data class used in the list (from the previous example)
package com.myapp.data
data class Product(val id: String, val name: String)
You could use the following keep rule to protect only the processProducts
function:
-keep class com.myapp.utils.DataProcessor {
public void processProducts(java.util.List);
}
Array types
Specify an array type by appending []
to the component type for each dimension
of the array. This applies to both class types and primitive types.
- One-dimensional class array:
java.lang.String[]
- Two-dimensional primitive array:
int[][]
For example, if you have the following code:
package com.example.data
class ImageProcessor {
fun process(): ByteArray {
// process image to return a byte array
}
}
You could use the following keep rule:
# Keeps a method that returns a byte array.
-keepclassmembers class com.example.data.ImageProcessor {
public byte[] process();
}
Wildcards
The following table shows how to use wildcards to apply keep rules to multiple classes or members that match a certain pattern.
Wildcard | Applies to classes or members | Description |
---|---|---|
** | Both | Most commonly used. Matches any type name, including any number of package separators. This is useful for matching all classes within a package and its sub-packages. |
* | Both | For class specifications, matches any part of a type name that doesn't contain package separators (. ) For member specifications, matches any method or field name. When used by itself, it is also an alias for ** . |
? | Both | Matches any single character in a class or member name. |
*** | Members | Matches any type, including primitive types (like int ), class types (like java.lang.String ), and array types of any dimension (like byte[][] ). |
... | Members | Matches any list of parameters for a method. |
% | Members | Matches any primitive type (such as `int`, `float`, `boolean`, or others). |
Here are some examples of how to use the special wildcards:
If you have multiple methods with the same name that take different primitive types as inputs, you can use
%
to write a keep rule that keeps them all. For example, thisDataStore
class has multiplesetValue
methods:class DataStore { fun setValue(key: String, value: Int) { ... } fun setValue(key: String, value: Boolean) { ... } fun setValue(key: String, value: Float) { ... } }
The following keep rule keeps all the methods:
-keep class com.example.DataStore { public void setValue(java.lang.String, %); }
If you have multiple classes with names that vary by one character, use
?
to write a keep rule that keeps them all. For example, if you have the following classes:com.example.models.UserV1 {...} com.example.models.UserV2 {...} com.example.models.UserV3 {...}
The following keep rule keeps all of the classes:
-keep class com.example.models.UserV?
To match the classes
Example
andAnotherExample
(if they were root-level classes), but notcom.foo.Example
, use the following keep rule:-keep class *Example
If you use * by itself, it acts as an alias for **. For example, the following keep rules are equivalent:
-keepclasseswithmembers class * { public static void main(java.lang.String[];) } -keepclasseswithmembers class ** { public static void main(java.lang.String[];) }
Inspect generated Java names
When writing keep rules, you must specify classes and other reference types using their names after they're compiled into Java bytecode (see Class specification and Types for examples). To check what the generated Java names for your code are, use either of the following tools in Android Studio:
- APK Analyzer
- With the Kotlin source file open, inspect the bytecode by going to Tools > Kotlin > Show Kotlin Bytecode > Decompile.