September 21st, 2010 | Published in Google Android
[This post is by Dan Galpin, an Android Developer Advocate specializing in games and comics. — Tim Bray]
The Securing Android LVL Applications blog post makes it clear that an Android developer should use an obfuscation tool such as Proguard in order to help safeguard their applications when using License Server. Of course, this does present another question. How should one integrate such a tool with the Android build process? We’re specifically going to detail integrating Proguard in this post.
Before you Begin
You must be running the latest version of the Android SDK Tools (at least v7). The new Ant build rules file included with v7 contains hooks to support user-created pre and post compile steps in order to make it easier to integrate tools such as Proguard into an Android build. It also integrates a single rules file for building against all versions of the Android SDK.
Adding an Optimization Step to build.xml
First, you’ll have to get Proguard if you don’t yet have it.
If you’ve been using Eclipse to do your development, you’ll have to switch to using the command line. Android builds are done using Apache Ant. A version of Ant ships along with Eclipse, but I recommend installing your own version.
The Android SDK can build you a starter build.xml file. Here is how it’s done:
android update project --path ./MyAndroidAppProject
If all works well, you’ll have a shiny new build.xml file sitting in your path. Let’s try doing a build.
You should end up with an unsigned release build. The command-line tools can also sign your build for you. You’ll notice that the android tool created a local.properties file in your directory. It will contain the sdk.dir property. You can have it make you a signed build by adding the location of your keystore and alias to this file.
Copy these files into your root directory (where the build.xml file sits). To add Proguard to your build, you first need to edit your local properties file to add the location of the directory that Proguard is installed in:
Finally... you need to add our script to your build file and have it override a few targets. To do this, we use the XML “entity” construct. At the top of your build.xml file, add an entity that references our script file:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE project [ <!ENTITY add-proguard-release SYSTEM "add-proguard-release.xml"> ]>
You’re not done yet. Somewhere within the project tag add the reference to our entity to include our script.
<project name="MyProjectName" default="help"> &add-proguard-release;
That’s it! In many cases, calling
Will give you an obfuscated build. Now test and make sure that it hasn’t broken anything.
But Wait, My App is Crashing Now
Most crashes happen because Proguard has obfuscated away something that your application needs, such as a class that is referenced in the AndroidManifest or within a layout, or perhaps something called from JNI or reflection. The Proguard configuration provided here tries to avoid obfuscating most of these cases, but it’s still possible that in edge cases you’ll end up seeing something like a
You can make edits to the procfg.txt file to keep classes that have been obfuscated away. Adding:
-keep public class * [my classname]
should help. For more information about how to prevent Proguard from obfuscating specific things, see the Proguard manual. Specifically, the keep section. In the interest of security, try to keep as little of your application unobfuscated as possible.
The standard settings provided in procfg.txt will be good for many applications, and will catch many common cases, but they are by no means comprehensive. One of the things that we’ve done is had Proguard create a bunch of output files in the obf directory to help you debug these problems.
The mapping.txt file explains how your classes have been obfuscated. You’ll want to make sure to keep this around once you have submitted your build to Market, as you’ll need this to decipher your stack traces.
Tools such as Proguard make the binary of your application harder to understand, and make your application slightly smaller and more efficient at the same time, at the cost of making it slightly more challenging to debug problems in the field. For many applications, the tradeoff is more than worthwhile.