Tuesday, January 10, 2017

Java for Android: URLClassLoader throws NullPointerException - a bug?

PROBLEM:

In standard Java, URLClassLoader can be used to dynamically load a class to a Java program. And also, on Android, URLClassLoader should be able to load an external class, from a folder or JAR. However, Android implementation unfortunately fails, either with a ClassNotFoundException , or with NullPointerException.

Here's the test code:


File pathFiles=this.getApplicationContext().getFilesDir();

// /data/data/<appdir>/files


String path1=pathFiles.getAbsolutePath()+"/qoslib/";

File finalPath = new File(path1);




URL classUrl=null;
       try {
// classUrl = new URL("file:"+path1);
        classUrl = finalPath.toURI().toURL();

} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
     
     
       URL[] urls = {classUrl};
     
       URLClassLoader urlLoader1=null;
     
       urlLoader1=new URLClassLoader(urls, this.getClassLoader());
     
       try {
// urlLoader1.loadClass("com.example.agent2.orm.senders.SendMeetingSync2");
        //urlLoader1.loadClass("SendMeetingSync2");
       
        Class classToLoad = Class.forName ("com.example.agent2.orm.senders.SendMeetingSync2", true, urlLoader1);

       
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
int a;
a=2;
}
       catch(Exception ex){
       
        //ex.printStackTrace();
        int b=0;
        b=2;
       }
     



My guess is that there's a bug inside of Andorid's URLClassLoader or it's badly documented and prone to security restrictions on OS-level, since NullPointerException returns no additional error information.

Some people have suggested to use DexClassLoader:
http://stackoverflow.com/questions/22541177/urlclassloader-working-on-java-but-not-in-android

Hopefully,it's going to work properly.










SOLUTION:

There's an example of DexClassLoader in the Dalvik test suite. It accesses the classloader reflectively, but if you're building against the Android SDK you can just do this:
String jarFile = "path/to/jarfile.jar";
DexClassLoader classLoader = new DexClassLoader(
    jarFile, "/tmp", null, getClass().getClassLoader());
Class<?> myClass = classLoader.loadClass("MyClass");
For this to work, the jar file should contain an entry named classes.dex. You can create such a jar with the dx tool that ships with your SDK.

Answer by JFL seems to be promising. Need to verify.


Finally, here is a workflow that works:

#1 Extract the .class file you want to dynamically load (make sure you respect exact folder strcture as in original project). Put that folder to a folder temp1
So you have a structure like this:
temp1/com/example/myapp/MyClass.class

#2 Move to temp1 folder and create normal jar using this command:
jar cvf myjar.jar ./*

This will generate jar1.jar inside of temp1 folder. Now we need to make Dalvik-ready JAR out of it.

#3 To make dalvik ready jar, we use dx tool:

Location of dx tool:
ANDROID-SDK/build-tools/vXX.X/dx

Command:
dx --dex --output=update1.jar jar1.jar

This will generate a Dalvik ready JAR which you can transfer to your device. There you can use the code above to successfully load it to your program at runtime!
If you have problems with /tmp directory, replace it with Context.getCacheDirectory().getAbsolutePath(). It's tested and it works!

No comments:

Post a Comment

Ubuntu 12.04, 14.04, 16.04 - auto start an app or script before login

To run a command or application at startup, even before the user has logged in, you can use this file: /etc/rc.local The commands entered...