Thursday, May 31, 2012

Java obfuscation, ndk, jni, and proguard.

We recently had the pleasure of posting an update to one of our apps - Bubble Zing - on to a couple Android markets.  Everything looked great on the development side and everything was pretty much tested and ready to go, so we exported our apk in Eclipse and sent it out!

Then the problems started trickling in (of course).

This was quite baffling that the same code running in Eclipse was completely breaking when running from an exported/signed APK file.  What was going on here?!  I googled around for various errors and almost all of them talked about missing libraries and such.  Well, none of those things were my issue.

I finally started getting some useful information from logcat about ClassNotFound and being unable to get new instances of *my activity* or various other failures about FindClass failing.

I was pretty stumped by the whole thing, so I ended up opening the APK like a zip file to check out what was actually IN the apk.  Everything looked as it should have been.  All my resources were there, the class path stuff was set as well (really confusing me why all this classnotfound stuff was happening now).  It wasn't until I ran apktool on my apk to get some actual additional information about my java files that I started to trace down the issue.  I noticed that all of my Java classes which were called from my native library were named "a.java" and "b.java", etc.  As opposed to all the normal java files which were not interacting my native shared library were fine.  So, it seems like we narrowed down what sort of thing was happening here.

Ok, somewhere in the export process, I was was losing my actual java names... and with things like fully-qualified names everywhere, I suspect this was a major problem.

So, I found that Proguard was obfuscating away my class names during the export process, meaning once my app was in apk form, calls from native code were completely unable to find half of what it needed to talk to for it to work!

For those interested, I added the following lines to proguard.cfg to inform proguard that I would like to keep most of my stuff unmangled please:


-keep public class [FULLY.QUALIFIED.PACKAGE.NAME].* {
public static *;
public static *;
private static *;
}

For us, only static methods were being called from native code.  We also set *all* of our classes in our package to be saved with the "-keep public class" line.


In retrospect, this totally makes sense, since during the apk packaging process, it realized that at no point were some of my chunks of java code being called by any other java code, so proguard figured it was ok to munge those up in to various cryptic alternate symbols.  I suppose it wasn't proguard's fault, but really my own misunderstanding of how the export process works and what proguard did for you automatically (or why).

I'm glad that was over, lesson learned?!  Always test an APK on my devices BEFORE sending it out to the market, even if we completely ran through all our tests before the export process.