30 May, 2011

Fixing ProGuard config for Android projects

This is a brief tutorial on code obfuscation for Android apps using ProGuard tool supplied with Android SDK. I had a bug related with this and solved it, so I would like to tell people about it.


If you use Eclipse plugin for your Android app, you have a generated proguard.cfg file in your project, and it probably contains the following:

-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*

-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService

-keepclasseswithmembernames class * {
    native <methods>;
}

-keepclasseswithmembernames class * {
    public <init>(android.content.Context, android.util.AttributeSet);
}

-keepclasseswithmembernames class * {
    public <init>(android.content.Context, android.util.AttributeSet, int);
}

-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}
Not going to details, the file has everything one could possibly need. Except one thing.
If you are working with GUI (and most likely you are), you might have onClick methods in your layout XMLs. Something like this:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
>
    <TextView
        android:id="@+id/mytext"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="This is TEXT"
    />
    <Button
        android:id="@+id/mybutton"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="The Button"

android:onClick="myOnClickHandler"

/> </LinearLayout>
Usual stuff. Everything works before obfuscation, but after ProGuard you will get:

java.lang.IllegalStateException: Could not find a method myOnClickHandler(View)
    in the activity class com.mypackage.MyActivity for onClick handler on view class
    android.widget.Button
at android.view.View$1.onClick(View.java:2059)
at android.view.View.performClick(View.java:2408)
at android.view.View$PerformClick.run(View.java:8817)
at android.os.Handler.handleCallback(Handler.java:587)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:143)
at android.app.ActivityThread.main(ActivityThread.java:4914)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:521)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.NoSuchMethodException: onActionSelectLevel
at java.lang.ClassCache.findMethodByName(ClassCache.java:308)
at java.lang.Class.getMethod(Class.java:985)
at android.view.View$1.onClick(View.java:2052)
... 11 more
Okay, so if there is no more myOnClickHandler method than probably ProGuard has renamed it. To check this out I've looked into proguard/mapping.txt where the compiler saves the obfuscation reports (in Eclipse). And I found no references to such a function at all! WTF?
Well, the answer is simple: ProGuard not just performs renaming, it also optimises the code. And since myOnClickHandler is not referenced from Java code it gets thrown away. Totally.

So after some RTFM-activity I got these lines, that I think should be inserted into the proguard.cfg by default:

-keepclassmembers class * {
      public void *(android.view.View);
}

-keepclassmembers class **.R$* extends android.app.Activity {
      public static <fields>;
}
The first part keeps potential onClick handlers. It might be optimised to class * extends android.app.Activity, it's just my precautions. The second part is actually taken from ProGuard official website. It preserves R class from being optimised or renamed. I assume, this is also a must have.

And one more thing. Make sure that you always increase the version number in AndroidManifest.xml. First time when I did the obfuscation using the standard config, my app worked while it shouldn't have. Clever Android did not updated the app with the obfuscated package because the version number was the same as in unobfuscated one.

Hope you find this useful.

No comments:

Post a Comment