pátek 15. února 2008

Running native Java bytecode on Google Android?

As you probably know, the Android platform is not Java. The Dalvik virtual machine (the VM that comes with Android) executes its own bytecode rather than Java.

So Android development goes like this:
  1. You write Java code.

  2. The Java source is compiled into Java bytecode (.class files).

  3. The Java bytecode is translated into a Dalvik bytecode using the dx tool - a tool that comes with the Android SDK. (This tool is external to Android - it doesn't run inside the Android but in the development environment.)

  4. The Dalvik bytecode is executed by the Dalvik VM on the Android.
The differences from Java are actually not that great. (This was demonstrated by Karl Pauls, who was able to get Apache Felix OSGi to run on Android with only simple modifications to the Felix classloaders, as discussed here.) However, external translation of the Felix's Java bytecode into the Dalvik bytecode via the dx tool is still required, which makes it hard to install bundles directly from Internet bundle repositories (like OBR) unless the bundles in the repositories are already translated into the Dalvik bytecode.

This is a major limitation in practice, and I found it when I was following Karl's steps to get Felix running on Android. I figured there had to be a way around this.

I started with the dx tool, since it's the only code I know of that's capable of doing the translation. It turns out that the tool's executable is just a thin shell script around a jar file (<android root>/tools/lib/dx.jar)! The next steps were pretty obvious:
  • Try to run the dx tool on itself (actually, on the dx.jar that implements its functionality).
  • Push the translated dx.jar onto the Android emulator and see if it works.
This worked, which told me that a little coding was all that was needed to make it happen online (= on demand).

After a bit of exploring of the classes inside the dx.jar I was able to come up with an implementation of a classloader (com.cloudsmith.android.runner.TranslatingClassLoader) capable of translating Java bytecode into the Dalvik bytecode on demand i.e. when a Java bytecode class needs to be loaded

You can find an Eclipse project containing the classloder and a simple test application in this SVN repository (note that you will need to have Buckminster installed in your Eclipse IDE for the project builds to work properly).

Since this worked, I decided to incorporate the concept into the Apache Felix classloader previously modified by Karl. You can find the project here (note that you will need Buckminster for this project as well). With the modification introduced by this project the Felix is capable of loading and executing unmodified (containing Java bytecode classes only) bundles.

This can be demonstrated by loading the telnetd bundle directly from an OBR repository:
C:\>cd C:\Workspaces\workspace-android\org.apache.felix.android

C:\Workspaces\workspace-android\org.apache.felix.android>deploy.bat
1220 KB/s (0 bytes in 878988.000s)
1593 KB/s (0 bytes in 51000.000s)
755 KB/s (0 bytes in 12090.000s)
1345 KB/s (0 bytes in 129143.000s)
3 KB/s (0 bytes in 3329.001s)
8 KB/s (0 bytes in 137.000s)

C:\Workspaces\workspace-android\org.apache.felix.android>adb shell# sh /data/felix/felix.sh
sh /data/felix/felix.sh

Welcome to Felix.
=================

DEBUG: WIRE: 1.0 -> org.osgi.service.packageadmin -> 0
DEBUG: WIRE: 1.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 1.0 -> org.ungoverned.osgi.service.shell -> 1.0
DEBUG: WIRE: 1.0 -> org.apache.felix.shell -> 1.0
DEBUG: WIRE: 1.0 -> org.osgi.service.startlevel -> 0
DEBUG: WIRE: 2.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 2.0 -> org.apache.felix.shell -> 1.0
DEBUG: WIRE: 3.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 3.0 -> org.osgi.service.obr -> 3.0
-> DEBUG: WIRE: 3.0 -> org.apache.felix.shell -> 1.0


-> obr list
obr list
Apache Felix Bundle Repository (0.8.0.incubator)
Apache Felix Shell Service (0.8.0.incubator)
Apache Felix Shell TUI (0.8.0.incubator)
telnetd (1.0.0)
-> obr deploy telnetd
obr deploy telnetd
Target resource(s):
-------------------
telnetd (1.0.0)

Deploying...done.
-> ps
ps
START LEVEL 1
ID State Level Name
[ 0] [Active ] [ 0] System Bundle (1.1.0.SNAPSHOT)
[ 1] [Active ] [ 1] Apache Felix Shell Service (1.0.2)
[ 2] [Active ] [ 1] Apache Felix Shell TUI (1.1.0.SNAPSHOT)
[ 3] [Active ] [ 1] Apache Felix Bundle Repository (1.1.0.SNAPSHOT)
[ 5] [Installed ] [ 1] telnetd (1.0.0)
-> start 5
start 5
DEBUG: WIRE: 5.0 -> org.osgi.framework -> 0
DEBUG: WIRE: 5.0 -> org.ungoverned.osgi.service.shell -> 1.0
DEBUG: WIRE: 5.0 -> com.softsell.open.osgi.telnetd -> 5.0
DEBUG: WIRE: 5.0 -> dtw.telnetd -> 5.0
[12/Feb/2008:01:50:42 GMT] Listening to Port 6,623 with a connectivity queue size of 5.
-> [12/Feb/2008:01:51:44 GMT] connection #1 made.

While the outlined approach is a bit of hacking it is still a proof of concept. And I hope that once Google releases the complete source codes of the Andorid SDK it won't be necessary any more or at least it will be possible to simplify/optimize it substantially.