3

So I want to build an extensible android application where developers can add 'CustomDevice' classes and the main program will run them automatically without editing existing code.

I've read about Service Provider interface and thought that would be a nice way to go about it.

So I tested it and created an interface called 'ICustomDevice' which custom device classes are expected to implement.

I've created a class called 'DummyDevice' that implements ICustomDevice.

Both DummyDevice and ICustomDevice are in the same package "CustomDevicePackage".

So in my main program I run the following.

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

It always returns false, which means it's not finding the 'DummyDevice'

In my eclipse project I created a folder at 'src' called META-INF and under it, a subfolder called 'services'.

'Services' has a file named 'CustomDevicePackage.ICustomDevice' with a line of content 'CustomDevicePackage.DummyDevice'.

Am I doing it right? Every example I see about SPI is about loading JARS. I'm not loading a JAR, I'm trying to run a class in the same Project. Does this method only works for loading JARs? I want my program to support loading local subclasses and external JARs alike.

user1378063
  • 263
  • 1
  • 6
  • 9
  • 1
    Quoting [Dianne Hackborn](http://stackoverflow.com/a/5761705/115145), a key Google engineer working on Android: "ServiceLoader is stuff from the Java language that is not really relevant on Android. I recommend not using it." – CommonsWare Apr 14 '15 at 16:20
  • Quote doesn't help those of us using ServiceLoader in the Java world and trying to port to Android (without rewriting huge chunks of code). I have check the apk and the correct files are located under META-INF/services (in my case, no jars just like author). Any help to resolve would be appreciated. – user579013 Jan 28 '19 at 04:19
  • It appears that getResource is being done on the "META-INF/services" folder and that is where the issue occurs. ClassLoader.getResource() is returning null. I do not see an easy way around it. – user579013 Jan 28 '19 at 04:45
  • Sorry about the high speed quotes but further info: ServiceLoader is using getResources (plural) to get the URL(s) of all services. The URL is a jar:file "protocol" and get the exception: "Provider "jar" not installed". Since there is no jar in the Android world, there is no jar provider for URL parsing. – user579013 Jan 28 '19 at 05:08
  • URL.openStream() works! Tested with class loader of class, Thead's contextClassLoader, and ClassLoader.getSystemClassLoader() (used if ClassLoader is null in load: Only getSystemClassLoader() fails to return any files. Going to check using load(class, classloader) to force using non-system loader. – user579013 Jan 28 '19 at 05:33
  • It appears a Service is run in a security context which is causing ServiceLoader to fail to load any files. Researching to see if there is a resolution. – user579013 Jan 28 '19 at 06:03

3 Answers3

0

I am adding this as an answer but leaving the prior "answer" to provide extended code detail for this workaround. I am working on reporting the prior answer results as a bug to Google.

Because the Android implementation of java.util.ServiceLoader is broken (always populating internal java.security.AccessControlContext field with AccessController.getContext() even if System.getSecurityManager() == null), the workaround is to create your own ServiceLoader class by copying the code found at OpenJDK for Java 8 into your class, add specific imports required from java.util without using import java.util.*;, and call that ServiceLoader in your code (you will have to fully reference the ServiceLoader you created to over ambiguity).

This isn't elegant but it is a functional workaround that works! Also, you will need to use a ClassLoader in your ServiceLoader.load() call. That ClassLoader will either have to be YourClass.class.getClassLoader() or a child ClassLoader of the class' ClassLoader.

user579013
  • 197
  • 2
  • 9
0

Though it's an old post, This may be still be of some help to others:

When I was running or debugging a project that contained a ServiceLoader Class, I had to put the META-INF/services folder into the src/ folder in Eclipse. If I tried to export the project as Runnable jar and tried to use the class with the service loader, it never worked. When I checked the jar, unzipping it, I found the folder under src/META-INF/services though. Only when I also added the META-INF folder directly in the root directory of the jar, it started to work.

I haven't found a fix though inside Eclipse, that makes sure it gets exported right...maybe an ANT script can solve this issue, but so far no attempts made...

botg
  • 31
  • 5
-1

This is an answer:

At some point, Android removed the AccessControlContext field in ServiceLoader and ServiceLoader now works. As my comments indicate, this was reproduceable using the "out-of-the-box" OREO (API 26) Intel Atom x86 emulator with Android Studio (also fresh download). 24 hours later, ServiceLoader no longer contained the acc field (as shown in the Android Studio debugger with the same emulator). The Android SDKs dating back to API 24 do not show the acc field.

Per the Android developer currently maintaining the ServiceLoader code: He is not aware of ServiceLoader ever having the acc field in Android (it did as we were able to reproduce) and thought the debugger/emulator might have been using JDK code (but I showed the OpenJDK code works correctly). Somewhere along the way, the errant code was updated and I am no longer able to reproduce.

Be sure your OS is up-to-date and you should no longer see this phenomena.

user579013
  • 197
  • 2
  • 9
  • I copied the source of ServiceLoader from Oracle's Java 8 into a new class to test against and it works perfectly. The "decompiled" code for ServiceLoader that Android Studio produces looks correct but it is definitely putting an AccessControlContext where it shouldn't be. Possible missing parenthesis? I will see if I can file a bug report. – user579013 Jan 28 '19 at 06:55
  • Reported to Google as bug #123498358 – user579013 Jan 28 '19 at 15:16
  • When I went to create a sample app for Google for the bug, the ServiceLoader returned was different from 2 days ago! I checked the sdk source for 26 (my emulator is also 26) and the source was changed by Google at some point. Why it was using old code in the emulator during my initial testing, I do not know. I have asked Google when the class was changed. – user579013 Jan 29 '19 at 15:58