April 24, 2018

    ProGuard configuration made easy

    ProGuard configuration made easy

    ProGuard is our powerful open source tool to optimize Java bytecode. Talking to developers across the globe, we hear that it can be a life saver for projects that have grown too large or too slow to comfortably work on the wide range of Android devices. Many developers immediately add that ProGuard has a steep learning curve and that setting up a working configuration for a complex project can be a challenge. But there is good news: ProGuard 6.0 is making life a lot easier, thanks to a new option to get suggestions at runtime about any missing configuration.

    Background: it's all about reflection

    ProGuard removes unused classes, fields and methods, and it optimizes and obfuscates the rest. Usually, this works very well due to the consistent structure of Java bytecode. ProGuard can, for example, analyze the transitive dependencies in your code and remove unused elements. However, reflection can throw a spanner in any deep static analysis. For instance, a call like Class.forName(someClass) could potentially refer to any class in the code. If ProGuard removes or renames the seemingly unused class, the processed code is broken. ProGuard, therefore, needs information about such reflection. This is where you as a developer come in: you have to specify -keep options to tell ProGuard to preserve the entry points that reflective calls expect. For example, you may need to preserve a class with its original name:

    -keep class com.example.SomeClass

    ProGuard already supports wildcards and Java-style templates to keep the configuration as concise and as robust as possible. You can syntactically keep all classes (fields, methods) that match a given pattern, like "class com.example.**". You can also semantically keep all classes (fields, methods) that match a given structure, like "class * extends android.os.Parcelable". You can even specify complex combinations of both. This works great, assuming you know the inner working of your code well enough. A growing number of external libraries help out some more: they come with corresponding ProGuard configuration in the shape of a proguard.txt file inside the .aar file.

    However, real projects may have many developers and many external libraries. It can be difficult to have a good overview of reflection in the code in that case. You may find yourself updating your ProGuard configuration and running your processed application until it works. With some luck, you get informative ClassNotFoundExceptions, but sloppier code may spit out unhelpful NullPointerExceptions or worse, malfunction without reporting anything.

    Let your app do the hard work for you

     With ProGuard 6.0, you can specify this new option:

    -addconfigurationdebugging

    ProGuard then instruments your app so that it provides feedback at runtime about any missing configuration. You can run the app and put it through its paces. If it crashes because of some missing ProGuard rule, you no longer need to chase elusive exceptions. You get suggested configuration in your logcat. For example, your application may rely on GSON to serialize data to JSON strings. GSON reflects on the serialized fields, expecting them to be present with their original names. If you don't have the required ProGuard rule yet, your app prints it out for you:

    The class 'com.google.gson.internal.bind.ReflectiveTypeAdapterFactory'
    is calling Class.getDeclaredFields on class 'com.example.Person' 
    to retrieve its fields.You might consider preserving all fields 
    with their original names, with a setting like:
    
    -keepclassmembers class com.example.Person { 
      <fields>; 
    }

    Normally, you can just copy and paste this rule into your ProGuard configuration file. At that point, you have to iterate and test again, but the process is much faster.

    Occasionally, the instrumented code may report a false positive when it tries to reflect on a class that doesn't exist, on purpose, for some reason. You can then ignore the suggestion.

    Once your app runs smoothly, you can remove the debugging option again. You definitely don't want the instrumentation (including the obfuscation mapping) in your final app.

    Our experience

     We work with ProGuard and DexGuard configurations every day, for a wide variety of apps. With minimal background information on the structure of these apps, the option -addconfigurationdebugging has been magical. We no longer need to rely on expertise and study of the code. We can just toggle the option and let the app report on any configuration that is missing. We highly recommend it to anyone - beginner or expert - who wants to set up a ProGuard configuration with minimal effort.

    Guardsquare

    Discover how Guardsquare provides industry-leading protection for mobile apps.

    Request Pricing

    Other posts you might be interested in