1. 程式人生 > >【Android Studio】Resource Shrinking去掉無用的資源

【Android Studio】Resource Shrinking去掉無用的資源

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% Note: If necessary, you can disable resource shrinking by adding android {     buildTypes {         debug {             shrinkResources false         }     } } :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

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  (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 toadd_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"         } }
You could also add resConfigs "nodpi", "hdpi" to for example also limit the density folders that are packaged, and in general you can use this facility to limit the bundled resources among any resource follder qualifier.

Bugs

  • There are reports that tools:discard only works to mark resources in your own project as used, not resources from libraries. We're investigating.
  • Due to a bug, many library resources that could be removed are not treated as unused. This is due to this bug, and this has been fixed for a future update to the Gradle plugin.
  • An older version of the resource shrinker accidentally compressed resources in the raw folder. That is incorrect. You may have seen larger APK gains with those versions, but that was not correct and could result in runtime crashes.
If you find other problems, use Android Studio's Help > Submit Feedback feature to file a bug. Please include as much detail to reproduce as possible.