I’ve written several blog posts recently about technical aspects of using ProGuard to obfuscate your code in Android apps. Then, in March, I gave a talk at DroidCon Boston walking through an overview of How ProGuard Works. Through all of those, I tried to keep the content very matter-of-fact and technical. I got a lot of technical questions in response, but I also got a lot of questions about the organizational and software engineering aspects — how to think about maintenance, and testing, and debugging, and communicating with your team. So what follows are the less technical, but possibly more practical, guidelines I’ve found for working with ProGuard.
You’ll find that most of these tips are focused on the fact that obfuscation can expose problems that are only seen at runtime, and then only when a specific code path is executed. This means that most of these rules are focused on making sure you’re likely to discover those problems.
As early as possible
If possible, start using ProGuard from day 1 of a new project. I’ve gone through the experience of enabling ProGuard on a project that’s been worked on for years without it, and it’s more difficult. You’ll get a warning about some code, and you’ll find that nobody on the team remembers that code.
Enabling ProGuard from day 1 ensures that issues are encountered immediately, when the relevant code is fresh in your mind.
As many builds as possible
Don’t enable ProGuard in your day-to-day dev builds*, it will increase your build times a little and cause you extra pain in debugging. But for every other build — QA, beta, release — turn ProGuard on. You want as many testers as possible exercising the code as many ways as possible to ensure that you notice any ProGuard bugs early on.
*There is an exception to this, which is when you get a report of a bug that you suspect might be triggered by obfuscation, you want to enable ProGuard in your dev environment so you can easily reproduce the bug. This means that you want to have a single, easy, flag in your build.gradle that you can flip to true to enable ProGuard on your debug builds. Have all of the configuration files ready for that build type — the same configuration files you’re using for the rest of your builds — you want the only effort on your part to be flipping the one flag.
The Phased Approach
If you’ve inherited a codebase that is already in production, you can’t enable ProGuard from day 1, but you can try to fake it a bit. Start by enabling ProGuard in your dev builds and fixing the compile-time errors. Then, run a ProGuard-ed build and exercise the app as much as possible. Walk through each feature as well as you can, and try to find problems. Next, enable ProGuard for your QA builds, and wait for a bit. This is the real key.
You’ll give your QA team time to work through the app and find problems. You want to make sure they’ve had time to exercise the app before you move on to the next phase, which is a beta build, if you have one. A beta should hopefully expose your obfuscated app to a broad, but still small, set of users. Let that work for a bit, then you can enable for your public builds.
Make your Keep rules as specific as possible
I’ve mentioned this in my blog post and in the talk, but it bears repeating. There’s no value in using ProGuard if you’re going to kneecap it with overly-broad keep rules.
Document every keep rule
This is deceptively important. It’s tempting to just add the keep rule you need to a config file and be done, but you’re hurting yourself in the long run. A year from now, you’ll look at the rule and wonder why it was there. It might be referring to code that’s long gone, or a library that’s no longer used. Or it could be critically important. You don’t know, because the rule itself doesn’t tell you the reason it was added. And, perhaps more importantly, it doesn’t tell you how to test whether it’s still necessary.
Add a comment. Explain what was broken and why that rule fixed it.
That’s it. Try it out. If you’ve got some different ideas, based on your experience, I would love to hear it.