Maven And Android

Managing your Android JOGL projects with Maven is, if not effortless, at least not difficult. The following article attempts to describe one method of producing a project that will compile and run unmodified on both Android and ordinary "desktop" systems.

The article presents a demo that illustrates the organization of a trivial OpenGL application, and covers the rationale and methods behind its construction.

Maven Modules
The intention in this article is to develop an extremely simple OpenGL program that simply clears the screen to a garish colour at sixty frames per second.

Typically, software projects have handled platform-specific code by using some form of conditional compilation. That is, the build system usually contains some sort of "if the current platform is P, then compile A, else compile B" statements. The recommended way to handle this with Maven is to break a project up into separate modules so that any platform specific code is isolated and can be enabled or disabled on a per-module basis.

Assuming then that the intention is to get the program to run unmodified on Android and "anything else that can create an OpenGL window", the first thing that needs to be done is the break the program up into separate parts. Specifically:


 * The "Android" frontend (the Activity, and associated code and classes)
 * The "desktop" frontend (the usual window and context creation code, called from the main procedure in typical programs)
 * The platform-independent OpenGL core

Project
Firstly, a simple POM file is written that declares three submodules and very little else. As can probably be seen from the source browser, each of the modules has a corresponding directory each containing POM files that will be described further on in the article.

This demo project is named jp4da. Prizes won't be awarded for working out what the acronym means!

Core
The platform-independent part of the program is probably the simplest, and is contained in the jp4da-core module.

A simple Example class that implements GLEventListener is all that's required.

The POM file for the jp4da-core module is very simple, but does have one unusual distinction: The declared dependencies for the project are, at first glance, somewhat lacking. Typically (according to the published instructions on setting up a JOGL Maven project), projects will explicitly depend on the |ga|1|a%3A%22jogl-all-main%22 jogl-all-main and |ga|1|a%3A%22gluegen-rt-main%22 gluegen-rt-main packages in order to have Maven automatically retrieve and use the native libraries associated with each project.

The POM file for jp4da-core clearly doesn't do this.

org.jogamp.jogl jogl-all 2.0.2

The reason for this is simple: Depending on a specific set of native libraries would be implicitly stating that module is intended to run on a particular platform, as opposed to being platform-independent. Instead, the module essentially just depends on the one JOGL package that will allow the Java compiler to type-check and compile the code, without giving any consideration to how the final application will be constructed. It will also become clear, when describing the Android module, why depending on the native library packages here would be actively harmful!

Desktop
The jp4da-desktop module implements an extremely simple main program that opens a NEWT window and runs the event listener implemented in the jp4da-core</tt> module.

The POM file is similarly unsurprising, and simply declares some dependencies on the native packages that were specifically ignored earlier.

Android
The jp4da-android module implements the Android front-end for the program.

The main Activity is very similar to the desktop code: It simply opens a window and instantiates the core's event listener class. None of this code should be particularly surprising to an Android or JOGL programmer.

The POM file for the project is, however, not quite so simple. The project file attempts to solve three separate problems:


 * Process all class files with the various Android tools required to produce Dalvik bytecode
 * Organize the correct dependencies on JOGL itself, excluding "bad" dependencies that Maven implicitly adds.
 * Produce a single APK package that follows the correct layout required by JOGL itself and includes all application assets

Processing class files
Thankfully, as of version 3.5.0, the Android Maven plugin handles all the necessary processing of class files without requiring any configuration on the part of the developer (apart from, perhaps, setting the ANDROID_HOME</tt> environment variable to point to the SDK tools).

With the plugin added to the build section of the POM file, no further configuration is required to process class files.

Dependencies
For running code on Android, JOGL provides separate jogl-all-android</tt> and gluegen-rt-android</tt> jar files. However, this is where the first problem arises due to Maven's transitive dependency handling: The jp4da-android</tt> project depends on jp4da-core</tt> which depends on jogl-all</tt>. The jp4da-android</tt> module will therefore also implicitly depend on jogl-all</tt>, which will now conflict with jogl-all-android</tt>!

There are two accepted methods to handle this problem:


 * Make jogl-all</tt> an optional dependency of the jp4da-core</tt> project. It is then the responsibility of jp4da-android</tt> to provide a dependency that satisfies all constraints. Or:
 * Have the jp4da-android</tt> module explicitly ignore the jogl-all</tt> dependency of jp4da-core</tt>.

Neither method has any particular advantages or disadvantages in this case, so in the interest of keeping things explicit, the second method was chosen. The <tt>jp4da-android</tt> module explicitly ignores the <tt>jogl-all</tt> dependency and provides its own instead:

<groupId>com.io7m.examples.jp4da</groupId> <artifactId>jp4da-core</artifactId> 1.0.0

<groupId>org.jogamp.jogl</groupId> <artifactId>jogl-all</artifactId>

<groupId>com.google.android</groupId> <artifactId>android</artifactId> 1.6_r2 provided

