Add keep rules

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 are public in the .class files produced by the Kotlin compiler, so you must use the public 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, this DataStore class has multiple setValue 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 and AnotherExample (if they were root-level classes), but not com.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.