📜 ⬆️ ⬇️

Using Java 8 Libraries for Android Applications with Maven

Java 8 was released in early 2014, allowing Java developers to use very handy innovations to facilitate the programming of trivial tasks. Among them are lambda expressions, references to methods and constructors, the implementation of default interface methods at the language level and the JVM, as well as the use of the Stream API at the standard library level. Unfortunately, the lethargy of introducing such introductions affects the support of these tools on other Java-oriented software platforms. GWT and Android still do not have official support for at least Java 8 language tools. However, the spring SNAPSHOT versions of GWT 2.8.0 already supported lambda expressions. With Android, things are different, because here the work of lambda expressions depends not only on the compiler itself, but also on the execution environment. But with Maven, you can relatively easily solve the problem of using Java 8.

It so happened that I keep the entire code base for my projects on Maven due to the fact that:


General purpose libraries from this code base are written and connected to other modules in such a way that they can be used both in Java SE projects and in GWT or Android. But since Android is bad with Java 8, these libraries continue to remain on Java 6 or 7, just like the applications themselves from the code base on Android. However, after successful work with lambdas in GWT, there was a desire to migrate all of their code base to Java 8. Compiling and installing your libraries into a local repository is not a big deal:

<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> 

After installing the libraries in the local repository, you can, in principle, build the application itself. But in the “dex” process, the following error will occur:
')
 [INFO] UNEXPECTED TOP-LEVEL EXCEPTION: [INFO] com.android.dx.cf.iface.ParseException: bad class file magic (cafebabe) or version (0034.0000) [INFO] at com.android.dx.cf.direct.DirectClassFile.parse0(DirectClassFile.java:472) [INFO] at com.android.dx.cf.direct.DirectClassFile.parse(DirectClassFile.java:406) [INFO] at com.android.dx.cf.direct.DirectClassFile.parseToInterfacesIfNecessary(DirectClassFile.java:388) [INFO] at com.android.dx.cf.direct.DirectClassFile.getMagic(DirectClassFile.java:251) [INFO] at com.android.dx.command.dexer.Main.processClass(Main.java:665) [INFO] at com.android.dx.command.dexer.Main.processFileBytes(Main.java:634) [INFO] at com.android.dx.command.dexer.Main.access$600(Main.java:78) [INFO] at com.android.dx.command.dexer.Main$1.processFileBytes(Main.java:572) [INFO] at com.android.dx.cf.direct.ClassPathOpener.processArchive(ClassPathOpener.java:284) [INFO] at com.android.dx.cf.direct.ClassPathOpener.processOne(ClassPathOpener.java:166) [INFO] at com.android.dx.cf.direct.ClassPathOpener.process(ClassPathOpener.java:144) [INFO] at com.android.dx.command.dexer.Main.processOne(Main.java:596) [INFO] at com.android.dx.command.dexer.Main.processAllFiles(Main.java:498) [INFO] at com.android.dx.command.dexer.Main.runMonoDex(Main.java:264) [INFO] at com.android.dx.command.dexer.Main.run(Main.java:230) [INFO] at com.android.dx.command.dexer.Main.main(Main.java:199) [INFO] at com.android.dx.command.Main.main(Main.java:103) [INFO] ...while parsing foo/bar/FooBar.class 

This error means that dx cannot handle class files generated by the Java 8 compiler. Therefore, we include Retrolambda , which, in theory, should fix the situation:

 <plugin> <groupId>net.orfjackal.retrolambda</groupId> <artifactId>retrolambda-maven-plugin</artifactId> <version>2.0.6</version> <executions> <execution> <phase>process-classes</phase> <goals> <goal>process-main</goal> <goal>process-test</goal> </goals> </execution> </executions> <configuration> <defaultMethods>true</defaultMethods> <target>1.6</target> </configuration> </plugin> 

Unfortunately, foo/bar/FooBar.class belongs to the library and the error is not fixed. retrolambda-maven-plugin cannot cope with the task of retrolambda-maven-plugin application libraries in principle, since it can process class files only for the current module (and for this, you would need to process class files directly in the repository). That is, an application cannot use Java 8 libraries, but can use Java 8 code only in the current module. It can be solved like this:


The current implementation of the android-maven-plugin runs the dx with all dependencies indicated, which further aggravates dependency instrumentation in Java 8. Here’s what the android-maven-plugin runs like:

 $JAVA_HOME/jre/bin/java -Xmx1024M -jar "$ANDROID_HOME/sdk/build-tools/android-4.4/lib/dx.jar" --dex --output=$BUILD_DIRECTORY/classes.dex $BUILD_DIRECTORY/classes $M2_REPO/foo1-java8/bar1/0.1-SNAPSHOT/bar1-0.1-SNAPSHOT.jar $M2_REPO/foo2-java8/bar2/0.1-SNAPSHOT/bar2-0.1-SNAPSHOT.jar $M2_REPO/foo3-java8/bar3/0.1-SNAPSHOT/bar3-0.1-SNAPSHOT.jar 

Here, all three Java 8 libraries are sent to dx processing. In the plugin itself, it is not possible to manage the dependency filter that needs to be passed to dx . Why is it important to be able to manage such a filter? It can be assumed that some dependencies are already in a place more convenient for processing than the repository of artifacts. For example, in ${project.build.directory}/classes . This is where Java 8 dependencies can be processed using the retrolambda-maven-plugin .

