I bought a shiny new Android phone – it’s a HTC Desire, I like it, and having the internet in my pants makes me feel like I’m living in the future – and I’ve been doing a little bit of development for it. There’s another app idea I want to work on, but I don’t want to be working on it in java. I want something more functionalish, you see, because doing real stuff in functional languages has appealed to me for a long time.
So, I’ve been trying to get an app written in Clojure to happen. Thankfully, someone else has already blazed this particular trail. Stupidly, I thought that meant it would be easy. One attempt at compiling a fresh-baked android app with clojure later, my expectation was no longer one of ease. What ensued was a process of ambling forwards and backwards through the process until victory. This blog post is mostly about hopefully making error messages line up with solutions in google.
The first error message?
[echo] Converting compiled files and external libraries into /Users/iain/Code/chordtastic/bin/classes.dex…
[apply] UNEXPECTED TOP-LEVEL ERROR:
[apply] java.lang.OutOfMemoryError: Java heap space
[apply] at com.android.dx.util.IntList.(IntList.java:87)
I went back to the very start. Grabbed the hello-dalvik.sh script that somebody posted on the mailing list. Got it working (for me, it works trivially with the emulator but fails against the actual device). If you’re having trouble, that’s probably a good place to start. In my case, I discovered that I could solve the out of memory error by using a “target=android-8” line instead of “target=android-3” in the default.properties file. It looks like editing this file is discouraged, and you should have the sdk do it for you by issuing this command (which git tells me makes precisely that change in that file, no more, no less):
$ android update project -p $PROJECT_DIR -t 7
And then, like magic, dex stops running out of memory. Trumpets! Parades! A litter bearing the king!
Not yet. It’s very slow. There are some ideas on how to fix it, but I haven’t looked at them yet. Make it work, then make it fast.
So, making it work. At this stage if I do “ant install” in my project directory (I used this as a starting point of sorts), the clojure code gets compiled and pushed into the emulator just fine, but it just crashes when I try to start it. Taking a look at our friend Mr Adb Logcat, we see the following:
E/AndroidRuntime( 433): Uncaught handler: thread main exiting due to uncaught exception
E/AndroidRuntime( 433): java.lang.ExceptionInInitializerError
E/AndroidRuntime( 433): at java.lang.Class.newInstanceImpl(Native Method)
E/AndroidRuntime( 433): at java.lang.Class.newInstance(Class.java:1479)
E/AndroidRuntime( 433): at android.app.Instrumentation.newActivity(Instrumentation.java:1021)
E/AndroidRuntime( 433): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2409)
E/AndroidRuntime( 433): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2512)
E/AndroidRuntime( 433): at android.app.ActivityThread.access$2200(ActivityThread.java:119)
E/AndroidRuntime( 433): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1863)
E/AndroidRuntime( 433): at android.os.Handler.dispatchMessage(Handler.java:99)
E/AndroidRuntime( 433): at android.os.Looper.loop(Looper.java:123)
E/AndroidRuntime( 433): at android.app.ActivityThread.main(ActivityThread.java:4363)
E/AndroidRuntime( 433): at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime( 433): at java.lang.reflect.Method.invoke(Method.java:521)
E/AndroidRuntime( 433): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:860)
E/AndroidRuntime( 433): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:618)
E/AndroidRuntime( 433): at dalvik.system.NativeStart.main(Native Method)
E/AndroidRuntime( 433): Caused by: java.lang.ExceptionInInitializerError
E/AndroidRuntime( 433): at clojure.lang.Namespace.(Namespace.java:34)
E/AndroidRuntime( 433): at clojure.lang.Namespace.findOrCreate(Namespace.java:176)
E/AndroidRuntime( 433): at clojure.lang.Var.internPrivate(Var.java:94)
E/AndroidRuntime( 433): at net.remvee.android.hello.ClojureHelloAndroid.(Unknown Source)
E/AndroidRuntime( 433): … 15 more
E/AndroidRuntime( 433): Caused by: java.lang.RuntimeException: java.io.FileNotFoundException: Could not locate clojure/core__init.class or clojure/core.clj on classpath:
E/AndroidRuntime( 433): at clojure.lang.RT.(RT.java:305)
E/AndroidRuntime( 433): … 19 more
E/AndroidRuntime( 433): Caused by: java.io.FileNotFoundException: Could not locate clojure/core__init.class or clojure/core.clj on classpath:
E/AndroidRuntime( 433): at clojure.lang.RT.load(RT.java:412)
E/AndroidRuntime( 433): at clojure.lang.RT.load(RT.java:381)
E/AndroidRuntime( 433): at clojure.lang.RT.doInit(RT.java:416)
E/AndroidRuntime( 433): at clojure.lang.RT.(RT.java:302)
So, that’s awesome. I spent a while being stupid here. I read that “Could not locate clojure/core__init.class” message to mean that it couldn’t find clojure at all, so I took the clojure code from the shell script and dragged it into ClojureHelloAndroid.clj, compiled it with the ant task, and ran it on the device with the dalvikvm command (if you try to do this, bear in mind that you need to slap a namespace on the class that you’re asking it to start: dalvikvm -classpath /data/app/net.remvee.android.hello.apk net.remvee.android.hello.ClojureHelloAndroid). At that point I was satisfied that the build process in the project was baking clojure in in a functioning sort of way.
Google google mailing list newsgroup off-topic posts later, I find some talk about classloaders in relation to that error message. And am reminded of dalvik’s non-javaness. And realised that the last four lines of that stack trace clearly indicated that clojure was, in fact, coming out to play, it was just that it was having some trouble loading other bits of itself in.
To the source code! In the clojure source tree, there’s a file src/jvm/clojure/lang/RT.java. It has a line like this:
final static public Var USE_CONTEXT_CLASSLOADER =
Var.intern(CLOJURE_NS, Symbol.create("*use-context-classloader*"), T);
And if you change that T to an F, then rebuild clojure, drop the new jar into your project’s directory, rebuild and reinstall, you might just find that it works. Clearly this is an awful cludge. I have five things now to do:
1) Tinker with dex stuff, to try to get the dexing time down
2) Get the app startup time down in the emulator
3) See if the whole house of cards stays up on the actual device
4) Do some actual development
5) Find a way of making it work without that particular awful cludge. I suspect this involves learning about what class loader is in play when the app launcher kicks off my application.