<groupId>org.jogamp.gluegen</groupId> <artifactId>gluegen-rt-android</artifactId> 2.0.2      <groupId>org.jogamp.gluegen</groupId> <artifactId>gluegen-rt</artifactId> 2.0.2      natives-android-armv6 <groupId>org.jogamp.jogl</groupId> <artifactId>jogl-all-android</artifactId> 2.0.2      <groupId>org.jogamp.jogl</groupId> <artifactId>jogl-all</artifactId> 2.0.2      natives-android-armv6 runtime

Producing an APK
Finally, it's necessary to actually produce an APK that can be installed onto devices.

The project as described so far will not work when installed, both because JOGL requires that certain files appear in specific places inside the APK and because native libraries have not been placed in the correct directory to be packaged. Additionally, the Android Maven plugin will, by default, include everything that isn't a class file from every dependency jar on the classpath into the final APK.

Excluding redundant files
So, firstly, the Android Maven plugin needs to be told not to include anything from the JOGL and GlueGen jar files. This is achieved by giving a regular expression against which the names of each jar file on the classpath will be tested. If a jar file is matched then the contents of the jar file are not included in the resulting APK. Essentially, specific jar files are blacklisted.

<groupId>com.jayway.maven.plugins.android.generation2</groupId> <artifactId>android-maven-plugin</artifactId> 10              apk <excludeJarResources> <excludeJarResource>jogl-.*\.jar$</excludeJarResource> <excludeJarResource>gluegen-rt-.*\.jar$</excludeJarResource> </excludeJarResources>

Copying assets and native libraries
Then, the standard Maven dependency plugin is used to unpack assets into the <tt>assets</tt> directory (so that the Android Maven plugin can find and process them), and also to unpack and place native libraries into the correct <tt>libs</tt> directory (again, so that the Android Maven plugin can find and process them).

The process is simple, and nothing that should be surprising to any reasonably experienced Maven user:

<groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> 2.6

<id>unpack-jogl-natives</id> generate-resources unpack <artifactItems> <artifactItem> <groupId>org.jogamp.jogl</groupId> <artifactId>jogl-all</artifactId> 2.0.2                  natives-android-armv6 <overWrite>true</overWrite> <outputDirectory>${project.basedir}/libs/armeabi</outputDirectory> lib*.so                </artifactItem> </artifactItems>

<id>unpack-jogl-assets</id> generate-resources unpack <artifactItems> <artifactItem> <groupId>org.jogamp.jogl</groupId> <artifactId>jogl-all-android</artifactId> 2.0.2                  <overWrite>true</overWrite> <outputDirectory>${project.basedir}/assets</outputDirectory> **/*.class </artifactItem> </artifactItems>

<id>unpack-gluegen-natives</id> generate-resources unpack <artifactItems> <artifactItem> <groupId>org.jogamp.gluegen</groupId> <artifactId>gluegen-rt</artifactId> 2.0.2                  natives-android-armv6 <overWrite>true</overWrite> <outputDirectory>${project.basedir}/libs/armeabi</outputDirectory> lib*.so                </artifactItem> </artifactItems>

Cleaning up
Finally, because the project has performed some rather non-standard (from the perspective of typical Maven usage) operations, it's good practice to use the Maven Clean plugin to clean up the manually unpacked files so that each build always starts from a completely clean slate.

<artifactId>maven-clean-plugin</artifactId> 2.5              ${project.basedir}/libs/armeabi libgluegen-rt.so                libnewt.so                 libjogl_mobile.so               <followSymlinks>false</followSymlinks>

${project.basedir}/assets META-INF/** com/** jogl/** jogamp/** javax/** <followSymlinks>false</followSymlinks>

Building
Running a build from the main project directory should proceed without issue:

$ mvn -C clean verify [INFO] Scanning for projects... [INFO] [INFO] Reactor Build Order: [INFO] [INFO] jp4da-core [INFO] jp4da-desktop [INFO] jp4da-android [INFO] jp4da ... [INFO] [INFO] Reactor Summary: [INFO] [INFO] jp4da-core ........................................ SUCCESS [1.905s] [INFO] jp4da-desktop ..................................... SUCCESS [0.658s] [INFO] jp4da-android ..................................... SUCCESS [21.129s] [INFO] jp4da ............................................. SUCCESS [0.001s] [INFO] [INFO] BUILD SUCCESS [INFO] [INFO] Total time: 24.688s [INFO] Finished at: Fri Jan 04 23:06:47 GMT 2013 [INFO] Final Memory: 22M/236M [INFO] $

The <tt>jp4da-android/target</tt> directory now contains a <tt>jp4da-android.apk</tt> file that can be installed onto devices or the Android emulator.

$ adb install jp4da-android/target/jp4da-android.apk 1026 KB/s (2334537 bytes in 2.221s) pkg: /data/local/tmp/jp4da-android.apk Success

The program running on an ordinary Linux desktop:



The program installed on the Android emulator:



The program running on the Android emulator: