Sunday, April 24, 2011

Fixing the java.lang.ClassNotFoundException com.test.model.SomeModelClass in loader dalvik.system.PathClassLoader on Android

Recently, I had faced a very interesting issue on one of my Android project.  The issue was the dreadful java.lang.ClassNotFoundException.

The Android App, that was facing the ClassNotFoundException was tested on multiple devices.  Some of the devices that we tested the app on were
The app worked fine on all devices without any problems at all.  We decided to go live with the App on Android Market.

Everything looked great till one day when one of the QA's downloaded the app on HTC MyTouch 4G.  The app refused to work on this device.  The app threw ClassNotFoundExceptions.  The stack trace looked like

After looking at the stack trace, I was thinking, Hmm this is weird, the app works on all these devices without an issue.  Moreover, the app cannot compile if the class com.test.model.SomeModelClass was not found.  Then why it doesn't work on HTC MyTouch 4G?

My initial reaction was, may be the app was not downloaded correctly from the Market.  May be the downloaded APK was corrupt?

I requested the QA's to uninstall the app and re-install it again from the market.  I was hoping that this could fix the issue.

But software development has taught me one thing
Nothing changes, If Nothing changes
Yep, the reinstall didnt work.  The app was still throwing the ClassNotFoundException.

We were using ProGuard.  Proguard is a free Java class file shrinker, optimizer, obfuscator, and preverifier.  Another thought that crossed my mind was, Is ProGuard removing the class com.test.model.SomeModelClass?

I created the APK without running ProGuard on it.  Requested the QA to test it.

That didn't work either.  ProGuard was not at fault.

By now, I was really confused.  What could be the issue with the app?  I thought, may be, if I try to load the class explicitly as the first thing in the app, then would it fix the issue?  

I updated the APK, loaded the class explicitly when the app starts, requested the QA to retest the APK.

Nope, that didn't fix the issue.  Interesting thing that I noticed about about this change was, the class loads successfully when I load it explicitly, but the class is not found when the app really needs it at a later stage!

History has shown us, class loader issues are not easy to fix.

I was quickly running out of options.  People were downloading the app on their device from the Market and may be some of them were facing the ClassNotFoundException issue with the app.  I needed a solution and that too quickly!  We didn't want to make a wrong first impression.  What should I try next?

At the back of my mind, I was sure that its some sort of a class loader issue.  When I explicitly load the class its found, but when the class is needed later in the app, its not found.  Surely something to do with a different class loader.  I decided to go back to basics, let's to do it step by step.

First step, 

When does the app need the class?

The app makes a HTTP call to get some information.  The information is returned in form of JSON responses.  As mentioned in my previous post, I was using Gson to parse the JSON response.  Gson is an excellent library with it comes to JSON manipulation.

Gson converts JSON response into Java Model Objects.  This is the point when the Java Model Class com.test.model.SomeModelClass is loaded.  The problem lies here, Gson tries to load the class com.text.model.SomeModelClass dynamically and its unable to find it!

Why Gson does not find the class?

The real confusion in my mind was, why Gson is able to load the class on all other devices but HTC MyTouch 4G?  My reasoning to that was, may be, Gson is loaded in a different class loader in HTC MyTouch 4G?  Is that even possible?  

After some intense googling and trying our numerous things, I finally figured out what was issue and what was solution.

This issue occurs only when all of the following conditions are met
  • The App uses Gson to manipulate JSON
  • The device is HTC Desire HD or MyTouch 4G on T-Mobile
The issue is HTC - The device manufacturer, also uses Gson library and they have made it public.  

Somehow, when the JVM is loaded on such devices, the Gson is also loaded along with it.  Because of this, the Gson library that is bundled with the app never gets loaded.  

When the global Gson tries to load classes from our domain model, it fails to find them.  This is because, the class loader that loaded the global Gson library does not have our app on its classpath!

That explained what the problem was, let's see how we can fix it!

The Fix

The fix was pretty simple.  We need to change the package name of the Gson library so that the global Gson library does not interfere with the local Gson library.  To change the package name of local Gson library, I used JarJar project.

I followed the following steps to change the package name of the local Gson library
  • Download jarjar project.
  • Put jarjar-1.1.jar and gson-1.6.jar in the same directory
  • Create a new text file in this directory, lets call it rules.txt.
  • Write the following line in the rules.txt file: rule**
  • Open a command prompt and execute the command, java -jar jarjar.jar process rules.txt gson-1.6.jar myproject-gson-1.6.jar.
  • Replaced the gson library reference in the project with myproject-gson-1.6.jar
  • Update the imports to use the new package name
  • Compile and build the new APK
  • Requested the QA to test the updated APK
To look at more details visit this link.

And that was it, It worked!

It was such a relief!  I am glad that, I faced this issue, makes me love the job that I am doing!
Have some Fun!