2012-01-05

Android: Dynamically loading classes

This article will explain how to dynamically load a class on Android, how it differs from desktop Java, and how to prepare files for dynamic loading.



Here is a complete program to demonstrate class loading in standard desktop Java:
import java.net.URL; import java.net.URLClassLoader; import java.lang.reflect.Method; public class Main { private void load(String filename, String classname) { try { URL[] urls = { new URL(filename) }; ClassLoader parent = getClass().getClassLoader(); ClassLoader loader = new URLClassLoader(urls, parent); Class<?> c = loader.loadClass(classname); Object o = c.newInstance(); Method m = c.getMethod("func"); m.invoke(o); } catch (Exception e) { System.err.print(String.format("DLL failed: %s: %s", e.getClass().getName(), e.getMessage())); } } public static void main(String [ ] args) { Main main = new Main(); main.load("file:/tmp/Foo.jar", "Foo"); } } This will load class Foo from /tmp/Foo.jar, create an instance of that class, and finally call the member function func().
Note:
  • Loading of a regular file is done via URLClassLoader.
  • The file name must be prefixed by "file:".
  • The statements in the try clause throws a number of different exceptions, that are all caught by the catch-all Exception.
Test this code:$ javac *.java $ cp Foo.jar /tmp $ java Main Dynamic class loading in Android is slightly different:
  • The standard class loaders don't seem to work.
  • The class loader to use is dalvik.system.DexClassLoader.
  • The classes must be put into a .dex file that must be embedded in a .jar file. Done using the little known utility dx.
The application does not change much: package se.sdu; import java.lang.reflect.Method; import android.app.Activity; import android.os.Bundle; import android.util.Log; import dalvik.system.DexClassLoader; public class Main extends Activity { public void dload(String filename, String classname) { try { Context ctx = getApplicationContext(); String dex_dir = ctx.getDir("dex", 0).getAbsolutePath(); ClassLoader parent = getClass().getClassLoader(); DexClassLoader loader = new DexClassLoader(filename, dex_dir, null, parent); Class c = loader.loadClass(classname); Object o = c.newInstance(); Method m = c.getMethod("func"); m.invoke(o); } catch (Exception e) { Log.e("se.sdu", String.format("DLL failed: %s: %s", e.getClass().getName(), e.getMessage())); } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); dload("/mnt/sdcard/tmp/Foo.jar", "Foo"); } }
Note
  • dex_dir is as a cache for optimized classes.
  • The file name must not be prefixed by "file:".
This single-file application is built just like any other Android application. No special configuration is necessary.
Now to the dynamically loaded class:
import android.util.Log; public class Foo { public static void func() { Log.d("se.sdu", "Hello from Foo.func()"); } } To compile the dynamically loadable class, and build and push the jar file containing it:
javac -classpath /path/to/android.jar Foo.java dx --dex --output Foo.jar Foo.class adb push Foo.jar /mnt/sdcard/tmp/. The option --dex tell dx to generate a .dex file, and the file name extension .jar to put the .dex file into a .jar file.

That's all.

4 comments:

  1. How about making Proxy for class?

    ReplyDelete
  2. How about making Proxy for class?

    ReplyDelete
  3. How can I load a jar in app from a server say my jar is at http://abc.com/mydex.jar.
    I tried using URLClassLoader, it gives me ClassNotFoundException. I guess, this is because URLClassLoader is unable to find the class because the jar has classes.dex in it. Via dexClassLoader also, getting the same exception.

    Can you please help, how to load the dex jar from server?

    ReplyDelete
    Replies
    1. Have you found a solution please? I've a same problem.

      Delete