我正在开发一个带有可插入.jar模块的Android应用程序。
我的问题是,当我加载两个不同的.jar文件时,第一个.jar文件中的类不能从第二个.jar文件中“看到”(Class.forName())类,反之亦然。我使用DexClassLoader从主应用程序加载外部.jars。
===以下是一个示例情况===
我们有两个模块:
通过使用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())中的类,我需要做些什么?
欢迎任何我可以学习的建议或资源! 我被困了!
编辑:
答案 0 :(得分:2)
这是标准的Java行为;对于类A可见的类A,类A必须由类B的加载器或其父类之一加载。实际上,不同的类加载器可以具有相同名称的不同JVM类。
除非您有特殊原因,否则通常应为所有类/罐使用相同的类加载器。如果你有充分的理由,你需要确保你在装载机之间有适当的父/子关系。
Java类加载的语义模型在chapter 5 of the JVM spec中。达尔维克一般都遵循语义,但我对这些差异并不了解。
答案 1 :(得分:1)
警告:非常“解决方案”
鉴于这两个模块加载了不同的DexClassLoader(在不同的场合,不同的线程等):
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);
这很难看,因为很少:
至少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")
。
我假设你的代码负责加载每个插件,这意味着你有一个方便的类加载器的引用,并且每个插件可以有多个你没有先验知识的类。 (如果每个插件都有一个面向外部的类,你提前知道它的名字,你需要一个从String
到Class
的地图,你可以在插件加载时添加元素。)
如何处理重复的条目,以及是否需要缓存结果以提高性能,取决于您。