【问题标题】:Classes instantiated with different classloaders in the same Dalvik/JVM cannot "see" each other在同一个 Dalvik/JVM 中用不同的类加载器实例化的类不能“看到”彼此
【发布时间】:2014-03-14 05:46:31
【问题描述】:

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

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

=== 这是一个示例情况 ===

我们有两个模块:

  • First.jar(包含 FirstA 和 FirstB 类,打包在 classes.dex 中)
  • Second.jar(带有 SecondA 类,同样在 classes.dex 中)

通过使用 DexClassLoader,我加载了 First.jar。一切正常。 然后再次使用 DexClassLoader,我加载 Second.jar,SecondA 无法使用 Class.forName()“看到”FirstA 或 FirstB。我得到 java.lang.ClassNotFoundException。当我使用 Class.forName() 从 SecondA 检查 FirstA 时,我没有设置类加载器参数。

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

我在想,当一个类被加载到 dalvik/jvm 中时,它将在同一个 dalvik/jvm 中的所有其他类中可见/可访问。事实并非如此,还是我完全错了? 为了让 SecondA 能够看到 First.jar (Class.forName()) 中的类,我必须做什么?

欢迎任何我可以学习的建议或资源! 伙计们,我被困住了!

编辑:

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

【问题讨论】:

    标签: java android jar classloader dalvik


    【解决方案1】:

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

    您通常应该为所有类/jar 使用相同的类加载器,除非您有特定的理由不这样做。如果您确实有充分的理由,则需要确保加载程序之间具有适当的父/子关系。

    Java 类加载的语义模型在chapter 5 of the JVM spec 中。 Dalvik 通常遵循语义,但我对其中的差异并不十分了解。

    【讨论】:

    • 听起来你是在暗示发帖者可能正在为每个 jar 创建一个唯一的类加载器,而这些兄弟姐妹将没有必要的关系,而如果他们在 DVM 生命周期中使用单个类加载器,它会工作,因为所有东西要么在同一个加载器中,要么在一个具有父/子关系的加载器中?
    • DexClassLoader 的构造函数需要目标 .jar/.apk 文件的路径,并且我在不同的 .jar 文件中有多个模块,因此我不能在整个应用程序中全局只使用一个 DexClassLoader。我在考虑父/子关系,但可见性原则不会成为问题吗?从我一直在阅读的内容中,我知道父类加载器无法看到其子类加载的类。我一定会阅读您提供的所有论文。谢谢!
    • @Android5360:DexClassLoader 构造函数采用dexPath 参数,即“包含类和资源的 jar/apk 文件列表,由 File.pathSeparator 分隔,默认为“:”在安卓上”。所以你可以指定多个jar文件。
    • @fadden - 这似乎意味着它们都必须一次指定,对吧?因此,如果(相互可见性)插件的集合发生变化,是否需要某种重启/重新加载周期?
    • 它们都实现了通用接口吗?如果它们的公共方法都是应用程序 APK 中定义的接口的实现,则插件可以将它们的同伴对象视为该接口的实例,而不是在另一个加载器中加载的具体类。应用程序可以看到接口,因为它们是在应用程序 APK 中定义的,并且所有插件都可以看到它们,因为应用程序 APK 的类加载器是插件加载器的父级。 (如果它们不共享一个通用接口,那么我们可以就这些是否真的是“插件”进行迂腐的争论。)
    【解决方案2】:

    警告:非常丑陋的“解决方案”

    鉴于这 2 个模块使用不同的 DexClassLoader 加载(在不同的场合,由不同的线程等):

    • Module-1(带有 ClassModule1 类的 module-1.jar)
    • Module-2(带有 ClassModule2 类的 module-2.jar)

    让 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() "true" 上的第二个参数,我们肯定会再次加载该模块
    • 我们必须知道 Module-2.jar 的路径,这是不好的,因为模块是由 ModuleManager 而不是由它们自己管理的(责任分离)

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

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

    任何帮助表示赞赏。

    【讨论】:

      【解决方案3】:

      另一个想法...

      创建一个数据结构来保存用于加载每个插件的类加载器(可能只是ArrayList<ClassLoader>)。不要调用Class.forName("ClassIWant"),而是编写一个遍历每个类加载器并调用ClassLoader#loadClass("ClassIWant")的方法。

      我假设您的代码负责加载每个插件,这意味着您可以方便地引用类加载器,并且每个插件可以有多个您没有先验知识的类。 (如果每个插件都有一个您事先知道其名称的面向外部的类,那么您只需要一个从 StringClass 的映射,在插件加载时添加元素。)

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

      【讨论】:

      • 是的,这样的事情应该可以解决问题。如果我要这样做,我将不得不更改主 .apk 文件,因为 ModuleManager 是其中的一部分。但在这种情况下,另一个问题就出现了……我的应用程序安装了数千次,而且所有这些都在 Google Play 之外。如果我要更新应用程序,它不会在没有用户交互的情况下发生。而且我不想以任何方式打扰用户(Google Play 更新通知是迄今为止我可以期望用户接受的最多)。
      • 另外,据我所知,我无法隐形卸载/安装 .apk 文件。即使我可以,那将是通过使用引擎盖下的东西(而不是 SDK),并且很可能需要一个有根设备。我想坚持使用标准实用程序,因为我已经看到了足够多的 Android 兼容性问题 - Android SDK 结合不同的制造商(尤其是便宜的中国制造商)就像 CSS 和 Internet Explorer :) Fadden,谢谢你的时间!
      猜你喜欢
      • 1970-01-01
      • 2021-07-18
      • 1970-01-01
      • 1970-01-01
      • 2017-06-23
      • 1970-01-01
      • 1970-01-01
      • 2018-04-01
      • 1970-01-01
      相关资源
      最近更新 更多