Hints for using Proguard on your Android app

So last time I explained how to set up Proguard in your Maven build for your Android application without getting yourself into trouble with regards to different operating systems or with automatically saving your mapping file in case you use obfuscation and want to be able to make sense of the stack traces in the Android market backend. I however mostly left out any tips on configuring proguard itself, since this is a whole other chapter. And thats what I am going to explain a bit more in detail today..

To understand how to configure Proguard you have to understand what it can do for you so lets have a look.

Shrinking
means that it looks at all the classes in your application and libraries and removes any classes that are not called.
Obfuscating
means that it renames and jumbles up the names of packages, classes and methods and so on in order to make reverse engineering your source code from byte code harder
Optimizing
means that it manipulates the byte code in various ways to improve performance and size of your application

All these three steps have different configurations and and different degrees of difficulty to get to work. So the best approach is to switch Obfuscating and Optimizing off for starters and see that you get to a running application. Then as a next step try obfuscation, if you need that and/or optimisations. To switch those trouble makers off you use

<obfuscate>false</obfuscate>
<options>
  <option>-dontoptimize</option>

in your Maven Proguard plugin configuration. Note this post will document the Maven related usage but all tips will work in a standard proguard config file as used by the stock android Ant based build as well.

Shrinking

Shrinking happens automatically and most of the time does not cause any issues. However beware, you can still run into trouble, if you use reflection or some other runtime dynamic lookout. For example I had this code fails after proguarding

try {
  Field f = R.drawable.class.getField(imageName);
  imageResourceId = f.getInt(null);
} catch (Exception e) {
  Log.d(LOGTAG, "Access failed. Returning default.");
  imageResourceId = R.drawable.thumbnail1;
}

but when I refactored it to

try {
  int imageResourceId = context.getResources().getIdentifier(name, "drawable", "com.simpligility.mobile");
   categoryImage = context.getResources().getDrawable(imageResourceId);
} catch (Resources.NotFoundException e) {
  Log.d(LOGTAG, "Access failed");
}

As you can see the reflection based access in the upper code snippet basically failed all the time and returned the default only. This was also good to refactor since reflection is known to be slower, especially on Android.

In general you will tell Proguard to definitely keep a whole bunch of classes around that are part of your Android app. Typically these are activities, services, broadcast receivers and so on. A pretty exhaustive configuration could look like this

<option>-keep public class * extends android.app.Activity</option>
<option>-keep public class * extends android.app.Application</option>
<option>-keep public class * extends android.app.Service</option>
<option>-keep public class * extends android.content.BroadcastReceiver</option>
<option>-keep public class * extends android.content.ContentProvider</option>
<option><![CDATA[-keep public class * extends android.view.View {
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
    public void set*(...);  }]]></option>
<option><![CDATA[-keepclasseswithmembers class * {
    public <init> (android.content.Context, android.util.AttributeSet); } ]]></option>
<option><![CDATA[-keepclasseswithmembers class * {
    public <init> (android.content.Context, android.util.AttributeSet, int); } ]]></option>
<option><![CDATA[-keepclassmembers class * implements android.os.Parcelable {
    static android.os.Parcelable$Creator *; } ]]></option>
<option><![CDATA[-keepclassmembers class **.R$* { public static <fields>; } ]]></option>
<option><![CDATA[-keepclasseswithmembernames class * { native <methods>; } ]]></option>
<option><![CDATA[-keepclassmembers class * extends java.lang.Enum {
    public static **[] values();
    public static ** valueOf(java.lang.String); } ]]></option>
<option><![CDATA[-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View); } ]]></option>

With these options your build should be pretty much ready to roll for most Android applications being shrunk down considerably. As you can see they make sure that a whole bunch of Android specific classes are untouched. This can be important e.g. if an Activity is only referenced from the manifest file. Proguard does not know about it and might end up tossing it out, which is probably not what you intended.

Depending on your libraries and set you will have to use

<option>-ignorewarnings</option>

to get the build to pass, but have a good look at the reported warnings, while your build log zooms pass ;-)

Obfuscating

Once you are happy with shrinking and it is working you can get more adventurous and enable obfuscation by setting the configuration mentioned above to true. You might run into trouble with mixed case class or methods names which you can disable with

<option>-dontusemixedcaseclassnames</option>

and since you will probably want to be able decipher the stack traces later on with retrace you should preserver line numbers like that

 
<option>-renamesourcefileattribute SourceFile</option>
<option>-keepattributes SourceFile,LineNumberTable</option>

Optimizing

Last but not least you can get into the complexities of getting optimizations to work. In my experience you should disable a few of them on Android and then see how you go

<option>-optimizationpasses 3</option>
<option>-optimizations !code/simplification/arithmetic</option>

Other tips

If your setup from above fails, you might be e.g. using Roboguice and your @Inject, @InjectView and other annotations are getting tossed out. To avoid that you should add

<option>-keepattributes **</option>

and you will be much happier. You can also specify the annotations by name if you only want to preserve some of them, but the full sweep will definitely work.

In general you should really check out the excellent proguard website and specifically the refcard is a good place to get an idea what you could be doing.

With these hints and potentially some help from the very helpful author of Proguard Eric Lafortune on forums and more you should be able to get it all going and end up with a significantly smaller apk and some headaches for potentially hackers and maybe even a tiny better performance. I hope it was worth it and I could help a bit.

Manfred

Leave a Reply

Required fields are marked *.


You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>