Importance of Proguard for the Android platform and its Debugging Techniques

Mobile Development Jun 7, 2022

ProGuard is an open source command-line tool that detects and removes unused classes, fields, methods, and attributes. It helps in creating a production-ready application in the Android Platform.

Why is Proguard necessary?

  • Shrink(Minify) the code: Removes unused code in the project.
  • Obfuscate the code: Rename the names of class, fields, etc.
  • Optimise the code: Inlining the functions.

Importance of Proguard:

  • It reduces the size of the application.
  • It removes the unused classes and methods that contribute to the 64K method counts limit of an Android application.
  • It makes the application difficult to reverse engineer by obfuscating the code.
  • The optimisations may also improve the performance of the application, by up to 20%. For Java virtual machines on servers and desktops, the difference generally isn't noticeable. For the Dalvik virtual machine and ART on Android devices, the difference can be worth it.
  • ProGuard can also remove logging code, from applications and their libraries, without needing the source code.

How it works

ProGuard first reads the input jars (or aars, wars, ears, zips, apks, or directories). It then subsequently shrinks, optimizes, obfuscates, and preverifies them. ProGuard also lets perform multiple optimisation passes. It writes the processed results to one or more output jars. It requires the library jars (or aars, wars, ears, zips, apks, or directories) of the input jars to be specified. These are essentially the libraries that you would need for compiling the code. ProGuard uses them to reconstruct the class dependencies that are necessary for proper processing. The library jars themselves always remain unchanged.


Enable Proguard in Android

1. Enable Proguard for a single module project:

  • Add the code in release build of app-level build.gradlefor a single module project.
  • The  minifyEnabled  property is part of the buildTypes release block that controls the settings applied to release builds and Flag minifyEnabled  enables obfuscation and code optimisation.
  • shrinkResource  controls the optimisation of resources and this can be set true only if minify enabled. You can read more about this here.
  • The getDefaultProguardFile('proguard-android.txt') method obtains the default Proguard settings from the Android SDK tools/proguard folder.
  • Android Studio adds the proguard-rules.pro file at the root of the module, which helps to add custom Proguard rules.

2. Enable Proguard in a Library or in a custom Module:

  • We can define Proguard-rules for any/each module and pass those rules to the app level by using consumerProguardFiles  

We can define all Proguard rules for any dependency used in any module in two places:

  • either in the main project’s Proguard rules
  • or we can make separate Proguard rules for each module and pass them to the app level via consumerProguardFiles


Proguard Configuration Debugging Techniques:

Sometimes the app crashes in release build even when the app is running fine in debug build. It might be an issue with Proguard/R8 configuration (R8 configuration files). One of the most advanced ways of investigating issues in release builds is analysing the content of the APK file. This can be done via Build > Analyze APK .... There we can see classes.dex containing all the bytecode, resources.arsc  which contains the mapping between original resources IDs and obfuscated and all resources under /res  folder.

To debug Proguard/R8 configuration, navigate to app\build\outputs\mapping\release. There we can see the following files:

  • configuration.txt  – merged file with all configurations – from the app, default Android, AAPT, all the libraries, etc. Here we can find what rules might cause an issue.
  • mapping.txt – file with mappings of original names to obfuscated ones. This will help in analyzing logs and the content of the APK file.
  • missing_rules.txt – file with Android Gradle plugin automatically generated rules to add to the existing keep rules in order to suppress warnings.
  • seeds.txt – file with kept files/classes/etc
  • usage.txt – files that are removed. We can see whether some needed class was removed

First of all, make sure that the issue is because of some misconfiguration of the release build. To confirm this, disable release optimisations and check if the bug disappeared:

minifyEnabled = false
shrinkResources = false

  • If the issue still reproduces, then the issue is not with proguard. Check logs or usages of BuildConfig.DEBUG in the codebase.
  • Additionally, release build can be made debuggable by making debuggable = true

If after disabling release optimisations, the issue disappears, then issue is with release optimisation configurations.

For example: Suppose we have in our project some code like:

Running the release app crashes with error :

Caused by: java.lang.RuntimeException at com.halodoc.MainActivity.onCreate(:1)

Going with the steps:

  • disable release optimisations - the issue still reproduces in the release build, therefore it is not related to proguard configuration. So we need to check the logs and debug the app.

