Domanda

I'm working on an Android application with pluggable .jar modules.

My problem is, when I load two different .jar files, the classes from the first .jar file cannot "see" (Class.forName()) classes from the second .jar file and vice-versa. I load external .jars from the main application with DexClassLoader.

=== Here is an example situation ===

We have two modules:

  • First.jar (with classes FirstA and FirstB, packaged in classes.dex)
  • Second.jar (with class SecondA, again in classes.dex)

By using DexClassLoader I load First.jar. Everthing is OK. Then again by using DexClassLoader I load Second.jar and SecondA cannot "see" FirstA or FirstB with Class.forName(). I get java.lang.ClassNotFoundException. When I check for FirstA from SecondA with Class.forName() I'm not setting the classloader parameter.

=== End of example situation ===

I was thinking that when a class is loaded into the dalvik/jvm, it will be visible/accessible from all other classes in that same dalvik/jvm. This is not the case or I'm totally wrong? What I have to do in order to make SecondA to see classes from First.jar (Class.forName())?

Any suggestions or resources I can learn from are welcome! I'm stuck guys!

EDIT:

  • All modules are loaded runtime.
  • Every module is loaded by different instance of DexClassLoader because I'm required to provide path to the .jar file and I cannot pack all modules in one jar.
È stato utile?

Soluzione

This is standard Java behavior; for class A to be visible to class B, class A has to be loaded by class B's loader or one of its parents. In fact, different class loaders can have distinct JVM classes with identical names.

You should generally use the same class loader for all your classes/jars unless you have a specific reason not to. If you do have a good reason, you need to ensure that you have the appropriate parent/child relationships between the loaders.

The semantic model for Java class loading is in chapter 5 of the JVM spec. Dalvik generally follows the semantics, though I'm not well-informed on the differences.

Altri suggerimenti

WARNING: VERY UGLY "SOLUTION"

Given these 2 modules loaded with different DexClassLoaders (on different occasions, by different threads, etc):

  • Module-1 (module-1.jar with class ClassModule1)
  • Module-2 (module-2.jar with class ClassModule2)

For ClassModule1 to "see" ClassModule2 instead of doing this:

Class class = Class.forName("com.example.app.ClassModule2", false, ClassModule1.class.getClassLoader());

One should use this:

ClassLoader dexClassLoader = new DexClassLoader(
    manager.getPluginsFolder() + "Module-2.jar",
    this.context.getApplicationContext().getFilesDir().getAbsolutePath(), 
    null, 
    ClassModule1.class.getClassLoader()
);

Class class = Class.forName("com.example.app.ClassModule2", true, dexClassLoader);

This is ugly because of few things:

  • we are loading the module once again for sure because of the 2nd arg on forName() "true"
  • we have to know the path to Module-2.jar which is not good because modules are being management by a ModuleManager and not by themselves (separation of responsibility)

At least Dalvik (DexClassLoader) is smart enough not to optimize the .jar before loading it, but use the cached version generated by previous a DexClassLoader instance.

I guess the problem lies in: "At run time, a class or interface is determined not by its name alone, but by a pair: its binary name (§4.2.1) and its defining class loader. Each such class or interface belongs to a single run-time package. The run-time package of a class or interface is determined by the package name and defining class loader of the class or interface." (reference: chapter 5 of the JVM spec at http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html).

Any help appreciated.

Another idea...

Create a data structure that holds the class loaders used to load each plugin (maybe just ArrayList<ClassLoader>). Instead of calling Class.forName("ClassIWant"), write a method that iterates through every class loader and calls ClassLoader#loadClass("ClassIWant").

I'm assuming that your code is responsible for loading each plugin, which means you have a reference to the class loader handy, and that each plugin can have multiple classes that you don't have a priori knowledge of. (If each plugin has a single externally-facing class whose name you know in advance, you just need a map from String to Class that you add elements to as the plugins are loaded.)

How you deal with duplicate entries, and whether you need to cache results for performance, is up to you.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top