在同一个Dalvik / JVM中使用不同类加载器实例化的类不能相互“看到”

时间:2014-02-14 18:13:01

标签: java android jar classloader dalvik

我正在开发一个带有可插入.jar模块的Android应用程序。

我的问题是,当我加载两个不同的.jar文件时,第一个.jar文件中的类不能从第二个.jar文件中“看到”(Class.forName())类,反之亦然。我使用DexClassLoader从主应用程序加载外部.jars。

===以下是一个示例情况===

我们有两个模块:

  • First.jar(使用类FirstA和FirstB,打包在classes.dex中)
  • Second.jar(使用类SecondA,再次使用classes.dex)

通过使用DexClassLoader,我加载了First.jar。 Everthing还可以。 然后再次使用DexClassLoader我加载Second.jar,而SecondA不能“看到”FirstA或FirstB与Class.forName()。我得到java.lang.ClassNotFoundException。当我使用Class.forName()从SecondA检查FirstA时,我没有设置classloader参数。

===示例情况结束===

我在想,当一个类加载到dalvik / jvm中时,它将在同一个dalvik / jvm中的所有其他类中可见/可访问。情况并非如此,或者我完全错了? 为了让SecondA看到First.jar(Class.forName())中的类,我需要做些什么?

欢迎任何我可以学习的建议或资源! 我被困了!

编辑:

  • 所有模块都已加载运行时。
  • 每个模块都由DexClassLoader的不同实例加载,因为我需要提供.jar文件的路径,而我无法将所有模块打包在一个jar中。

3 个答案:

答案 0 :(得分:2)

这是标准的Java行为;对于类A可见的类A,类A必须由类B的加载器或其父类之一加载。实际上,不同的类加载器可以具有相同名称的不同JVM类。

除非您有特殊原因,否则通常应为所有类/罐使用相同的类加载器。如果你有充分的理由,你需要确保你在装载机之间有适当的父/子关系。

Java类加载的语义模型在chapter 5 of the JVM spec中。达尔维克一般都遵循语义,但我对这些差异并不了解。

答案 1 :(得分:1)

警告:非常“解决方案”

鉴于这两个模块加载了不同的DexClassLoader(在不同的场合,不同的线程等):

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

ClassModule1要“看”ClassModule2而不是这样做:

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

应该使用这个:

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);

这很难看,因为很少:

  • 我们正在再次加载模块,因为forName()上的第二个arg()“true”
  • 我们必须知道Module-2.jar的路径,这是不好的,因为模块是由ModuleManager管理而不是自己(责任分离)

至少Dalvik(DexClassLoader)足够聪明,不能在加载之前优化.jar,而是使用之前DexClassLoader实例生成的缓存版本。

我猜问题在于:“在运行时,类或接口不仅仅由它的名称决定,而是由一对决定:它的二进制名称(§4.2.1)及其定义的类加载器。每个这样的类或接口属于单个运行时包。类或接口的运行时包由包名称和类或接口的类加载器确定。 (参考:JVM规范第5章http://docs.oracle.com/javase/specs/jvms/se7/html/jvms-5.html)。

任何帮助表示感谢。

答案 2 :(得分:0)

另一个想法......

创建一个数据结构,其中包含用于加载每个插件的类加载器(可能只是ArrayList<ClassLoader>)。编写一个迭代每个类加载器并调用Class.forName("ClassIWant")的方法,而不是调用ClassLoader#loadClass("ClassIWant")

我假设你的代码负责加载每个插件,这意味着你有一个方便的类加载器的引用,并且每个插件可以有多个你没有先验知识的类。 (如果每个插件都有一个面向外部的类,你提前知道它的名字,你需要一个从StringClass的地图,你可以在插件加载时添加元素。)

如何处理重复的条目,以及是否需要缓存结果以提高性能,取决于您。