There is incorrect logic for the release build, fix it and the issue is fixed.

Step 2 - Check whether it is a shrinker issue

Enable minification and keep the shrinker disabled:

minifyEnabled = true
shrinkResources = false

  • If the issue disappeared, then issue is with configuring shrinker. Probably it removed something we’ve relied on. Look at the resources.txt  file looking for the resources you access dynamically. Check whether these resources are added to keep.xml

If the issue still exists, then check next step.

For example:

If we launch the release build app will crash. In resources.xml we’ll see the error message:

Skipped unused resource res/layout/test.xml: 880 bytes (replaced with small dummy file of size 104 bytes)

So, going with the steps it will look like this:

  • disable release optimisations - the issue still here
  • enable minification while keeping shrinker enabled - no crash

Therefore we know that the issue is with shrinker removing unused resource. To fix that we need to add our resource to /res/raw/keep.xml like this:

Now enable release optimisations, check once again and it works fine.

Step 3 - Check whether it is an obfuscation issue

Inside  proguard-rules.pro file, add - dontobfuscate  to direct Proguard to disable obfuscation. If the issue isn't reproduce anymore, then there is some issue because of obfuscation.

  • Look at mapping.txt and check that the classes accessed via reflection are not obfuscated. If they are obfuscated, then add keep rules in the proguard-rules.pro.
  • Additionally, checking usages.txt will help to check classes removed during release optimisation.
  • ClassNotFoundExceptions can be fixed by adding a -keep public class line in the Proguard configuration file.

For example:

When running the app in the release, it crashes.
Going with the steps:

  • disable release optimisations - app crashes
  • enable minification while keeping shrinker disabled - app crashes
  • disable obfuscation in proguard-rules.pro - no crash.

Check mapping.txt and see that the class was obfuscated. So we need to make an exception in our proguard rules.

-keepnames class com.halodoc.TestClass

And the issue is fixed.

Another example:

App crashes in release because there is no such a class in the release build.
We even can see that by looking at logs:

Caused by: java.lang.ClassNotFoundException: com.halodoc.TestClass

Following the steps:

  • disable release optimisations - app crashes
  • enable minification keeping shrinker enabled - app crashes
  • disable obfuscation - app crashes

Here we understand that issue is because of code optimisations. Look at the usage.txt and see that the class was removed as a part of code optimisation. So, we need to keep the class explicitly by adding in proguard-rules.pro:

-keep class com.halodoc.TestClass

And the issue is fixed.

Conclusion:

As outlined in this blog, enabling Proguard leads to more optimised application and makes the app tougher to reverse engineer which helps in improving app security.

Overall Halodoc application has achieved 15-30% reduction in app size by enabling Proguard.

Reference:

Shrink, obfuscate, and optimize your app | Android Developers
Learn how to shrink code in your release build to remove unused code and resources.
ProGuard Manual: Usage | Guardsquare
ProGuard documentation about usage, configuration and options.

Read More:

You can read more about other Android topics in our Halodoc blogs at https://blogs.halodoc.io/


Join us
Scalability, reliability and maintainability are the three pillars that govern what we build at Halodoc Tech. We are actively looking for engineers at all levels and  if solving hard problems with challenging requirements is your forte, please reach out to us with your resumé at careers.india@halodoc.com.

About Halodoc

Halodoc is the number 1 all around Healthcare application in Indonesia. Our mission is to simplify and bring quality healthcare across Indonesia, from Sabang to Merauke. We connect 20,000+ doctors with patients in need through our Tele-consultation service. We partner with 3500+ pharmacies in 100+ cities to bring medicine to your doorstep. We've also partnered with Indonesia's largest lab provider to provide lab home services, and to top it off we have recently launched a premium appointment service that partners with 500+ hospitals that allow patients to book a doctor appointment inside our application. We are extremely fortunate to be trusted by our investors, such as the Bill & Melinda Gates Foundation, Singtel, UOB Ventures, Allianz, GoJek, Astra, Temasek and many more. We recently closed our Series C round and In total have raised around USD$180 million for our mission. Our team works tirelessly to make sure that we create the best healthcare solution personalised for all of our patient's needs, and are continuously on a path to simplify healthcare for Indonesia.

Priya Bhat

SDE, Android