Sunday, September 5, 2010

Be careful to use "Class.forName()" in J2ME

A couple weeks ago, I made another mistake: I tried to use Class.forName() method to solve the multiple platform issue, but unfortunately it did not work on the actual devices due to the issue caused by the obfuscation. This is my another blind spot, because of my ignorance of J2ME obfuscation.

Scenario
I need to provide an API to support multiple mobile platform, for example: Nokia, Blackberry, and simulator, etc.
What I implement is pretty easy:
  • define an interface: Service
  • Implement the Service interface for each mobile platform, for example: NokiaService, BlackBerryService, SimualtorService
  • At the build time, inject different Service class file in the package.
  • At the run time, the application use the Class.forName() to create the Service objects.
The snippet code looks like this:
public Service createService(int device)
 {
    Service service = null;

    if( device == NOKIA)
    {
        service = Class.forName("NokiaService").newInstance();
    }
    else if( device == BLACKBERRY)
    {
        service = Class.forName("BlackBerryService").newInstance();
    }
    else if(device == SIMULATOR)
    {
        service = Class.forName("SimulatorService").newInstance();
    }
    else 
        service = Class.forName("GenericService").newInstance();

     return service;
 }

Here I use a factory pattern to support multiple platform, no need to use preprocess, sound pretty simple, right?
But I found the above solution only works on BlackBerry platform, on other J2ME platform, the application will throw the ClassNotFound Exception.

The reason of failure
The problem is in obfuscation phase, we usually use Proguard to do preverification and obfuscation for the J2ME application. The proguard will remove the unused class file. (for detail please check this link ) In my solution above use class.forName() by passing different string values, which is not recognized in Proguard. For example: in Nokia platform, the NokiaService.class is used, but Proguard did not find the NokiaService.class is explicitly reference in the java code, so it think it is unused class file, then remove it from the final package. Then it cause the ClassNotFoundException.If you don't use obfuscation, the application works, but it is really rarely the commercial application does not have obfuscated.

But why it works on BlackBerry platform?
Because Blackberry is quite different with other J2ME platforms, it requires it own preverification and obfuscation using its own RAPC compiler. And I found that in Blackberry application package it usually keep all the class files in the package, which is different with the Proguard. That is the reason why it works in BlackBerry platform

Solution
I have to rewrite the above code, remove all the Class.forName(), replaced with the explicit method call.
To support multiple platform, I use javaassit to adjust the createService() method for different platform, while you can use pre-process if you want, even I really don't like the J2ME pre-process, because it will bring other more complicated issue in your application.

Conclusion
Class.forName() seems the only reflection that is support in the J2ME platform, but actually it will cause the problem during obfuscation phase, try not to use the Class.forName() in your J2ME application.

1 comment:

  1. You probably just need to tell proguard to ignore classes under the packages that you want to use, then it won't obfuscate those calls.

    ReplyDelete