As a library author, you should make sure that app developers can easily incorporate your library into their app while maintaining a high quality end user experience. You should ensure that your library is compatible with Android optimization without additional setup—or document that the library might be inappropriate for usage on Android.
This documentation is targeted at developers of published libraries, but might also be useful for developers of internal library modules in a large, modularized app.
If you're an app developer and want to learn about optimizing your Android app, see Enable app optimization. To learn about which libraries are appropriate to use, see Choose libraries wisely.
Use codegen over reflection
When possible, use code generation (codegen) over reflection. Codegen and reflection are both common approaches to avoid boilerplate code when programming, but codegen is more compatible with an app optimizer like R8:
- With codegen, code is analyzed and modified during the build process. Because there aren't any major modifications after compile time, the optimizer knows what code is ultimately needed and what can be safely removed.
- With reflection, code is analyzed and manipulated at runtime. Because the code isn't really finalized until it executes, the optimizer doesn't know what code can be safely removed. It'll likely remove code that is used dynamically through reflection during runtime, which causes app crashes for users.
Many modern libraries use codegen instead of reflection. See KSP for a common entrypoint, used by Room, Dagger2, and many others.
When reflection is okay
If you must use reflection, you should only reflect into either of the following:
- Specific targeted types (specific interface implementers or subclasses)
- Code using a specific runtime annotation
Using reflection in this way limits the runtime cost, and enables writing targeted consumer keep rules.
This specific and targeted form of reflection is a pattern you can see across both the Android framework (for example when inflating activities, views, and drawables) and AndroidX libraries (for example when constructing WorkManager ListenableWorkers, or RoomDatabases). By contrast, the open ended reflection of Gson isn't appropriate for usage in Android apps.
Write consumer keep rules
Libraries should package "consumer" keep rules, which use the same format as app keep rules. These rules are bundled into library artifacts (AARs or JARs) and get consumed automatically during Android app optimization when the library is used.
AAR libraries
To add consumer rules for an AAR library, use the consumerProguardFiles
option
in the Android library module's build script. For more information, see our
guidance on creating library modules.
Kotlin
android { defaultConfig { consumerProguardFiles("consumer-proguard-rules.pro") } ... }
Groovy
android { defaultConfig { consumerProguardFiles 'consumer-proguard-rules.pro' } ... }
JAR libraries
To bundle rules with your Kotlin/Java library that ships as a JAR, put your
rules file in the final JAR's META-INF/proguard/
directory, with any filename.
For example if your code in <libraryroot>/src/main/kotlin
, put a consumer
rules file at
<libraryroot>/src/main/resources/META-INF/proguard/consumer-proguard-rules.pro
and the rules will be bundled in the correct location in your output JAR.
Verify that the final JAR bundles rules correctly by checking that the rules are
in the META-INF/proguard
directory.
Support different shrinkers (advanced)
You can tailor rules to that target specific shrinkers (R8 or ProGuard), as well as specific shrinker versions. This enables your library to work optimally in projects that use new shrinker versions, while allowing existing rules to continue to be used in projects with older shrinker versions.
To specify targeted shrink rules, you need to include them at specific locations inside an AAR or JAR library, as described below.
In an AAR library:
consumer-proguard-rules.pro (legacy location)
classes.jar
└── META-INF
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
In a JAR library:
META-INF
├── proguard/<ProGuard-rules-file> (legacy location)
└── com.android.tools (targeted shrink rules location)
├── r8-from-<X>-upto-<Y>/<R8-rules-file>
└── proguard-from-<X>-upto-<Y>/<ProGuard-rules-file>
That means targeted shrink rules are stored in the META-INF/com.android.tools
directory of a JAR or in the META-INF/com.android.tools
directory inside
classes.jar
of an AAR.
Under that directory, there can be multiple directories with names in the form
of r8-from-<X>-upto-<Y>
or proguard-from-<X>-upto-<Y>
to indicate which
versions of which shrinker the rules inside the directories are written for.
Note that the -from-<X>
and -upto-<Y>
parts are optional, the <Y>
version
is exclusive, and the version ranges must be continuous.
For example, r8-upto-8.0.0, r8-from-8.0.0-upto-8.2.0
, and r8-from-8.2.0
form
a valid set of targeted shrink rules. The rules under the
r8-from-8.0.0-upto-8.2.0
directory will be used by R8 from version 8.0.0 up to
but not including version 8.2.0.
Given that information, the Android Gradle plugin selects the rules from the
matching R8 directories. If a library does not specify targeted shrink rules,
the Android Gradle plugin will select the rules from the legacy locations
(proguard.txt
for an AAR or META-INF/proguard/<ProGuard-rules-file>
for a
JAR).