To enable app optimization, you must use libraries that are compatible with Android optimization. If a library isn't configured for Android optimization—for example, if it uses reflection without bundling associated keep rules—it might not be a good fit for an Android app. This page explains why some libraries are better suited for app optimization and provides general tips to help you choose.
Prefer codegen over reflection
Generally, you should choose libraries that use code generation (codegen) instead of reflection. With codegen, the optimizer can more easily determine what code is actually used at runtime and what code can be removed. It can be difficult to tell whether a library uses codegen or reflection, but there are some signs—see the tips for help.
For more information about codegen versus reflection, see Optimization for library authors.
General tips when choosing libraries
Use these tips to help ensure that your libraries are compatible with app optimization.
Check for optimization issues
When considering a new library, look through the library's issue tracker and online discussions to check if there are issues related to minification or configuring app optimization. If there are, you should try to look for alternatives to that library. Keep in mind the following:
- The AndroidX libraries and libraries such as Hilt work well with app optimization because they use codegen instead of reflection. When they do use reflection, they provide minimal keep rules to keep only the code that is needed.
- Serialization libraries frequently use reflection to avoid boilerplate code when instantiating or serializing objects. Instead of reflection-based approaches (such as Gson for JSON), look for libraries that use codegen to avoid these problems, for example by using Kotlin Serialization instead.
- Libraries that include package-wide keep rules should be avoided if possible. Package-wide keep rules can help resolve errors, but broad keep rules should eventually be refined to keep only the code that is needed. For more information, see Adopt optimizations incrementally.
Enable optimization after adding a new library
When you add a new library, enable optimization afterwards and check if there are errors. If there are errors, look for alternatives to that library or write keep rules. If a library isn't compatible with optimization, file a bug with that library.
Rules are additive
Be aware that keep rules are additive. This means that certain rules that a library dependency includes cannot be removed and might impact the compilation of other parts of your app. For example, if a library includes a rule to disable code optimizations, that rule disables optimizations for your entire project.
Check for use of reflection (advanced)
You might be able to tell if a library uses reflection from inspecting its code. If the library uses reflection, check that it provides associated keep rules. A library is probably using reflection if it does the following:
- Uses classes or methods from the
kotlin.reflect
orjava.lang.reflect
packages - Uses the functions
Class.forName
orclassLoader.getClass
- Reads annotations at runtime, for example if it stores an annotation value
using
val value = myClass.getAnnotation()
orval value = myMethod.getAnnotation()
and then does something withvalue
Calls methods using the method name as a string, for example:
// Calls the private `processData` API with reflection myObject.javaClass.getMethod("processData", DataType::class.java) ?.invoke(myObject, data)
Filter out bad keep rules (advanced)
You should avoid libraries with keep rules that retain code that should really be removed. But if you must use them, you can filter the rules out as shown in the following code:
// If you're using AGP 8.4 and higher
buildTypes {
release {
optimization.keepRules {
it.ignoreFrom("com.somelibrary:somelibrary")
}
}
}
// If you're using AGP 7.3-8.3
buildTypes {
release {
optimization.keepRules {
it.ignoreExternalDependencies("com.somelibrary:somelibrary")
}
}
}
Case study: Why Gson breaks with optimizations
Gson is a serialization library that often causes issues with app optimization
because it heavily uses reflection. The following code snippet shows how Gson is
typically used, which can easily cause crashes at runtime. Notice that when you
use Gson to get a list of User objects, you don't call the constructor or pass a
factory to the fromJson()
function. Constructing or consuming app-defined
classes without either of the following is a sign that a library might be using
open-ended reflection:
- App class implementing a library, or standard interface or class
- Code generation plugin like KSP
class User(val name: String)
class UserList(val users: List<User>)
// This code runs in debug mode, but crashes when optimizations are enabled
Gson().fromJson("""[{"name":"myname"}]""", User::class.java).toString()
When R8 analyzes this code and doesn't see the UserList
or User
instantiated
anywhere, it can rename fields, or remove constructors which don't seem to be
used, causing your app to crash. If you are using any other libraries in similar
ways, you should check that they won't interfere with app optimization, and if
they do, avoid them.
Note that Room and Hilt both construct app defined types, but use codegen to avoid the need for reflection.