【问题标题】:ServiceLoader.load is not finding the META-INF/servicesServiceLoader.load 没有找到 META-INF/services
【发布时间】:2015-06-20 08:38:20
【问题描述】:

所以我想构建一个可扩展的 android 应用程序,开发人员可以在其中添加“CustomDevice”类,主程序将自动运行它们而无需编辑现有代码。

我已经阅读了有关服务提供者接口的信息,并认为这是一个很好的解决方法。

因此我对其进行了测试并创建了一个名为“ICustomDevice”的接口,自定义设备类有望实现该接口。

我创建了一个名为“DummyDevice”的类,它实现了 ICustomDevice。

DummyDevice 和 ICustomDevice 都在同一个包“CustomDevicePackage”中。

所以在我的主程序中,我运行以下命令。

    ServiceLoader<ICustomDevice> loader = ServiceLoader.load(ICustomDevice.class);
    Iterator<ICustomDevice> devices = loader.iterator();
    System.out.println("Does it have devices? " + devices.hasNext());

它总是返回 false,这意味着它没有找到 'DummyDevice'

在我的 Eclipse 项目中,我在“src”中创建了一个名为 META-INF 的文件夹,并在其下创建了一个名为“services”的子文件夹。

“服务”有一个名为“CustomDevicePackage.ICustomDevice”的文件,其中包含一行内容“CustomDevicePackage.DummyDevice”。

我做得对吗?我看到的每个关于 SPI 的例子都是关于加载 JARS。 我没有加载 JAR,我正在尝试在同一个项目中运行一个类。此方法是否仅适用于加载 JAR?我希望我的程序支持加载本地子类和外部 JAR。

【问题讨论】:

  • 引用Dianne Hackborn,一位从事 Android 工作的关键 Google 工程师:“ServiceLoader 是来自 Java 语言的东西,与 Android 并不真正相关。我建议不要使用它。”
  • 引用对我们这些在 Java 世界中使用 ServiceLoader 并尝试移植到 Android 的人没有帮助(无需重写大量代码)。我检查了 apk,正确的文件位于 META-INF/services 下(在我的情况下,没有像作者一样的 jars)。任何帮助解决将不胜感激。
  • 看来 getResource 正在“META-INF/services”文件夹上完成,这就是问题所在。 ClassLoader.getResource() 返回 null。我没有看到一个简单的方法。
  • 对高速报价感到抱歉,但有更多信息:ServiceLoader 正在使用 getResources(复数)来获取所有服务的 URL。 URL 是一个 jar:file "protocol" 并得到异常:"Provider "jar" not installed"。由于Android世界没有jar,所以没有用于URL解析的jar提供者。
  • URL.openStream() 有效!使用类的类加载器、Thead 的 contextClassLoader 和 ClassLoader.getSystemClassLoader() 进行测试(如果加载时 ClassLoader 为空,则使用:只有 getSystemClassLoader() 无法返回任何文件。要检查使用 load(class, classloader) 强制使用非系统加载器。

标签: android meta-inf serviceloader


【解决方案1】:

这是一个答案:

在某些时候,Android 删除了 ServiceLoader 中的 AccessControlContext 字段,现在 ServiceLoader 可以正常工作。正如我的 cmets 所指出的,这可以使用带有 Android Studio 的“开箱即用”OREO (API 26) Intel Atom x86 仿真器(也是全新下载)来重现。 24 小时后,ServiceLoader 不再包含 acc 字段(如使用相同模拟器的 Android Studio 调试器所示)。追溯到 API 24 的 Android SDK 不显示 acc 字段。

根据当前维护 ServiceLoader 代码的 Android 开发人员: 他不知道 ServiceLoader 曾经在 Android 中拥有 acc 字段(它确实如此,因为我们能够重现)并认为调试器/模拟器可能一直在使用 JDK 代码(但我显示 OpenJDK 代码可以正常工作)。在此过程中,错误的代码已更新,我不再能够重现。

确保您的操作系统是最新的,您应该不会再看到这种现象。

【讨论】:

  • 我将 ServiceLoader 的源代码从 Oracle 的 Java 8 复制到一个新类中进行测试,它运行良好。 Android Studio 生成的 ServiceLoader 的“反编译”代码看起来是正确的,但它肯定将 AccessControlContext 放在不应该的位置。可能缺少括号?我会看看我是否可以提交错误报告。
  • 向 Google 报告为 bug #123498358
  • 当我去为该错误为谷歌创建一个示例应用程序时,返回的ServiceLoader与2天前不同!我检查了 26 的 sdk 源(我的模拟器也是 26)并且源在某个时候被 Google 更改了。我不知道为什么在我最初的测试期间它在模拟器中使用旧代码。课程更改时,我已询问 Google。
【解决方案2】:

我将此添加为答案,但保留先前的“答案”以提供此解决方法的扩展代码详细信息。我正在努力将先前的答案结果作为错误报告给 Google。

由于 java.util.ServiceLoader 的 Android 实现被破坏(即使 System.getSecurityManager() == null,总是使用 AccessController.getContext() 填充内部 java.security.AccessControlContext 字段),解决方法是创建自己的ServiceLoader 类,将 OpenJDK for Java 8 中的代码复制到您的类中,添加 java.util 所需的特定导入而不使用 import java.util.*;,并在您的代码中调用该 ServiceLoader(您必须完全引用 ServiceLoader您创建的含糊不清)。

这并不优雅,但它是一种有效的解决方法!此外,您需要在 ServiceLoader.load() 调用中使用 ClassLoader。该 ClassLoader 必须是 YourClass.class.getClassLoader() 或该类的 ClassLoader 的子 ClassLoader。

【讨论】:

    【解决方案3】:

    虽然是老帖子了,但可能对别人还是有帮助的:

    当我运行或调试包含 ServiceLoader 类的项目时,我必须将 META-INF/services 文件夹放入 Eclipse 中的 src/ 文件夹中。 如果我尝试将项目导出为 Runnable jar 并尝试将该类与服务加载器一起使用,它永远不会起作用。 当我检查 jar 并解压缩它时,我在 src/META-INF/services 下找到了该文件夹。 只有当我也直接在jar的根目录中添加了META-INF文件夹时,它才开始起作用。

    虽然在 Eclipse 中我还没有找到一个修复程序,以确保它被正确导出...也许 ANT 脚本可以解决这个问题,但到目前为止还没有尝试过...

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-22
      • 2012-05-06
      • 2011-10-31
      • 2011-09-16
      相关资源
      最近更新 更多