Jenkins : Jenkins Agent on DalvikVM in Android

Please Help

This is a work in progress. Please help by adding a comment, or making contributions or edits to this wiki doc. Please also see the jenkinsci-dev mailing list thread.

Overview

While compiling code on an Android device does not make any sense, driving tests on Android devices does. The Android Emulator Plugin by Christopher Orr exploits multi configuration jobs to test Android applications on many different screen sizes and configurations. Unfortunately, the Emulator only emulates ARMv5 and it lacks a real GPU. This means that some software must be tested on bare-metal hardware. Running the agent jar on a host PC machine to communicate with USB devices introduces undesired complexity and, as some of you may know, the Android Debug Bridge has some issues, especially while under a stressful load.
Currently our solution employs multiple pc agents each with multiple usb adb connections to many devices. The pc agents send commands to the phones over a tcp connection established using busybox, netcat, and bash. To prevent more than one job using the same phone at a time the devices are locked as a "busy resource" using an external web application.
This configuration ads more complexity and maintenance than is needed by duplicating much of the jenkins distributed execution core. Ideally we could leverage all of the work that has already been implemented in Jenkins by running the agent jar directly on the Android device (or emulator).

Approach

Convert the agent jar to Dalvik bytecode and launch it from adb shell or include it in an Android application and launch it from an Android Service. This worked swimmingly until the agent attempted to download java classes from the master using a RemoteClassLoader.

Issues

Agent downloads classes dynamically

A different set of classes can always be remotely loaded by any client. Some classes come from plugin configurations.

Dalvik bytecode

A major issue is that Android does not run native java bytecode. All bytecode must be converted to Dalvik bytecode before being executed on the Android device. The Apache Felix Project has an excellent article about deploying the OSGi client to Android which details how to convert java bytecode to dex bytecode at compile time. We need to be able to do this at runtime.

Not the whole Runtime

The following packages are not supported in Android:

  • java.applet
  • java.awt
  • java.beans
  • java.lang.management
  • java.rmi
  • javax.accessibility
  • javax.activity
  • javax.imageio
  • javax.management
  • javax.naming
  • javax.print
  • javax.rmi
  • javax.security.auth.kerberos
  • javax.security.auth.spi
  • javax.security.sasl
  • javax.swing
  • javax.transaction
  • javax.xml (except javax.xml.parsers)
  • org.ietf.*
  • org.omg.*
  • org.w3c.dom.* (sub-packages)
    From http://www.zdnet.com/blog/burnette/java-vs-android-apis/504. Fortunately there are work-arounds to this issue.

Our Own Custom RemoteClassLoader

It seems that the most reliable way to run the agent jar on an Android device is to create an overloaded ClassLoader that converts the java bytecode to Dalvik bytecode before attempting to load it.
This question has been asked before on different ways to load java bytecode into DalvikVM at runtime. It seems that the Dalvik team does have it on their radar but as Jesse Wilson explains in his Stack Overflow post:

The Dalvik team would like to build a first-class runtime code generation library. We're tracking the feature request as Android bug 6322. Unfortunately, we have a very long list of performance and correctness issues, so I can't give you a timeline for when we'll spend time on this issue.

There are some alternatives, but they will all take some work:

  • Run your application on a standard JVM and exercise all runtime code generation there. Dump the .class files from memory to files, and then run dx on those files. If you're quite sophisticated, you could integrate all of this work into your build.
  • Include the open source dx tool as a project library, and execute it programatically from within your application, possibly in your application's classloader. This will bloat your application's binary.

The Implementation

I like the second option and dx.jar comes with the Android SDK. Upon reviewing the source it appears that most of the work is done by com.android.dx.dex.cf.CfTranslator.translate(). There is some example code that converts java classes to dex at runtime over at Jythonroid: https://jythonroid.googlecode.com/svn-history/r34/branches/Jythonroid/src/org/python/debug/FixMe.java

It seems to me that the most elegant solution would be to update hudson.remoting.RemoteClassLoader to do a System.getProperty() check against java.vm.name to check for "Dalvik" and then insert the branch to the conversion logic at runtime.