Paul Brower bio photo

Paul Brower

Android development for fun and profit.

Email Twitter Github Stackoverflow Codementor

Find further code and implementation here: https://github.com/browep/AndroidProguardTutorial

The following tutorial follows closely what’s in the code at https://github.com/browep/AndroidProguardTutorial . I suggest you clone that repo and follow along with it. It was created with the Android Studio IDE which is free and easy to use. It also uses Gradle to build the project which can be utilized without Android Studio.

What is Proguard?

Proguard minimizes your code by taking recurring method names, field names, and class names and renaming them with smaller names. It also removes any unused code from your APK.

Why use Proguard?

With names minimized and unused code removed your APK is much smaller. Users tend to like smaller APK sizes as they download quicker and eat up less data. Since proguard changes the names of your methods it is harder for someone to decompile your code to determine how it works and possibly find weaknesses. Change your code like this is called obsfuscation.

Where does it come in?

Since we only need these security features during distribution like in the Google Play store, proguard is only used for release builds. Luckily gradle takes care of this for us.

Using Proguard

This tutorial assumes you are using gradle to build your project. There are links to corresponding files in the sample project.

The first thing to do is tell gradle that we want to use proguard for our release buildType.

full file /app/build.gradle

android {
  buildTypes {
    release {
      minifyEnabled true
    }
  }
}

You can run proguard now without any further configuration but lets tell proguard a little more about what we want. Proguard gets its configuration from a file in the project called proguard-rules.pro. Lets modify it to tell proguard we want to keep the line numbers in our stacktraces. Modify the contents to looks like this:

-keepattributes SourceFile,LineNumberTable

Now we are ready to build the sample project and see it run.

# in project base
./gradlew build

in the output you should see :app:proguardRelease which happens after the Java is turned into .class files and before the .class files are turned into dex code.

Proguard has also created some text files for us. We will use these text files to turn the obsfuscated stacktraces into readable stacktraces. The created text files are found in app/build/outputs/mapping/release/.

$ ls app/build/outputs/mapping/release/
dump.txt  mapping.txt  seeds.txt  usage.txt

Install this APK and click the “Cause Crash” button. You should see a stacktrace that looks similar to this in your ADB output:

java.lang.IllegalStateException: Could not execute method of the activity
  at android.view.View$1.onClick(View.java:3823)
  at android.view.View.performClick(View.java:4438)
  at android.view.View$PerformClick.run(View.java:18422)
  at android.os.Handler.handleCallback(Handler.java:733)
  at android.os.Handler.dispatchMessage(Handler.java:95)
  at android.os.Looper.loop(Looper.java:136)
  at android.app.ActivityThread.main(ActivityThread.java:5001)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:515)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
  at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.reflect.InvocationTargetException
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:515)
  at android.view.View$1.onClick(View.java:3818)
  at android.view.View.performClick(View.java:4438)
  at android.view.View$PerformClick.run(View.java:18422)
  at android.os.Handler.handleCallback(Handler.java:733)
  at android.os.Handler.dispatchMessage(Handler.java:95)
  at android.os.Looper.loop(Looper.java:136)
  at android.app.ActivityThread.main(ActivityThread.java:5001)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:515)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
  at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.IndexOutOfBoundsException
  at java.util.LinkedList.get(LinkedList.java:519)
  at com.github.browep.proguard.MainActivity.j(MainActivity.java:56)
  at com.github.browep.proguard.MainActivity.i(MainActivity.java:50)
  at com.github.browep.proguard.MainActivity.onClick(MainActivity.java:46)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:515)
  at android.view.View$1.onClick(View.java:3818)
  at android.view.View.performClick(View.java:4438)
  at android.view.View$PerformClick.run(View.java:18422)
  at android.os.Handler.handleCallback(Handler.java:733)
  at android.os.Handler.dispatchMessage(Handler.java:95)
  at android.os.Looper.loop(Looper.java:136)
  at android.app.ActivityThread.main(ActivityThread.java:5001)
  at java.lang.reflect.Method.invokeNative(Native Method)
  at java.lang.reflect.Method.invoke(Method.java:515)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
  at dalvik.system.NativeStart.main(Native Method)

The interesting part is

Caused by: java.lang.IndexOutOfBoundsException
  at java.util.LinkedList.get(LinkedList.java:519)
  at com.github.browep.proguard.MainActivity.j(MainActivity.java:56)
  at com.github.browep.proguard.MainActivity.i(MainActivity.java:50)
  at com.github.browep.proguard.MainActivity.onClick(MainActivity.java:46)

The methods have been renamed from subMethod & subSubMethod to i & j by proguard. See the full file here. The onClick method is called when the button is tapped.

So now lets de-obsfuscate this stacktrace. Take the text from ADB and save it to a file. I have used stacktrace.txt. The proguard tools are found in $ANDROID_HOME/tools/proguard/bin/. We will be using the mapping.txt file that proguard produced in the build.

$ retrace.sh mapping.txt stacktrace.txt

The outputted stacktrace will contain the de-obsfuscated stacktrace. In my example:

Caused by: java.lang.IndexOutOfBoundsException
at java.util.LinkedList.get(LinkedList.java:519)
at com.github.browep.proguard.MainActivity.void subSubMethod()(MainActivity.java:56)
at com.github.browep.proguard.MainActivity.void subMethod()(MainActivity.java:50)
at com.github.browep.proguard.MainActivity.void onClick(android.view.View)(MainActivity.java:46)
at java.lang.reflect.Method.invokeNative(Native Method)

We see that the method names have been filled in now.

Common pitfalls

  • One common mistake is that proguard doesn’t know about any reflection you are doing in the code so it may rename some methods that you are invoking programmatically. You can tell proguard to skip file by adding it to proguard-rules.pro
-keep class com.github.browep.MainActivity
  • Save the mapping.txt file for each release build. Without it you cannot de-obsfuscate your code. Consider adding to your source repository.

Taking it further

Proguard has lots of options and can be customized heavily, see http://developer.android.com/tools/help/proguard.html for Google’s take on it and http://proguard.sourceforge.net/ for full docs.