Creating Android applications with Clojure: Building with Ant
In part one of this series, I wrote about some of the current obstacles to creating a user-friendly Clojure application for Android, as well as some techniques which may be useful in creating a better user experience. In this post, I will begin concentrating on Android Clojure development from the developer's point of view by examining how to integrate Clojure into Android’s build process.
I will briefly describe the typical Android compile and deploy cycle, and then show how to modify the build to integrate Clojure. Afterwards, I will note some of the ways developing for Android with Clojure differs from other Clojure development. Finally, I will provide a link to some example code that implements the ideas presented here and provide a preview for my next post.
The Android build work flow
Broadly speaking, the standard Android build uses Ant to automate the following steps:
- Perform automated code generation based on the application’s resources and Android Interface Definition Language1 files.
- Compile both the generated code and the application source code to Java
class files using the standard Java compiler
javac
. - Generate Dalvim VM byte codes from the Java classes and any project
libraries using the
dx
tool provided by the Android SDK. The result is a file calledclasses.dex
. - Finally, create and sign an Android package file that includes the
classes.dex
file and any application resources. This package can then be installed on an Android device.
The good thing about using Ant as a build tool, is that it is relatively easy
to modify the build process. In fact, Android includes a number of extension
points, such as -pre-compile
or -post-compile
. It is also possible to
completely replace Android’s stock tasks with your own.
In the following section, I will show how to modify the second step above to include Clojure compilation.
Clojure and Android integration using Ant2
The following will demonstrate how to add Clojure compilation to a stock
Android project. For the purposes of this exercise, I will assume that this is
a fresh project created using the Android SDK and its android create project
command. If you need more help with this step, check the Android
Documentation.
It will be necessary to make a number of changes to the build.xml
file
created by Android. Be sure to make these changes in the file before the line
that reads <setup />
.
Adding new input directories
The following set of lines modifies where Android will look for source code
such that instead of having one set of source files under src
, it will
look under src/java
. You can then place your code under src/clojure
.
<!-- Modified input directories --> <property name="source.dir" value="src/java" /> <property name="clojure.source.dir" value="src/clojure" /> <property name="clojure.source.absolute.dir" location="${clojure.source.dir}" />
It is important to do this even if you have no Java code, as Android will copy any non-Java source files out of its input directory and add them to the Android package. As such, if you place your Clojure code where it expects to find its Java code, your source will be included in your compiled application, resulting in unneeded bloat and an unintentional release of your code.
Adding new output directories
The next bit of code allows you to direct the output of Clojure compilation to a different location than Android expects. For now, Clojure will compile to the same location as Java.
<!-- Clojure output directories --> <property name="out.dir" value="bin"/> <property name="clojure.out.dir" value="${out.dir}" /> <property name="clojure.out.absolute.dir" location="${clojure.out.dir}" /> <property name="clojure.out.classes.dir" value="${clojure.out.absolute.dir}/classes" /> <property name="clojure.out.classes.absolute.dir" location="${clojure.out.classes.dir}" />
Adding the Clojure targets
This Ant target will ensure that the output directories for Clojure exist before compilation:
<!-- Ensure that Clojure directories exist --> <target name="-clojure-dirs"> <mkdir dir="${clojure.out.classes.absolute.dir}" /> </target>
Finally, this next target is in charge of doing the actual Clojure compilation:
<!-- Compile Clojure namespaces --> <target name="-clojure-compile" depends="-clojure-dirs"> <java classname="clojure.lang.Compile" classpathref="android.target.classpath" fork="true" failonerror="true"> <sysproperty key="clojure.compile.path" value="${clojure.out.classes.absolute.dir}"/> <classpath> <pathelement path="${clojure.source.absolute.dir}"/> <path refid="jar.libs.ref"/> <fileset dir="${jar.libs.dir}" includes="*.jar" /> <pathelement path="${out.classes.absolute.dir}"/> </classpath> <!-- Add your Clojure namespace here --> <arg value="com.sattvik.android.clojure.basic.ClojureBrowser"/> </java> </target>
You will need to modify line 16 above and replace with your namespace. If you have multiple namespaces to compile, just add multiple lines, each with a different value.
Wiring Clojure to the Android build process
All that is left is to ensure that Ant will run the targets added above. In
this implementation I replace Android’s compile
target with a new one. This
new target will execute both Android’s standard compilation and the Clojure
compilation added above.
<!-- Hook Clojure compile into Android build work flow --> <target name="compile" depends="android_rules.compile,-clojure-compile">
Final steps
Before you compile and deploy your Clojure application to the emulator or your device, there are some things you will have to do:
- Remove the “Hello, world!” activity generated by Android under the
src
directory. - Create the
src/java
andsrc/clojure
directories. - Install the Clojure JAR file to the
libs
directory. - Create your new Clojure application under
src/clojure
.
Once you have done these steps, you can use the following commands:
ant compile
compiles your application.ant debug
creates a development version of your program as an Android package.ant install
installs the Android package onto an Android device or a running emulator.
Observations
In my experience, developing with Clojure for Android is a bit different from developing in other environments. For one, the build speed is quite a bit slower, which is problematic because there is no REPL that will run on the platform itself. Also, as I noted my last post, application sizes are relatively large for a mobile. However, there are ways to address some of these issues.
Build speed
Once everything is set up, you will find that compiling your application will be fairly quick. However, when it comes time to package your application and deploy it to your device, it will be much slower. In my experience, compilation takes only a few seconds, but creating the full package takes about a half minute.
The reason for this is that once the application has been compiled, the JVM bytecodes need to be transformed to Dalvik bytecodes, a memory- and CPU-intensive task. Ideally, the Clojure library could be processed once and the result could be reused. Unfortunately, Android does not support this.
No REPL
One of the great things about developing in Clojure is being able to use a REPL within your application for the purpose of interactive development. Being able to dynamically update your code without waiting for a complete compile-deploy cycle is invigorating. Unfortunately, on Android, with the stock Clojure library, this is impossible at this time.
The reason for this is that Clojure generates JVM classes, and these are incompatible with the Dalvik VM. Off-hand, I can see two possible resolutions to this:
- Use the Android
dx
tool at runtime to convert Clojure’s JVM bytecode into Dalvik executables which can then be loaded. - Create a whole new byte-compiling back end for Clojure that uses Dalvik VM bytecodes instead of JVM bytecodes.
The primary advantage of the first approach is that it is probably the easiest and least intrusive method. In fact, I am using it to develop a Clojure REPL application I plan to release soon. Unfortunately, it is also extremely inefficient.
The second approach is really the ideal solution, but it is going to be a much more difficult task and will quite possibly be too disruptive to include as a standard part of Clojure any time soon.
Application size
If you examine the contents of an Android package built using the above steps,
you may find that it includes source files from Clojure, such as
clojure/core.clj
. This is because the standard Clojure JAR file includes
them, and Android happily copies them into your package. However, these files
are useless and add nothing but dead weight. For best results, remove these
files from the Clojure JAR.
Things to come
I originally intended this to be a two-part series, but it turns out that I have much more material than I originally thought. Next time, I will show how to include ProGuard into the build cycle in order to slim your application and weed out unneeded classes. I will also show how this affects the runtime behaviour of the application.
I appreciate your feedback. If you have any requests or ideas, feel free to comment below.
Example code
I have created an example application written in Clojure that provides a very basic web browser. With each new instalment of this series, I will update the repository with a version of the code that uses the new techniques. The examples are available from GitHub.
Footnotes
- From the Android documentation: Android Inteface Definition Language is an IDL language used to generate code that enables two processes on an Android-powered device to talk using interprocess communication (IPC).
- If you prefer working with Maven, check out android-clojure-flashlight-example.
TrackBacks
No trackbacks, yet.
Trackbacks are closed for this story.
Comments
-
On Tuesday, 8 Feb 2011 04:33, hsaliak wrote the following:
Really nice read! Looking forward to your discussion on the proguard integration as this is an area where the results I have got so far are less than what I would like. Also, the link from your github page back to this article 404's?
-
On Tuesday, 8 Feb 2011 09:33, Daniel Solano Gómez wrote the following:
hsaliak:
Thank you for letting me know about the broken link; I have now fixed it.
My next post will show up sometime later week. I hope it lives up to your expectations.
-
On Sunday, 13 Mar 2011 14:00, Alex wrote the following:
Can I convert clojure.jar into clojure.dex and use every time dex file when building an application (to accelerate)?
-
On Sunday, 13 Mar 2011 15:32, Daniel Solano Gómez wrote the following:
Alex:
That is something I will cover in greater detail in my next "Creating Android applications with Clojure" post. In summary, you can't combine a clojure.dex file into your project's classes.dex. You can, however create a clojure.jar with all of the classes compiled into a classes.dex file inside the jar.
Your application can include the new clojure.jar (for example, as an asset) and then use a DexClassLoader to load the jar. Unfortunately, this also means you have to make sure all of your Clojure code is in its own separate jar that is also loaded at runtime.
There is really too much detail to include here, just keep an eye out for the next post in the series. If you need help more urgently, feel free to e-mail me.
-
On Monday, 19 Dec 2011 11:58, Jean Dupont wrote the following:
Hi,
thanks for the great tutorial. However, it doesn't seem to work for me.
When I do "ant compile", I get the following message:
BUILD FAILED Target "android_rules.compile" does not exist in the project "MyProject". It is used from target "compile".
If you have any idea how I could make it work, I would greatly appreciate your help.
-
On Monday, 19 Dec 2011 12:03, Daniel Solano Gómez wrote the following:
Jean Dupont:
I think the changes introduced in revision 14 of the SDK have made my tutorial obsolete. I'll look into it and revise the blog/source code so that it works with the latest SDKs.
In the meantime, you could try using Neko to compile your project.
-
On Monday, 19 Dec 2011 12:07, Jean Dupont wrote the following:
Wow! That's a quick reply! Thanks!
-
On Monday, 19 Dec 2011 15:37, Jean Dupont wrote the following:
It looks like I found a way to make it work.
You have to replace:
<target name="compile" depends="android_rules.compile,-clojure-compile">
with
<target name="compile" depends="-compile,-clojure-compile">
I don't understand how all this building process with ant works, but it looks like they moved the settings for compilation from the old android_rules.xml files to a default build.xml files in the tools/ant folder of the sdk.
I now have my first Clojure android app. However, the startup time is long. I thought the Clojure sources were all compiled into java bytecode at the compilation stage, so what is it that takes so long when you launch the app?
-
On Monday, 19 Dec 2011 16:14, Daniel Solano Gómez wrote the following:
Jean Dupont:
Yes, in the SDK release 14, they made some significant changes to how Android projects are built. I'll have to try your changes later.
As far as the start time goes, the short answer is that Clojure needs to bootstrap itself when it loads. In particular, about 7/8ths of the time is spent running the 'clojure/core.clj' script and about 1/8th of the time is spent creating and intialising the 'user' namespace. Unfortunately, there's not a good workaround (yet) for the first part, but you can disable the second in the Clojure source code. I have a fork of Clojure on GitHub that does that as well as add support for dynamic compilation. It's in the 'android' branch and is based on Clojure 1.4.
-
On Thursday, 12 Jan 2012 22:45, James N. wrote the following:
thanx a lot! really good tutorial. http://www.enterra-inc.com/techzone/using_ant_android_applications_building/ - may be useful as well
-
On Sunday, 5 Feb 2012 15:03, BostX wrote the following:
After I had to apply:
<target name="compile" depends="-compile,-clojure-compile">
I get:
E/AndroidRuntime(964): java.lang.RuntimeException: Unable to instantiate activity ComponentInfo{com.sattvik.android.clojure.basic/com.sattvik.android.clojure.basic.ClojureBrowser}: java.lang.ClassNotFoundException: com.sattvik.android.clojure.basic.ClojureBrowser
I even tried to replace:
<activity android:name="ClojureBrowser"
with
<activity android:name=".ClojureBrowser"
but it doesn't work either :(
-
On Tuesday, 7 Feb 2012 03:32, Daniel Solano Gómez wrote the following:
BostX:
I think the issues you are running into may be related to the recent changes in the Android build system introduced in SDK revision 14. I'll take some time later this week to update my sample code.
Thanks for the note.
-
On Saturday, 18 Aug 2012 23:50, Ryland wrote the following:
I'm trying to get a clojure android app working, and so far I'm having no success. Would really appreciate it if you updated this tutorial. Of course, I understand if you're busy. I know how that goes. :)
-
On Saturday, 25 Aug 2012 08:03, Daniel Solano Gómez wrote the following:
Hello, Ryland,
I apologise to the delay in getting to your comment. I agree that I need to update these pages. In the meantime, I suggest you check out the lein-droid and Neko fork from Alexander Yakushev's Google Summer of Code 2012.
Comments are closed for this story.