学了点 Clojure,就忍不住想看看能不能用它来开发 Android 设备上的应用程序,实在是不喜欢 java 代码的萝莉啰嗦,搜了半天,没发现,换一个搜索关键词 “use clojure to create an Android app” 继续搜,结果发现确实有个哥们儿早在2011年就开始研究这个了,写了三篇文章,链接位置在这里[http://www.deepbluelambda.org/2011/02/]
懒得翻译了,反正也没神马生僻词,直接贴过来,做个资料备份:
第一篇:
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.
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.
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 theclojure.core name space that is root of the problem, and some of that work is unnecessary.
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 ordefrecord. 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, slimmingclojure.core is not a trivial endeavour.
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:
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.
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 asclojure.test and clojure.java.javadoc 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.
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.
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.
No trackbacks, yet.
Trackbacks are closed for this story.
Hmm, yes it is interesting why clojure can not generate code at run-time? It is a dalvik restriction?
I am going to write a bit more about that in part two, but it essentially boils down to the fact that Clojure produces JVM bytecodes instead of Dalvik bytecodes.
I think you can get faster start times using ProGuard, see https://github.com/hsaliak/android-clojure-flashlight-example at the bottom of the README he has timings saying it starts in 2s on his phone.
Sounds like you need one of the old LISP tree-shakers, which would "shake" the dependency tree to get rid of anything in the standard library that wasn't used by your initial source files.
Mike and Scott:
Thank you for your comments. I have used ProGuard to help with Scala development on Android, and I plan on writing about using it with Clojure in part two. I will also include some code examples to help demonstrate this and other techniques.
i guess "Coming in Part 2" article is really the "Building with Ant" article? (http://www.deepbluelambda.org/programming/clojure/creating-android-applications-with-clojure--building-with-ant)?
Jeff:
Yes, that would be it. And part 3 would be Creating Android applications with Clojure: Slimming things down with roGuard. However, I would recommend you look at lein-droid for Clojure/Android development.
Comments are closed for this story.
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.
Broadly speaking, the standard Android build uses Ant to automate the following steps:
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.
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 />.
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.
1
2
3
4
|
<!-- Modified input directories -->
<propertyname="source.dir"value="src/java"/>
<propertyname="clojure.source.dir"value="src/clojure"/>
<propertyname="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.
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.
1
2
3
4
5
6
7
8
9
|
<!-- Clojure output directories -->
<propertyname="out.dir"value="bin"/>
<propertyname="clojure.out.dir"value="${out.dir}"/>
<propertyname="clojure.out.absolute.dir"
location="${clojure.out.dir}"/>
<propertyname="clojure.out.classes.dir"
value="${clojure.out.absolute.dir}/classes"/>
<propertyname="clojure.out.classes.absolute.dir"
location="${clojure.out.classes.dir}"/>
|
This Ant target will ensure that the output directories for Clojure exist before compilation:
1
2
3
4
|
<!-- Ensure that Clojure directories exist -->
<targetname="-clojure-dirs">
<mkdirdir="${clojure.out.classes.absolute.dir}"/>
</target>
|
Finally, this next target is in charge of doing the actual Clojure compilation:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
<!-- Compile Clojure namespaces -->
<targetname="-clojure-compile"depends="-clojure-dirs">
<javaclassname="clojure.lang.Compile"
classpathref="android.target.classpath"
fork="true"
failonerror="true">
<syspropertykey="clojure.compile.path"
value="${clojure.out.classes.absolute.dir}"/>
<classpath>
<pathelementpath="${clojure.source.absolute.dir}"/>
<pathrefid="jar.libs.ref"/>
<filesetdir="${jar.libs.dir}"includes="*.jar"/>
<pathelementpath="${out.classes.absolute.dir}"/>
</classpath>
<!-- Add your Clojure namespace here -->
<argvalue="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.
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.
1
2
|
<!-- Hook Clojure compile into Android build work flow -->
<targetname="compile"depends="android_rules.compile,-clojure-compile">
|
Before you compile and deploy your Clojure application to the emulator or your device, there are some things you will have to do:
Once you have done these steps, you can use the following commands:
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.
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.
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:
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.
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.
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.
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.
No trackbacks, yet.
Trackbacks are closed for this story.
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?
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.
Can I convert clojure.jar into clojure.dex and use every time dex file when building an application (to accelerate)?
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.
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.
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.
Wow! That's a quick reply! Thanks!
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?
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.
thanx a lot! really good tutorial. http://www.enterra-inc.com/techzone/using_ant_android_applications_building/ - may be useful as well
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 :(
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.
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. :)
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.
Last time, I described how to integrate Clojure into the standard Android build process. In this post, I will build on this by integrating ProGuard into the build process in an effort to make applications smaller and faster.
First, I will give a brief overview of ProGuard and describe how to enable the ProGuard capability of the Android build process. Next, I will show how to configure ProGuard so that it can work with your Clojure code. Finally, I will summarise my recommendations and let you know what to expect next in this series.
The sample application I will use is available on my Clojure-Android-Examples repository on GitHub under the slimmeddirectory.
As described on its web site, ‘ProGuard is a free Java class file shrinker, optimizer, obfuscator, and preverifier.’ What does this mean?
MinimisationProGuard can rid your application of unused classes, methods, and fields, leading to smaller application sizes and load times.OptimisationProGuard can analyse your code and perform a large number of optimisations, such as removing unnecessary field accesses and method calls, merging identical code blocks, inlining method invocations, etc. This should make your code smaller and run faster (depending on VM implementation).ObfuscationProGuard can remove debug information normally included class files and rename classes, methods, etc. to make them meaningless. The idea is to make it harder to reverse engineer a compiled application into source code.PreverificationThis is something specific to Java 6 or Java ME VMs. As a result, it does not apply to working with Android.Now that you have an idea of what ProGuard can do, how can you start using it?
It is very simple to enable ProGuard when you are creating a release build of your application using ant release. All you have to do is edit the build.properties file created by Android and add the following line:
1
|
proguard.confg = proguard.cfg
|
If you want to enable ProGuard for debug builds, you will also need to override the -debug-obfuscation-check target. I set up my target as follows:
1
2
3
|
<targetname="-debug-obfuscation-check"if="proguard.enabled">
<pathid="out.dex.jar.input.ref"/>
<target/>
|
Line two above ensures that the libraries you are using, such as Clojure, will be processed in addition to your application’s code.
These two changes will give you the ability to do the following:
If you want to run ProGuard for every build, you can simply add proguard.enabled = true to your build.propertiesfile.
However, if you try to run ProGuard now, you will find your build fails. First, it has to be configured.
The ProGuard configuration bundled with the latest Android SDK is configured with some sane defaults. However, if you are doing Clojure development, you will need to make some adjustments in order for ProGuard to work correctly. The following sections should help guide you to use ProGuard with your application.
All of these changes will be made to the proguard.cfg file in the root directory of your application.
ProGuard’s default configuration will not even allow the build process to complete. To get proper shrinking, you must silence some warnings and ensure that all of your application’s code is not shrunk away. If you are following along, you may want to add the following two lines to your proguard.cfg before continuing any further:
1
2
|
-dontoptimize
-dontobfuscate
|
Naturally, these disable ProGuard’s optimization and obfuscation functionality. By disabling them here, you can concentrate on getting one thing working at a time.
Silencing Clojure-related warnings and notes
Clojure refers to a number of classes that are not available on Android, particularly the Swing components used from theclojure.inspector and clojure.java.browse namespaces and some java.beans classes used byclojure.core/bean. ProGuard will complain about these missing referenced classes, but these warnings can be disabled by adding the following lines to proguard.cfg:
1
2
|
-dontwarn clojure.inspector**,clojure.java.browse**,clojure.core$bean*
-dontnote clojure.inspector**,clojure.java.browse**
|
With these two lines in place, the sample application shrinks from an install size of 4.04MB to only 460KB. Also, instead of taking 4.7 seconds to load, it crashes almost instantaneously. Why? It's because most of Clojure has been stripped out.
Keeping Clojure
The reason why Clojure is stripped out is because ProGuard does not understand how Clojure initialises a namespace. Briefly, when loading a namespace, Clojure tries to do one of two things:
For example, when trying to load the namespace org.deepbluelambda.example, Clojure will look for the fileorg/deepbluelambda/example.clj or the class org.deepbluelambda.example__init.
Therefore it is necessary to configure ProGuard to keep Clojure’s core initialization classes. This can be done by adding the following lines to proguard.cfg:
1
2
3
4
5
6
7
8
|
-keep class clojure.core__init { public static void load(); }
-keep class clojure.core_proxy__init { public static void load(); }
-keep class clojure.core_print__init { public static void load(); }
-keep class clojure.genclass__init { public static void load(); }
-keep class clojure.core_deftype__init { public static void load(); }
-keep class clojure.core.protocols__init { public static void load(); }
-keep class clojure.gvec__init { public static void load(); }
-keep class clojure.java.io__init { public static void load(); }
|
You must include these eight lines, otherwise your application will fail to run. If you use any other Clojure namespaces, you will need to add a corresponding line. For example, if you use clojure.set and clojure.contrib.string, you will need to add the following lines to your proguard.cfg:
1
2
|
-keep class clojure.set__init { public static void load(); }
-keep class clojure.contrib.string__init { public static void load(); }
|
Taking these steps will help ensure that Clojure itself loads up, but how about your application?
Keeping your Application
In addition to making sure all of Clojure’s namespaces load properly, you will need to ensure that all of your application’s namespaces load as well. The approach is the same; just be sure not to forget any gen-classed namespaces.
Furthermore, if you expose superclass methods with gen-class, as below, you will need to ensure that ProGuard does not delete those methods.
1
2
3
4
5
|
(nscom.sattvik.android.clojure.slimmed.ClojureBrowser
(:gen-class:extendsandroid.app.Activity
:mainfalse
:exposes-methods{onCreatesuperOnCreate})
…)
|
To ensure that the above activity works fine, the following lines need to be added to the ProGuard configuration:
1
2
3
4
5
6
|
-keep class com.sattvik.android.clojure.slimmed.ClojureBrowser__init {
public static void load();
}
-keep class com.sattvik.android.clojure.slimmed.ClojureBrowser {
public *** super*(...);
}
|
The first three lines should be familiar to you, the second three lines will preserve any public method, with any return and parameter types, so long as its name starts with ‘super’. Thus, it should be able to handle any number of :exposes-methodsso long as the exposed method name starts with ‘super’.
Measuring the results
Now that you have successfully shrunk your application, what can you expect? The following table summarises the results from my sample application:
Metric | Basic | Slimmed | Savings (%) |
---|---|---|---|
Package size (KB) | 1007 | 644 | 36 |
Installed application size (MB) | 4.43 | 2.77 | 37 |
Start time on device A (s) | ~4.7 | ~4.2 | 12 |
Start time on device B (s) | ~3.0 | ~2.3 | 23 |
As can be seen, shrinking definitely helps significantly reduce application package and installation sizes. The effect on performance tends to be much more device-dependent.
However, one caveat about the above data. Most of the runtime savings is realised by the absence of the clojure.set,clojure.xml, and clojure.zip namespaces. In Clojure version 1.2, these namespaces are automatically loaded, if available. If you actually use and include the namespaces, much of the runtime savings will disappear. In version 1.3, Clojure will no longer try to automatically load these namespaces.
The default proguard.cfg already disables certain optimisations known not to work on Android. Unfortunately, I find that ProGuard often fails to analyse Clojure code. During optimization, you may encounter an error similar to the following:
1
2
3
4
|
Unexpected error while performing partial evaluation:
Class = [clojure/java/io$buffer_size]
Method = [invoke(Ljava/lang/Object;)Ljava/lang/Object;]
Exception = [java.lang.IllegalArgumentException] (Stacks have different current sizes [2] and [1])
|
I have tried using the latest released version of ProGuard, but have found that it still has problems analysing Clojure code. As a result, I recommend that you disable optimisation by including the following in your proguard.cfg:
1
|
-dontoptimize
|
As with optimization, the default configuration enables obfuscation. Your application will most likely build, but you will get an error similar to the following at runtime:
1
|
java.lang.IllegalStateException: b already refers to: class clojure.b.a.b in namespace: clojure.core
|
Unfortunately, I do not see any easy way to resolve this problem. As a result, for now, I recommend you disable obfuscation as follows:
1
|
-dontobfuscate
|
ProGuard can definitely help reduce the size of your application by eliminating any namespaces you are not using. Additionally, if you are not using clojure.set, clojure.xml, or clojure.zip, you can expect a runtime benefit of faster loading when using Clojure 1.2.
Whether or not to enable ProGuard during regular development depends on the nature of your application. If the number of classes that are removed is large enough, the time spent shrinking will be offset by the time not spent processing all of those classes into a Dalvik executable. In some cases, you may forced to shrink your application, as there is a limit of 65536 method references per dex file. You may run into this limit if you are using large libraries (such as the Scala or JRuby core libraries) or many different libraries.
Unfortunately, at this time it does not seem like optimization and obfuscation with ProGuard is compatible with Clojure code. There may be ways to work around some of these problems, such as fine-tuning optimizations or running your code through ProGuard in stages with different configurations.
In my next post, I demonstrate how improve application start-up responsiveness by using a Java bootstrap activity. Additionally, I plan to show how to exploit the bootstrap activity to use supplemental class loaders to circumvent the dex file format shortcomings and drastically improve build times.
No trackbacks, yet.
Trackbacks are closed for this story.
No comments, yet.
Comments are closed for this story.
他的实例项目地址
https://github.com/hsaliak/android-clojure-flashlight-example