Beginner to ProGuard:

The Basics

It can be tricky to setup ProGuard in Android projects correctly, especially when your app uses multiple third party libraries. Understanding what ProGuard is doing, knowing a few good internet resources and sensibly structuring your configuration can save you a lot of time.

In the Android build process, the ProGuard tool does a few important jobs: Minification, obfuscation, repackaging and optimisation. Enabling it is straightforward if you’re using gradle, just set minifyEnabled to true for your release buildType in build.gradle and pass it the default set of android optimisation rules.

buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile(‘proguard-android.txt’)
}
}

This will help to shrink, speed up and protect your app. However it mainly works by removing code that is never called and renaming what’s left. This is all well and good until you encounter reflection.

Reflection lets you write code that can look up and execute other code based on its name (among other things). For example we could have a TestRunner.java class which scans through all your classes to find and execute any methods with ‘Test’ in their name. See the problem? Thanks to ProGuard the test runner will no longer be able to find any tests because:

public void importantBusinessLogicTest(int testParameter) { ... }

Has been shrunk down to:

public void a(int b) {...}

(In reality, your unit tests would most likely run before any minification)

So how do you use ProGuard if you or any of the libraries in your app use reflection?

Do Not Disturb

Thankfully, you can specify rules as to which classes, methods and other parts of your app ProGuard should leave alone. You can list all these rules in a file and pass them to ProGuard via the proguardFiles method back in your build.gradle. The general convention is for this file to be called proguard-rules.pro.

proguardFiles getDefaultProguardFile(‘proguard-android.txt’), ‘proguard-rules.pro’

Inside of proguard-rules.pro we could instruct ProGuard not to touch any of test methods with the following rule:

-keepclassmembers class * {
public void *test*(...);
}

Now the test suite should work even after minification. More syntax for writing ProGuard rules can be found here.

Adding Rules for Libraries

The rules needed for 3rd party libraries are often complicated, hard to find and once you’ve added a couple, they make your proguard-rules.pro file look like an unintelligible mess.

If the library you’re using doesn’t list its required ProGuard rules — and a lot of them don’t — a great resource is this Git Repo, which contains rules for the most popular android libraries (and will welcome pull requests for any it doesn’t list).

So how should you keep all these rules organised and easy to manage whenever your app dependencies change?

The approach I’m currently using is to have a proguard directory inside my app folder with a separate .pro file for each library I’m using.

app
|-- build
|-- libs
|-- proguard
|-- butterknife.pro
|-- crashlytics.pro
|-- square-okhttp.pro
|-- src
|-- build.gradle

Then pass all the files in this folder to ProGuard via the fileTree method back in your build.gradle file:

proguardFiles getDefaultProguardFile(‘proguard-android.txt’), fileTree('proguard')

This lets you easily drop in new rules whenever you need a new library, and does all the work of merging them together.

Where to go from here?

The Android Developers guide is always a good starting point. There’s a blog post over at Crashlytics which goes into a bit more depth from a libraries point of view. And of course if all else fails, you could read the manual.

Update (20/09/2016)

As of gradle plugin version 2.2 fileTree(‘proguard’) will no longer work, instead it gives an error of Cannot convert the provided notation to a File or URI.

A workaround for this is to instead use:

proguardFiles getDefaultProguardFile('proguard-android-optimize.txt')
proguardFiles fileTree('proguard').asList().toArray()

Note the now two proguardFiles lines, see here for more details: https://code.google.com/p/android/issues/detail?id=212882