For Maven, there is a plugin that can unpack the dependencies in the correct directory, which allows you to handle the dependencies as needed. For example:

 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.10</version> <executions> <execution> <phase>process-classes</phase> <goals> <goal>unpack-dependencies</goal> </goals> <configuration> <includeScope>runtime</includeScope> <includeGroupIds>foo1-java8,foo2-java8,foo3-java8</includeGroupIds> <outputDirectory>${project.build.directory}/classes</outputDirectory> </configuration> </execution> </executions> </plugin> 

I added support for several options for managing the dependency filter to the android-maven-plugin . Among them are filtering and inclusion ( excludes and includes ) by group identifier, artifact identifier and version identifier. Artifact identifiers and their versions can be omitted. All items identifying an artifact or group of artifacts must be separated by a colon. However, you can try Java 8 and Java 8 in the Android application, although the request to merge into the parent repository has not yet been accepted. To do this, you first need to assemble the fork itself:

 #      upstream  : PLUGIN_REVISION=a79e45bc0721bfea97ec139311fe31d959851476 #  : git clone https://github.com/lyubomyr-shaydariv/android-maven-plugin.git #   ,    : cd android-maven-plugin git checkout $PLUGIN_REVISION #  : mvn clean package -Dmaven.test.skip=true #   target,        Maven-: cd target cp android-maven-plugin-4.3.1-SNAPSHOT.jar android-maven-plugin-4.3.1-SNAPSHOT-$PLUGIN_COMMIT.jar #  pom.xml: cp ../pom.xml pom-$PLUGIN_COMMIT.xml sed -i "s/<version>4.3.1-SNAPSHOT<\\/version>/<version>4.3.1-SNAPSHOT-$PLUGIN_COMMIT<\\/version>/g" pom-$PLUGIN_COMMIT.xml #   : unzip android-maven-plugin-4.3.1-SNAPSHOT-$PLUGIN_COMMIT.jar META-INF/maven/plugin.xml sed -i "s/<version>4.3.1-SNAPSHOT<\\/version>/<version>4.3.1-SNAPSHOT-$PLUGIN_COMMIT<\\/version>/g" META-INF/maven/plugin.xml zip android-maven-plugin-4.3.1-SNAPSHOT-$PLUGIN_COMMIT.jar META-INF/maven/plugin.xml # , , : mvn org.apache.maven.plugins:maven-install-plugin:2.5.2:install-file -DpomFile=pom-$PLUGIN_COMMIT.xml -Dfile=android-maven-plugin-4.3.1-SNAPSHOT-$PLUGIN_COMMIT.jar 

After all this, you can configure your application's pom.xml :

 <!--   Java 8    --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <!--      Java 8     --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.10</version> <executions> <execution> <phase>process-classes</phase> <goals> <goal>unpack-dependencies</goal> </goals> <configuration> <includeScope>runtime</includeScope> <!--    Java 8  --> <includeGroupIds>foo1-java8,foo2-java8.foo3-java8</includeGroupIds> <outputDirectory>${project.build.directory}/classes</outputDirectory> </configuration> </execution> </executions> </plugin> <!--   --> <plugin> <groupId>net.orfjackal.retrolambda</groupId> <artifactId>retrolambda-maven-plugin</artifactId> <version>2.0.6</version> <executions> <execution> <phase>process-classes</phase> <goals> <goal>process-main</goal> <goal>process-test</goal> </goals> </execution> </executions> <configuration> <defaultMethods>true</defaultMethods> <target>1.6</target> </configuration> </plugin> <!-- DEX-   Java 8  (    target/classes   ,     dx)     APK --> <plugin> <groupId>com.simpligility.maven.plugins</groupId> <artifactId>android-maven-plugin</artifactId> <version>4.3.1-SNAPSHOT-a79e45bc0721bfea97ec139311fe31d959851476</version> <executions> <execution> <phase>package</phase> </execution> </executions> <configuration> <androidManifestFile>${project.basedir}/src/main/android/AndroidManifest.xml</androidManifestFile> <assetsDirectory>${project.basedir}/src/main/android/assets</assetsDirectory> <resourceDirectory>${project.basedir}/src/main/android/res</resourceDirectory> <sdk> <platform>19</platform> </sdk> <undeployBeforeDeploy>true</undeployBeforeDeploy> <proguard> <skip>true</skip> <config>${project.basedir}/proguard.conf</config> </proguard> <excludes> <exclude>foo1-java8</exclude> <exclude>foo2-java8</exclude> <exclude>foo3-java8</exclude> </excludes> </configuration> <extensions>true</extensions> <dependencies> <dependency> <groupId>net.sf.proguard</groupId> <artifactId>proguard-base</artifactId> <version>5.2.1</version> <scope>runtime</scope> </dependency> </dependencies> </plugin> 

That's all. It should be noted that this approach implies using only Java 8 language tools, and not standard libraries of the Stream API type. I also want to emphasize that using this technique, you can not only make friends with Android with applications and their dependencies written in Java 8, but also handle the bytecode of third-party dependencies as you like. I can not say that I completely like this solution in terms of elegance.

Perhaps in other project building systems everything is much simpler. I don’t even know if it could be easier in Maven itself, and whether this whole craftwork is not part of the bicycle industry, but, nevertheless, I was interested in forcing Maven to do what was required of him.

Source: https://habr.com/ru/post/266881/


All Articles