Mastering ProGuard for Building Lightweight Android Code

Proguard-Header-Image.png

ProGuard, a code optimization and obfuscation tool provided as part of the Android SDK, can be a double edge sword -- it presents bootstrapping challenges but when applied correctly, provides tremendous benefits! At Crashlytics we’ve spent a lot of time leveraging the power of ProGuard to develop lightweight libraries to help app developers ship awesome products -- in particular, we use these four features in our day-to-day development.

Shrinking

As your codebase grows and becomes more full featured, it’s important to keep a small binary in mind. Reducing the size of the APK can be extremely advantageous, since large binaries are much less likely to be installed in poor network conditions or on older less powerful devices.

It's been well publicized among the developer community that the Dalvik Virtual Machine is memory limited to 64k methods. With this restriction, ProGuard can help provide a buffer as you consider which measures to take to reduce your code size. Removing unused code, which likely exists in a 64k method project, enables your development team to work on features unimpeded by technical limitations, while refactoring or external class loading is considered.

Even though shrinking is advantageous, simply identifying unused code to remove is a good practice. By using the printusage flag in Proguard.cfg, your configuration file, ProGuard will list the unused code to allow for proper code maintenance and cleanup.

Obfuscating

With tools available to extract the contents of APK’s, deodex, and read the class files, it’s important to obfuscate to protect the proprietary aspects of your codebase. ProGuard generates a mapping file that allows you to map the stack traces of obfuscated code to actual methods.

Original code:

Code obfuscated by ProGuard:

By automatically collecting the mapping files on build, Crashlytics streamlines the deobfuscation process of your code and intelligently prioritize stack traces to make your debugging process effortless.

Repackaging

Repackaging allows ProGuard to take externals jars and class files and move them to a single container with a common java package location:

or those of you building libraries, repackaging is extremely helpful if you choose to show a simple interface to third party developers while keeping a maintainable and well structured project hierarchy in the source repository. This can also be useful in organizing lower level packages while exposing well defined interfaces!

Optimizing

Optimizing works on compiled classes to implement many small optimizations based on the java version. By default the proguard-android.txt that ships with the Android tools has optimizations turned off, but the proguard-android-optimize.txt has the presets if needed.

ptimizations provide performance improvements for language operations. However, there are known incompatibility issues with various Dalvik versions, so we encourage a thorough review of the code base and device target demographic before enabling.

Beyond leveraging these four core features of ProGuard, we crafted several strategies for those of you looking to build lightweight apps/libraries and optimize your interaction with ProGuard.

Improving Build Times

Adding ProGuard to the build process can slow down build time, so it's important to minimize the amount of code ProGuard needs to examine. This is vital when considering third party libraries, like Crashlytics, that have already been processed by ProGuard -- it’s just a waste of CPU to reprocess with ProGuard again, and it’s much slower!

We thought it would be valuable to estimate the improvement in build times when preprocessed, third party libraries are ignored in ProGuard. Using the Crashlytics library as an example, we conducted numerous runs with internal test apps across various sizes. We found that build times improved by up to 5% when the Crashlytics package is ignored. But that’s just one library that is already ultra-lightweight. Imagine the build time improvements for apps leveraging additional libraries -- it can be tremendous.

To avoid processing a library that may have been preprocessed, simply add the following to Proguard.cfg:

As obfuscation is usually done for security, if using an open source library, there may be no reason to obfuscate it. By following a similar pattern as listed above, processing can be further reduced ultimately improving build time. The Android support library is a great example:

Reflection

Using reflection in Android is highly discouraged for many well known reasons, including performance and instability in changing APIs, however, it can be quite useful for Unit Testing. Common use includes changing the scope of methods to set test data or mock objects. If you’re using ProGuard during development build to obfuscate, it’s important to understand that when a method or class name is changed, string representations of them are not. When designing testable interfaces, if tests are run on a device using a build that has been processed by ProGuard, this will cause method not found exceptions.

Library development

Additional complexity is introduced when developing libraries that are processed with ProGuard, both when they are distributed and when the app developer runs the process. When the code is obfuscated twice, it is much more challenging to track down bugs as two mapping.txt files would be required to de-obfuscate the stack trace. To avoid processing these libraries with ProGuard a second time, be sure to follow our steps in section above on improving build times!

For those of you building libraries, you may have encountered more challenges with ProGuard because in any sufficiently complex project, the possibility for custom ProGuard rules exists. We recommend not requiring custom ProGuard rules because the library can break if a different set of rules is applied after a custom set. If custom rules are required, be sure that those using your library include any custom ProGuard rules in their own config file. This will ensure compatibility between your library and the app!

Ever since Crashlytics was born, we’ve made it our mission to make developers’ lives easy. We hope that these strategies will help you build the next groundbreaking Android app/library and perhaps in the process, make a dent in the universe ;)

External Resources

3 Key Ways to Increase Android Support Library Stability

At Crashlytics, we‘re constantly exploring ways to help developers build the most stable apps. With this in mind, we recently began identifying some of the most common reasons why Android apps crash. Specifically, we were interested to see if given its broad adoption, does Android Support Library have any influence on your crashes?At Crashlytics, we‘re constantly exploring ways to help developers build the most stable apps. With this in mind, we recently began researching common reasons why Android apps crash. We were especially curious to see any trends in crashes originating from the Android Support Library, given that it is one of the most widely-used libraries in Android applications.

We found that about 4% of the 100 million crashes that we analyzed were related to the Support Libraries. Digging deeper, our research showed that the overwhelming majority of those crashes are caused by a small handful of recurring, preventable errors. Based on this analysis, we’ve identified some best practices for using the Support Libraries that are commonly overlooked and three key ways to increase stability.

Read More

Defining Custom Pre and Post-Processing Tasks in Gradle

We're always on the lookout for ways to make developing apps as easy as possible by providing developers with the most powerful crash reporting tools. When Google announced at I/O 2013 that they would be backing Gradle as a build system for Android development, we embarked on a ground-up approach to integrate Gradle into our supported build systems.

Read More

Announcing Crashlytics Logs

A treasure-trove of data lies in an app's logs and there's no better way to debug a problem than by knowing exactly what happened leading up to the critical moment. Capturing logging data has been our number-one customer request for months and our number-one concern. We care deeply about security and end-user privacy: collecting logging data opens the door to substantial risks. We wanted to begin the path down the road to building a Splunk for Mobile. I'm excited to announce that after focusing our R&D efforts, we think we've cracked it, and I wanted to share some details on our approach.

Read More

Reducing MongoDB traffic by 78% with Redis

As Crashlytics has scaled, we’ve always been on the lookout for ways to drastically reduce the load on our systems. We recently brought production Redis servers online for some basic analytics tracking and we’ve been extremely pleased with their performance and stability. This weekend, it was time to give them something a bit more load-intensive to chew on.The vast majority – roughly 90% – of inbound traffic to our servers is destined for the same place. Our client-side SDK, embedded in apps on hundreds of millions of devices worldwide, periodically loads configuration settings that power many of our advanced features. These settings vary by app and app version, but are otherwise identical across devices – a prime candidate for caching.

Read More

SLIDES: Building Backbone.js Apps for Scale

We had a blast at last night's Backbone.js MeetUp. It was awesome to see such a thriving community here in Boston and to share share a few of the insights we've had here at Crashlytics about building scalable application with Backbone.js. The slides from our talk are up on SlideShare for viewing.

Read More