Status Update
Comments
ma...@gmail.com <ma...@gmail.com> #2
Yes, this issue is a very big pain in the rear. I also need META-INF/services folder for registering my own CharsetProvider (http://developer.android.com/reference/java/nio/charset/Charset.html ). Right now I've created factory class to get desired Charset, but it's not a desired solution. Please do NOT remove this folder during apk build time.
an...@gmail.com <an...@gmail.com> #3
Though i can be wrong, i expect this feature not being hard to fix, right ?
Anyways, I've found a hack that enables to use ServiceLoader :
1. Create a custom ClassLoader that changes calls through "META-INF**" to something else (For example "META_INF". I've attached a file)
2. Create your own Application and define it as your Application in your Manifest
3. Use Thread.currentThread().setContextClassLoader() in Application.onCreate() using "new MetaInfCircumventClassLoader(getClassLoader)".
4. Place your services in "META_INF/services". If your project uses libraries (such as jar), you need to copy their META-INF folder into your META_INF one.
It worked so far for me, because new Threads inherit the context ClassLoader that has been set.
I think, there could be solutions that do not involve creating an Application (as Android documentation discourages to do so), but i didn't try.
Anyways, I've found a hack that enables to use ServiceLoader :
1. Create a custom ClassLoader that changes calls through "META-INF**" to something else (For example "META_INF". I've attached a file)
2. Create your own Application and define it as your Application in your Manifest
3. Use Thread.currentThread().setContextClassLoader() in Application.onCreate() using "new MetaInfCircumventClassLoader(getClassLoader)".
4. Place your services in "META_INF/services". If your project uses libraries (such as jar), you need to copy their META-INF folder into your META_INF one.
It worked so far for me, because new Threads inherit the context ClassLoader that has been set.
I think, there could be solutions that do not involve creating an Application (as Android documentation discourages to do so), but i didn't try.
ma...@gmail.com <ma...@gmail.com> #4
Hey, thanks for the info. Given solution is quite ok, but it may crash when I'll try to get Charset from the other thread then the main application thread. Of course I can call setContextClassLoader() each time before I use Charset in the new thread, but... :). It would be really nice if in next Build Tools G-guys change this.
an...@gmail.com <an...@gmail.com> #5
Just a precision : I tested it in a multi-thread context and it worked. So as i said in my previous post, i believe that once set, the context ClassLoader is inherited by Threads created afterwards (when creating inside the Thread whose ClassLoader has been set or one of its children).
I'll give more details if it happens that some cases contradict this guessed "rule"
I'll give more details if it happens that some cases contradict this guessed "rule"
ma...@gmail.com <ma...@gmail.com> #6
If it works as you described, then you're my saviour :). I use custom Application for exception logging (Thread.setDefaultUncaughtExceptionHandler()), so adding your ClassLoader won't be a problem for me.
an...@gmail.com <an...@gmail.com> #7
Here's an Android test project that shows what i explained : once the context ClassLoader set, child Thread will have theirs automatically set with the given context ClassLoader.
xa...@android.com <xa...@android.com>
an...@gmail.com <an...@gmail.com> #8
Some new edit : i've written an ant build that copies any '*/META-INF' folder to 'gen/META_INF' enabling to keep the well know 'META-INF' name in sources.
The files are then included in the .apk at the root in a 'META_INF' folder.
The file is attached.
I've then made a custom builder that calls for the script before any other builder.
Here's the build command from the .project file :
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>auto,full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value><project>/.externalToolBuilders/AndroidMetaInfCircumventer.launch</value>
</dictionary>
</arguments>
</buildCommand>
Notes :
- This has to be repeated for every project that includes a 'META-INF' folder
- This hack doesn't resolve parent projects or libraries on the build path whose 'META-INF' folder are stripped during .apk build
=> This is only a poor hack solution. Enabling build tools to really include the 'META-INF' folder would really make the difference !
Thanks =)
The files are then included in the .apk at the root in a 'META_INF' folder.
The file is attached.
I've then made a custom builder that calls for the script before any other builder.
Here's the build command from the .project file :
<buildCommand>
<name>org.eclipse.ui.externaltools.ExternalToolBuilder</name>
<triggers>auto,full,incremental,</triggers>
<arguments>
<dictionary>
<key>LaunchConfigHandle</key>
<value><project>/.externalToolBuilders/AndroidMetaInfCircumventer.launch</value>
</dictionary>
</arguments>
</buildCommand>
Notes :
- This has to be repeated for every project that includes a 'META-INF' folder
- This hack doesn't resolve parent projects or libraries on the build path whose 'META-INF' folder are stripped during .apk build
=> This is only a poor hack solution. Enabling build tools to really include the 'META-INF' folder would really make the difference !
Thanks =)
[Deleted User] <[Deleted User]> #9
According to http://stackoverflow.com/a/9114677/14731 ApkBuilder.java deliberately strips the META-INF directory.
Google, can you comment? Was this by design or by mistake? If by design, how are we supposed to use ServiceLoader?
Google, can you comment? Was this by design or by mistake? If by design, how are we supposed to use ServiceLoader?
xa...@android.com <xa...@android.com> #10
This was by mistake. The new Gradle build system doesn't do this anymore (starting with 0.7.0) however there are plenty of case when you'll have collisions between different jars with the same files under META-INF/ and you want to be able to control what happens in this case.
The Ant build or ADT do not lend well to controlling this. I'm not sure we'll fix this in Ant/ADT.
The Ant build or ADT do not lend well to controlling this. I'm not sure we'll fix this in Ant/ADT.
an...@gmail.com <an...@gmail.com> #11
Hi. Thanks for your reply.
Why not avoiding collision by prefixing files that are known to often be duplicated ?
I mean, files like Manifest, Notice, ReadMe, etc ... Could be removed like done actually or renamed using for example the name of the jar they're coming from as a prefix ?
Other files like services or properties shall be unique, could be merged.
At least, could it be that you provide a mechanism to explicitly include files in META-INF coming from jars or being custom config files ?
Why not avoiding collision by prefixing files that are known to often be duplicated ?
I mean, files like Manifest, Notice, ReadMe, etc ... Could be removed like done actually or renamed using for example the name of the jar they're coming from as a prefix ?
Other files like services or properties shall be unique, could be merged.
At least, could it be that you provide a mechanism to explicitly include files in META-INF coming from jars or being custom config files ?
tw...@gmail.com <tw...@gmail.com> #13
Is this supposed to work for Gradle build system >= 0.7.0 when the META-INF/* files are not in any .jar:s but in build/classes/debug/META-INF/* ? that is when they are directly in the project that generates the .apk...
I'm usinghttp://metainf-services.kohsuke.org/ to auto-generate my META-INF/services/* files, and they are present in build/classes/debug/META-INF/* - but not in the .apk.
Seems like a bug.
I used:
dependencies {
...
compile 'org.kohsuke.metainf-services:metainf-services:1.5'
}
And then added @MetaInfServices to some class implementing an interface.
I'm using
Seems like a bug.
I used:
dependencies {
...
compile 'org.kohsuke.metainf-services:metainf-services:1.5'
}
And then added @MetaInfServices to some class implementing an interface.
tw...@gmail.com <tw...@gmail.com> #14
It seems to originate from com.android.sdklib.build.ApkBuilder,
line 959: !folderName.equalsIgnoreCase("META-INF") &&
Is there any particular reason why it is excluded from the APK?
line 959: !folderName.equalsIgnoreCase("META-INF") &&
Is there any particular reason why it is excluded from the APK?
xa...@android.com <xa...@android.com> #15
right now it's only for jar files. I'll have to fix this.
tw...@gmail.com <tw...@gmail.com> #16
A temporary fix for #14, #15:
def jarFile = "metainfservices.jar"
android.applicationVariants.all{ v ->
def path = '/META-INF/services'
def mf = "${v.name }MetaINFServicesJar"
task "${mf}" (type: Jar) {
from "${buildDir}/classes/${v.name }${path}"
into path
archiveName "${v.name }/${jarFile}"
}
v.javaCompile.finalizedBy "${mf}"
}
dependencies {
debugCompile files( "build/libs/debug/${jarFile}" )
debugReleaseCompile files( "build/libs/debugRelease/${jarFile}" )
releaseCompile files( "build/libs/release/${jarFile}" )
}
def jarFile = "metainfservices.jar"
android.applicationVariants.all{ v ->
def path = '/META-INF/services'
def mf = "${
task "${mf}" (type: Jar) {
from "${buildDir}/classes/${
into path
archiveName "${
}
v.javaCompile.finalizedBy "${mf}"
}
dependencies {
debugCompile files( "build/libs/debug/${jarFile}" )
debugReleaseCompile files( "build/libs/debugRelease/${jarFile}" )
releaseCompile files( "build/libs/release/${jarFile}" )
}
ph...@riand.com <ph...@riand.com> #17
Was the SDK updated? because I can still see the issue.
Note that META-INF/services is not the only candidate. With servlet 3.0, resources can be packaged in META-INF/resource. We have several jar with such resources and we can't share them with our Android components.
Note that META-INF/services is not the only candidate. With servlet 3.0, resources can be packaged in META-INF/resource. We have several jar with such resources and we can't share them with our Android components.
ph...@riand.com <ph...@riand.com> #18
Hello Google - any news on this? This is pretty serious issue when using external libraries.
As the content of the META-INF/services files is well defined, a great behavior would aggregate all the files corresponding to a specific service into a single one. A simple concatenation would do the trick. That would work similarly to the JVM when multiple jar files are used.
As the content of the META-INF/services files is well defined, a great behavior would aggregate all the files corresponding to a specific service into a single one. A simple concatenation would do the trick. That would work similarly to the JVM when multiple jar files are used.
ol...@gmail.com <ol...@gmail.com> #19
Hi. We desperately needed to make it work under Android and we made it.
In general what one can do, is to wrap META-INF/services/com.remote.Interface put into simple .jar file and add this jar into android /libs directory.
Now Android does not tinker with META-INF, but when ServiceLoader tries to find the resource META-INF/services/ it will succeed as it is present in your class path due to .jar inclusion.
All you need to tinker with gradle to build this .jar for you or you can do it manually if you don't expect often service implemetnation updates
In general what one can do, is to wrap META-INF/services/com.remote.Interface put into simple .jar file and add this jar into android /libs directory.
Now Android does not tinker with META-INF, but when ServiceLoader tries to find the resource META-INF/services/ it will succeed as it is present in your class path due to .jar inclusion.
All you need to tinker with gradle to build this .jar for you or you can do it manually if you don't expect often service implemetnation updates
ma...@gmail.com <ma...@gmail.com> #20
I use META-INF/services in Android Studio without any problems (no go in Eclipse).
You just need to:
- right click on <project_name>/src/main (where you have /java and /res folders),
- select New > Folder > Java Resources Folder,
- click Finish (do not change Folder Location),
- right click on new /resources folder,
- select New > Directory
- enter "META-INF" (without quotes),
- right click on /resources/META-INF folder,
- select New > Directory
- enter "services" (without quotes)
- copy any file you need into /resources/META-INF/services
Those files will be included in your apk and ServiceLoader will detect those.
I use this with custom java.nio.charset.spi.CharsetProvider and it works splendid.
You just need to:
- right click on <project_name>/src/main (where you have /java and /res folders),
- select New > Folder > Java Resources Folder,
- click Finish (do not change Folder Location),
- right click on new /resources folder,
- select New > Directory
- enter "META-INF" (without quotes),
- right click on /resources/META-INF folder,
- select New > Directory
- enter "services" (without quotes)
- copy any file you need into /resources/META-INF/services
Those files will be included in your apk and ServiceLoader will detect those.
I use this with custom java.nio.charset.spi.CharsetProvider and it works splendid.
pa...@gmail.com <pa...@gmail.com> #21
Can confirm the above solution works (#22)
xa...@android.com <xa...@android.com>
ph...@riand.com <ph...@riand.com> #22
Yes, but the problem is with Eclipse. This doesn't seem hard to fix, at least with a simple solution. Google any news? We are all spending a enormous amount of time trying to work this around. Please, update the ADT. We cannot solely rely on Android and gradle.
te...@gmail.com <te...@gmail.com> #23
@Xav,
Is there a work-around available so that we can "merge" the files with similar names under META-INF/services/ from various libraries into a single one?
My situation is as follows (AS 1.1.0; gradle plugin 1.1.0): I have a library that defines a Service API (say, MyService). I then have multiple libraries that implement this service.
lib1 ==> MyServiceImpl1
lib2 ==> MyServiceImpl2
Currently, the lib1 and lib2 projects declare in *their respective* META-INF/services/ folders the concrete implementation classes of MyService.
lib1/src/main/resources/META-INF/services/com.example.MyService ==> com.lib1.MyServiceImpl1
lib2/src/main/resources/META-INF/services/com.example.MyService ==> com.lib2.MyServiceImpl2
Now, in my Android app I have:
compile project(':lib1')
compile project(':lib2')
This fails (as expected) with an error complaining about duplicate com.example.MyService files.
I can move the declaration of the concrete implementations from the various libraries to a single file in the app project - but that defeats the purpose.
Is there a work-around available so that we can "merge" the files with similar names under META-INF/services/ from various libraries into a single one?
My situation is as follows (AS 1.1.0; gradle plugin 1.1.0): I have a library that defines a Service API (say, MyService). I then have multiple libraries that implement this service.
lib1 ==> MyServiceImpl1
lib2 ==> MyServiceImpl2
Currently, the lib1 and lib2 projects declare in *their respective* META-INF/services/ folders the concrete implementation classes of MyService.
lib1/src/main/resources/META-INF/services/com.example.MyService ==> com.lib1.MyServiceImpl1
lib2/src/main/resources/META-INF/services/com.example.MyService ==> com.lib2.MyServiceImpl2
Now, in my Android app I have:
compile project(':lib1')
compile project(':lib2')
This fails (as expected) with an error complaining about duplicate com.example.MyService files.
I can move the declaration of the concrete implementations from the various libraries to a single file in the app project - but that defeats the purpose.
ph...@riand.com <ph...@riand.com> #24
Hello Google, any news on this one? This is a critical one when using external libraries, in particular when using webjars within an hybrid app (META-INF/resources are not included)
be...@google.com <be...@google.com>
be...@google.com <be...@google.com>
ru...@gmail.com <ru...@gmail.com> #25
Hi there!
Check out how maven solves this problem with maven-shade-plugin and a transformer:
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
maven-shade-plugin just merges all jars into one uber jar and this transformer merges all META-INF/services/* files.
For instance, both jai-imageio-core-1.3.0.jar and jai-imageio-jpeg2000-1.3.0.jar have the same META-INF/services/javax.imageio.spi.ImageReaderSpi (among others).
The first jar contains:
com.github.jaiimageio.impl.plugins.wbmp.WBMPImageReaderSpi
com.github.jaiimageio.impl.plugins.bmp.BMPImageReaderSpi
com.github.jaiimageio.impl.plugins.pcx.PCXImageReaderSpi
com.github.jaiimageio.impl.plugins.pnm.PNMImageReaderSpi
com.github.jaiimageio.impl.plugins.raw.RawImageReaderSpi
com.github.jaiimageio.impl.plugins.tiff.TIFFImageReaderSpi
the second jar contains
com.github.jaiimageio.jpeg2000.impl.J2KImageReaderSpi
Then, this transformer merges the contents of the two instances of this service into one service META-INF/services/javax.imageio.spi.ImageReaderSpi,
like this:
com.github.jaiimageio.impl.plugins.wbmp.WBMPImageReaderSpi
com.github.jaiimageio.impl.plugins.bmp.BMPImageReaderSpi
com.github.jaiimageio.impl.plugins.pcx.PCXImageReaderSpi
com.github.jaiimageio.impl.plugins.pnm.PNMImageReaderSpi
com.github.jaiimageio.impl.plugins.raw.RawImageReaderSpi
com.github.jaiimageio.impl.plugins.tiff.TIFFImageReaderSpi
com.github.jaiimageio.jpeg2000.impl.J2KImageReaderSpi
So, the magic is in the transformer, and we can implement whatever transformer we like and use it with this maven-shade-plugin plugin.
I used this myself and it works like a charm with java.util.ServiceLoader.
Cheers!
Check out how maven solves this problem with maven-shade-plugin and a transformer:
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer" />
maven-shade-plugin just merges all jars into one uber jar and this transformer merges all META-INF/services/* files.
For instance, both jai-imageio-core-1.3.0.jar and jai-imageio-jpeg2000-1.3.0.jar have the same META-INF/services/javax.imageio.spi.ImageReaderSpi (among others).
The first jar contains:
com.github.jaiimageio.impl.plugins.wbmp.WBMPImageReaderSpi
com.github.jaiimageio.impl.plugins.bmp.BMPImageReaderSpi
com.github.jaiimageio.impl.plugins.pcx.PCXImageReaderSpi
com.github.jaiimageio.impl.plugins.pnm.PNMImageReaderSpi
com.github.jaiimageio.impl.plugins.raw.RawImageReaderSpi
com.github.jaiimageio.impl.plugins.tiff.TIFFImageReaderSpi
the second jar contains
com.github.jaiimageio.jpeg2000.impl.J2KImageReaderSpi
Then, this transformer merges the contents of the two instances of this service into one service META-INF/services/javax.imageio.spi.ImageReaderSpi,
like this:
com.github.jaiimageio.impl.plugins.wbmp.WBMPImageReaderSpi
com.github.jaiimageio.impl.plugins.bmp.BMPImageReaderSpi
com.github.jaiimageio.impl.plugins.pcx.PCXImageReaderSpi
com.github.jaiimageio.impl.plugins.pnm.PNMImageReaderSpi
com.github.jaiimageio.impl.plugins.raw.RawImageReaderSpi
com.github.jaiimageio.impl.plugins.tiff.TIFFImageReaderSpi
com.github.jaiimageio.jpeg2000.impl.J2KImageReaderSpi
So, the magic is in the transformer, and we can implement whatever transformer we like and use it with this maven-shade-plugin plugin.
I used this myself and it works like a charm with java.util.ServiceLoader.
Cheers!
Description
But during build of the apk, ApkBuilder excludes the 'META-INF' folder.
Therefore, ClassLoader.getResource("META-INF/services/**") will always return null.
I'm using ADT 22.0.5 on Eclipse Indigo
STEPS TO REPRODUCE:
1. Create an Android Project
2. Add a META-INF/services/some.file file that's on the classpath and build (f.e. let your project by a library and create a second project that references it)
EXPECTED RESULTS:
The file should be present in the apk (or the builded jar file in case of the example)
OBSERVED RESULTS:
The file isn't present in the apk (or in the jar that in the dependencies or your project using the library)