New: January 4th, 2016: Version 2.0 Alpha 4 of the Gradle plugin contains a number of fixes for the resource shrinker; in addition to the various bug fixes, the resource shrinker now emits a diagnostic file in the same folder as ProGuard's diagnostic output files, named "resources.txt", which contains information to help diagnose resource shrinking issues.
New: September 4th, 2015: Version 1.4.0 (beta1 and later) of the Gradle plugin now works around crashes that could result from resource shrinking on some specific handsets where modified versions of the framework appeared to be crawling through available resources in the app. For more details, see issue 79325.
The Gradle build system for Android supports "resource shrinking": the automatic removal of resources that are unused, at build time, in the packaged app. In addition to removing resources in your project that are not actually needed at runtime, this also removes resources from libraries you are depending on if they are not actually needed by your application. For example, your application is using Google Play Services to for example access Google Drive functionality, and you are not currently using Google Sign In, then this would remove the various drawable assets for the Sign In buttons.
Note that resource shrinking only works in conjunction with code shrinking (such as ProGuard). That's how it can remove unused resources from libraries; normally, all resources in a library are used, and it is only when we remove unused code that it becomes apparent which resources are referenced from the remaining code.
To enable resource shrinking, update your build type as follows:
android {
...
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
Again, you have to enable minifyEnabled
in order to turn on code shrinking, and then shrinkResources
to turn on resource shrinking. If you have not already been using minifyEnabled
, make sure you get that working before also adding shrinkResources
, since you may have to edit your proguard-rules.pro
file to make sure any methods you access with reflection etc are listed as keep rules in that file.
When you enable shrinkResources
, building your app should display output like the following during the build:
...
:android:shrinkDebugResources
Removed unused resources: Binary resource data reduced from 2570KB to 1711KB: Removed 33%
:android:validateDebugSigning
...
If you want to see which resources are actually removed, you can supply the --info
flag to the Gradle command, which will cause it to display a lot of extra information; if you look for the string "Skipped unused resource" you'll see output like the following:
$ ./gradlew clean assembleDebug --info | grep "Skipped unused resource"
Skipped unused resource res/anim/abc_fade_in.xml: 396 bytes
Skipped unused resource res/anim/abc_fade_out.xml: 396 bytes
Skipped unused resource res/anim/abc_slide_in_bottom.xml: 400 bytes
Skipped unused resource res/anim/abc_slide_in_top.xml: 400 bytes
Skipped unused resource res/anim/abc_slide_out_bottom.xml: 400 bytes
Skipped unused resource res/anim/abc_slide_out_top.xml: 400 bytes
Skipped unused resource res/color/rating_bar_label.xml: 472 bytes
Skipped unused resource res/drawable-xhdpi-v4/big.png: 866901 bytes
Skipped unused resource res/drawable-xhdpi-v4/ic_action_add_schedule.png: 282 bytes
Skipped unused resource res/drawable-xhdpi-v4/ic_action_remove_schedule.png: 368 bytes
Skipped unused resource res/drawable-xhdpi-v4/ic_livestream_pause.png: 1694 bytes
Skipped unused resource res/drawable-xhdpi-v4/ic_livestream_play.png: 2141 bytes
Skipped unused resource res/drawable-xhdpi-v4/ic_media_route_on_holo_light.png: 1594 bytes
Skipped unused resource res/drawable-xxhdpi-v4/actionbar_icon.png: 2002 bytes
Skipped unused resource res/drawable-xxhdpi-v4/ic_action_overflow.png: 330 bytes
Skipped unused resource res/drawable-xxhdpi-v4/ic_action_play_dark.png: 331 bytes
Skipped unused resource res/drawable/photo_banner_scrim.xml: 620 bytes
Skipped unused resource res/drawable/session_detail_photo_gradient.xml: 620 bytes
Skipped unused resource res/drawable/transparent_background_pattern.xml: 436 bytes
Skipped unused resource res/layout/activity_letterboxed_when_large.xml: 360 bytes
Skipped unused resource res/menu/sessions_context.xml: 1088 bytes
Skipped unused resource res/raw/keep.xml: 262 bytes
Skipped unused resource res/transition-v21/shared_element.xml: 1008 bytes
Skipped unused resource res/transition-v21/window_enter_exit.xml: 108 bytes
NOTE: As of Gradle 2.0 Alpha 4, you don't have to supply --info when running the build; the plugin will write out a diagnostic file named resources.txt ( module/build/outputs/mapping/release/resources.txt ) which contains a lot of details about the resource shrinking process, explaining why each resource was retained or removed, etc.
Keeping Resources
You can tell the build system about resources you want to keep with the special tools:keep
attribute, similar to how ProGuard configuration files can list classes and methods to keep. It doesn't matter which XML resource file you place this in, but a good practice is to keep it in a file such as res/raw/keep.xml
(and don't worry; unless you reference this resource as R.raw.keep from your .java source files, this resource will be removed along with the other unused resources from the packaged app!).
The value of the keep attribute can be a comma separated list of resource references to keep, and they can also use the asterisk character as wildcards. Example:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:keep="@layout/l_used*_c,@layout/l_used_a,@layout/l_used_b*"/>
You can also specify tools:discard to deliberately remove resources that were kept:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:shrinkMode="safe"
tools:discard="@layout/unused2" />
Strict Versus Safe
Normally, the build system can accurately determine whether a resource is used or not. However,
if your application makes a call to Resources#getIdentifier
(or if any of your libraries do that -- and note that the appcompat library does), then that means that the app can be looking up resources names on the fly, based on Strings that it computes dynamically. When the build system sees that call, it tries to be more defensive. That's because your code could contain something like this: String name = String.format("img_%1d", angle + 1);
res = getResources().getIdentifier(name, "drawable", getPackageName());
so based on the above code fragment the build system will deliberately mark all resources with the prefix
img_
as potentially used (and therefore not eligible for shrinking).
It's possible that your code will contain many strings that are overly broad. You can optionally turn off this "better safe than sorry" handling, and ask for the resource shrinker to only consider resources referenced if it's certain. In that case it will be up to you to manually keep resources that you are referencing at runtime. This is similar to how code shrinking already works; you have to provide a proguard configuration file where any classes referenced by reflection are explicitly kept.
To turn off the safety checks, set the shrinkMode to "strict" as in the following keep.xml file:
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
tools:shrinkMode="strict" />
Diagnostics
To track down problems with resource shrinking (such as "why did resource X get removed?" or "why didn't resource Y get removed?"), you can use the --debug
flag to the gradle command. This will cause a lot of diagnostic output to be printed, so you'll probably want to send the output to a file:
$ ./gradlew assembleDebug --info > /tmp/build-output.txt
For example, let's say I want to know why @drawable/ic_plus_anim_016 is still in my APK:
$ cat /tmp/build-output.txt | grep drawable/ic_plus_anim_016 | grep reachable
16:25:48.052 [QUIET] [system.out] @drawable/ic_plus_anim_016 : reachable=true
In the build output resource reference graph, I find that it's referenced from a different resource:
16:25:48.005 [QUIET] [system.out] @drawable/add_schedule_fab_icon_anim : reachable=true
16:25:48.009 [QUIET] [system.out] @drawable/ic_plus_anim_016
Therefore, I now need to know why @drawable/add_schedule_fab_icon_anim
is reachable -- and if I search upwards I find that that resource is listed under "The root reachable resources are:
". This means that there was a code reference to add_schedule_fab_icon_anim
(e.g. its R.drawable
id was found in the reachable code.).
If we are not using strict checking, resource id's can be marked as reachable if there are string constants which look like they may be used to construct resource names for resources loaded dynamically. In that case, if you search the build output for the resource name you may find a message like this:
10:32:50.590 [QUIET] [system.out] Marking drawable:ic_plus_anim_016:2130837506 used because it format-string matches string pool constant ic_plus_anim_%1$d.
If you see one of these strings, and you are certain that the String is not being used to load the given resource dynamically, you can use the tools:discard
attribute (described under "Keeping Resources" above) to inform the build system to remove it.
Resource URLs
In addition to looking for calls to Resources#getIdentifier, the build system will also look through all the String constants in your code, as well as various res/raw
resources, looking for resource URLs of the form file:///android_res/drawable//ic_plus_anim_016.png
. If it finds these, or strings that look like they may be used to construct resources like these, it will mark these resources as used and will not shrink them. For raw resources in particular, it attempts to analyze .html, .css and .js files lexically such that it for example will ignore Strings that aren't used in URLs or in JavaScript literals that could be used to build up a resource URL.
Res Configs
In addition to removing unused resources, you can also use the Android Gradle plugin's "resConfigs" feature to have it remove any resource configurations that you app does not need.
For example, let's say the messages in your application have not been translated and are all in English. If you are using a library such as Google Play Services, you are picking up translations for all of the messages in those libraries. When functionality in the library is accessed, those will be shown to the user. Whether you prefer that, or want to have the whole app use a single language, is up to you. But if you choose to have a single language, or more generally, just the languages your app is targeting, you can set that up in your build.gradle file, and then at build time all other languages are dropped (which will make your APK smaller, similar to the resource shrinking facility).
Here's what you add to your build.gradle file if you for example want to limit your languages to just English and French:
android {
defaultConfig {
...
resConfigs "en", "fr"
}
}
When using build tools older than 21 you could also add resConfigs "nodpi", "hdpi"
to also limit the density folders that are packaged. Instead, use apk splits to provide different apks to devices with different densities. Strings, Dimensions, Styles, etc
Currently, only non-value resources are removed by the resource shrinker: drawables (.xml, .png, .9.png, etc), layouts, menus, etc. Resources that are defined in a values/ folder (strings, dimensions, styles, colors, etc) are not removed. Luckily, non-value resources, especially bitmaps, tend to be where most of the bulk is anyway. The reason non-value resources can't be removed currently is that aapt does not allow the Gradle plugin to specify predefined versions for resources. This is tracked in issue https://code.google.com/p/android/issues/detail?id=70869. (This means that the resource shrinker can't run aapt a second time to regenerate the resources.arsc file with new offsets, while preserving the resource integers that are already referenced in the compiled and proguarded code.) Bugs
If you find bugs, use Android Studio's Help > Submit Feedback feature to file a bug. Please include as much detail to reproduce as possible.