Friday, 04 February 2011

Creating Android applications with Clojure, Part 1

Over the past few weeks, I have started doing some application development on the Android platform. Overall, it has been a pleasant experience. The documentation, while not the best, is fairly good. Being an experienced Java programmer, I was comfortable enough to start writing real code quickly.

Despite all of this, writing in Java just is not as much fun any more. There is too much boilerplate code, and the language is missing some key features, such as closures. So, I decided to look into developing with some alternate JVM languages such as Scala and Clojure

In this two-part series of posts, I will discuss my experiences with coding in Clojure for Android in an effort to document what has worked for me. First, I will concentrate on those issues that affect the user’s experience. In the next part I will write about the development process.

Loading, please wait…

By far, the biggest problem with a Clojure application on Android is the load time. On a device, it take at least five to ten seconds for an activity written in Clojure to load up. This may be acceptable for some long-running applications, such as a game, but it is likely to be frustratingly slow for many other applications.

Investigating the problem

Profiling an application during this long wait time reveals that it is due to to the loading of the class clojure.lang.RT. One of the primary functions of this class is to bootstrap the Clojure runtime so that all of the basic language functionality is available for your program.

Unfortunately, for a constrained environment such as a mobile phone, this simply takes too long. In fact, it is loading the clojure.core name space that is root of the problem, and some of that work is unnecessary.

Slimming down Clojure

The most direct way to solve this problem is to have less of the language to load. In fact, there are some parts of Clojure that you simply cannot use on an Android device, such as dynamic code generation and compilation, e.g. gen-class or defrecord. You can AOT compile code that uses these forms, but you cannot generate new types within your application at runtime.

Perhaps the most direct way to help resolve the loading issue to create a modified version of Clojure that omits some of this functionality, a sort of Mini-Clojure. Perhaps a better solution may be to somehow make it so that at start-up only a minimal set of functionality is available and the rest loads up either in the background or on demand. Either way, slimming clojure.core is not a trivial endeavour.

Bootstrapping your application

Presuming that you are not going to hack Clojure for a quick start-up time, how do you create a better user experience during start-up? Since the problem is caused by loading the clojure.lang.RT class, the key is to choose when and how that happens.

If you code your main activity in Clojure, this means that loading Clojure will happen before anything is displayed to the user. Ideally, what would be better is to show a splash screen or progress dialog while Clojure loads in the background.

The easiest way to do this is ensure that your initial activity is written in Java. Using this approach, the onCreate method of your activity launches another thread that will load Clojure in the background. Once it is loaded, it can call back to your activity in the GUI thread so that your application can continue. At this point, you have two options:

  1. Integrate your Java-based activity with your Clojure code by directing all of the various call-back methods to your Clojure code.
  2. Launch a second activity that is pure Clojure.

I have had success with the first approach. Once you get passed the long initial wait, the rest of the application seems to run as well as a Java-based program. I have not tried the second approach, but I do not see why it should not work.

I believe a long start-up time is the biggest obstacle to a user-friendly Clojure program on Android. However, by loading Clojure in the background, it is possible to distract the user with a progress bar, splash screen, or animation.

Application size

The second major problem for users is the size of a Clojure-based application. Most users will be running your program on a mobile where disk space is limited, RAM is rare, and bandwidth is expensive.

A minimal Clojure application including the full Clojure libraries will be about one megabyte to download. This may not seem too bad as far as applications go, but it is enormous compared to the minimal Java application of about twenty kilobytes.

Once downloaded, the platform will uncompress the minimal Clojure program to about four megabytes. Again, not too bad, but still relatively large given that it does nothing. In my experiments, deleting some of the unused libraries such as clojure.test and helps save some space, but not significantly.

It is hard to calculate exactly how much memory use can be attributed to Clojure, but my informal experiments indicate that it may be about 1.5 megabytes. It has certainly been my experience that small Clojure programs use much more memory than comparable Java programs. On Android, a small memory footprint is quite important as you do not want to have your program killed to free up memory. If your application is killed, the user will have to endure the long start-up time once more.

Final thoughts

I am certainly happy to note that you can do Android development in Clojure, but the current standard Clojure library for the JVM does not make it easy. The good news is that good Android support is a goal for the Clojure development team. I believe that given some attention, it is quite feasible to develop a stream-lined Clojure library suitable for the Android platform1.

Nonetheless, it is possible to use the above bootstrapping technique to create a user experience better than a ‘black screen of death’. Given your particular application, it may even be perfectly acceptable.

Coming in part two

In part two, I will shift focus away from the user to describe how the development process works with Clojure. In particular, I will examine how the inability to dynamically generate code on the platform impacts development and some techniques for working around the problem.

  1. In fact, if someone is interested in sponsoring the work, I would be delighted to take on the task.