├── AndroidManifest.xml
├── README
├── TODO
├── clojure-inits.txt
├── pom.xml
├── proguard.cfg
├── res
├── drawable-hdpi
│ └── icon.png
├── drawable-ldpi
│ └── icon.png
├── drawable-mdpi
│ └── icon.png
├── layout
│ └── main.xml
└── values
│ └── strings.xml
└── src
└── com
└── hsaliak
└── HelloFlashlight.clj
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 |
2 | Flashlight Example for Clojure based on the example found at https://github.com/jayway/maven-android-plugin-samples
3 |
4 | Goals
5 | ====
6 | 1)Android development in Clojure
7 | 2)No java bridge.
8 | 3)Ability to leverage Android tools and workflow. Ie: using generated resources.
9 | 4)A REPL with Android libraries available for rapid prototyping
10 | 5)Able to use DDMS on the app. With stack traces hitting clojure line numbers.
11 | 6)Escape getting mugged out of all your RAM by gangster IDEs
12 | 7)Use ProGuard to shrink the apk size. - Done , a size reduction of around 300k can be achieved by pruning unused parts of clojure core. apk size is around 900k.
13 | 8) TODO: Optimize with proguard
14 |
15 | The purpose of this codebase is to serve as an illustrative example of how to achieve the above goals. The idea is that the code will allow you to replicate this set-up for your android projects, giving you a productive environment quickly.
16 |
17 | Prerequisites:
18 | ====
19 | Android SDK:
20 | - http://developer.android.com/sdk/index.html
21 | It is assumed that you have the SDK and a (Froyo) AVD ready.
22 | Maven:
23 | The whole build is managed by maven.
24 | - http://maven.apache.org
25 | You need to have a fair understanding of maven to not be put off by the xml work that needs to be done.
26 |
27 | Maven Android Plugin:
28 | The example implemented here is the flashlight example that demonstrates the android-maven plugin. The code for the Java version of this can be found at:
29 | - https://github.com/jayway/maven-android-plugin-samples
30 | To understand the maven-android example, this may be a good place to start.
31 | There is a detailed discussion of this plugin as well as all of maven here:
32 | - http://www.sonatype.com/books/mvnref-book/reference/public-book.html
33 | - http://www.sonatype.com/books/mvnref-book/reference/android-dev.html
34 |
35 | Clojure maven Plugin:
36 | The clojure-maven plugin is also required, and it can be found here:
37 | - https://github.com/talios/clojure-maven-plugin
38 |
39 | The project was first created by using the 'android' command found in the android SDK.
40 | #android create project -t android-8 -p helloflashlight -k com.yourname -a HelloFlashlight
41 | Refer to the android documentation for further details.
42 |
43 | Add maven support to this project, this is done by creating a pom.xml with the appropriate details.
44 | See http://www.sonatype.com/books/mvnref-book/reference/android-dev-sect-using.html
45 |
46 | Add clojure support to the project. For this, you will need to add the official clojure maven repository (for clojure-contrib) and the clojars repository for swank. (Goal 4)
47 |
48 | Add proguard support to the project. See http://proguard.sourceforge.net/ The tool is quite complex, so a lot of researching on the internet may be required.
49 | Clojure is also fairly complex and needs special treatment when used with proguard. The current approach and its limitations are explained in proguard.cfg
50 |
51 | Reading the material linked above, and this project's pom.xml should make the workflow clear.
52 | ====
53 |
54 | Reccomended reading order:
55 | 1)pom.xml.
56 | 2) com/hsaliak/HelloFlashlight.clj ( I know I am missing 1 package level)
57 | 3) res/layout/main.xml
58 | 4) res/values/strings.xml
59 | 5) proguard.cfg
60 | 6) You may want to peek into clojure-inits.txt at the top level as a reference.
61 |
62 |
63 | Debugging:
64 | You will be able to run DDMS from your android SDK. This can be used to debug the app once its deployed in the emulator.
65 |
66 | REPL:
67 | pom.xml contains the swank plugin.
68 | Running "mvn clojure:swank" will start a swank server. Emacs can be used to slime-connect it.
69 | In the REPL, Android libs will be available to test.
70 | Eg:
71 | user> (import [android.graphics Color])
72 | android.graphics.Color
73 | user> Color/WHITE
74 | -1
75 | user> Color/RED
76 | -65536
77 |
78 |
79 | Compilation:
80 | #mvn install android:deploy
81 | This step requires the avd to be started using the 'android' tool.
82 | pom.xml can be set up to start the emulator with the android:start-emulator target
83 | but has not yet been set up to do so.
84 |
85 | Useful targets:
86 | android:generate-sources - THis will create R.java, useful to know how to look at when coding the clojure bits
87 | android:undeploy : Make sure you enable undeploy before deploy so that you will always be running the latest app on your emulator
88 | android:deploy
89 |
90 |
91 |
92 | Startup time
93 | ==========
94 | The app took 9 seconds to start up on a froyo emulator. [ Measured via ddms]
95 | 02-03 14:34:11.560: INFO/ActivityManager(59): Displayed activity com.hsaliak/.HelloFlashlight: 8767 ms (total 8767 ms)
96 |
97 | The app took 2 seconds to start up on a Samsung Galaxy S I9000 [Measured via ddms ]
98 | 02-03 14:03:08.969: INFO/ActivityManager(2494): Displayed activity com.hsaliak/.HelloFlashlight: 2163 ms (total 2163 ms)
99 |
100 |
--------------------------------------------------------------------------------
/TODO:
--------------------------------------------------------------------------------
1 | - Investigate the use of Proguard to minimize binary size [Done, but needs continued investigation]
2 | - Investigate including of Clojure-contrib and optimizing it.
3 | - Android-test support
4 | - Clojure unit test support
5 |
--------------------------------------------------------------------------------
/clojure-inits.txt:
--------------------------------------------------------------------------------
1 | -keep public class clojure.template__init {*;}
2 | -keep public class clojure.core_deftype__init {*;}
3 | -keep public class clojure.zip__init {*;}
4 | -keep public class clojure.set__init {*;}
5 | -keep public class clojure.inspector__init {*;}
6 | -keep public class clojure.core__init {*;}
7 | -keep public class clojure.test.junit__init {*;}
8 | -keep public class clojure.core_print__init {*;}
9 | -keep public class clojure.repl__init {*;}
10 | -keep public class clojure.java.browse_ui__init {*;}
11 | -keep public class clojure.main__init {*;}
12 | -keep public class clojure.pprint.column_writer__init {*;}
13 | -keep public class clojure.java.browse__init {*;}
14 | -keep public class clojure.core_proxy__init {*;}
15 | -keep public class clojure.pprint.cl_format__init {*;}
16 | -keep public class clojure.java.shell__init {*;}
17 | -keep public class clojure.stacktrace__init {*;}
18 | -keep public class clojure.pprint.utilities__init {*;}
19 | -keep public class clojure.walk__init {*;}
20 | -keep public class clojure.pprint.dispatch__init {*;}
21 | -keep public class clojure.pprint.pprint_base__init {*;}
22 | -keep public class clojure.core.protocols__init {*;}
23 | -keep public class clojure.java.javadoc__init {*;}
24 | -keep public class clojure.test__init {*;}
25 | -keep public class clojure.java.io__init {*;}
26 | -keep public class clojure.genclass__init {*;}
27 | -keep public class clojure.test.tap__init {*;}
28 | -keep public class clojure.xml__init {*;}
29 | -keep public class clojure.pprint__init {*;}
30 | -keep public class clojure.string__init {*;}
31 | -keep public class clojure.gvec__init {*;}
32 | -keep public class clojure.pprint.pretty_writer__init {*;}
33 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 | 4.0.0
8 | com.hsaliak
9 | helloflashlight
10 | 0.1-SNAPSHOT
11 | apk
12 | HelloFlashlight
13 |
14 |
15 |
16 |
17 | com.google.android
18 | android
19 | 2.2.1
20 | provided
21 |
22 |
23 |
24 | org.clojure
25 | clojure
26 | 1.2.0
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | swank-clojure
36 | swank-clojure
37 | 1.3.0-SNAPSHOT
38 |
39 |
40 |
41 |
42 |
43 | src
44 |
45 |
47 |
48 | com.theoryinpractise
49 | clojure-maven-plugin
50 | 1.3.2
51 |
52 |
53 | src
54 |
55 |
56 |
57 |
58 | compile-clojure
59 | compile
60 |
61 | compile
62 |
63 |
64 |
65 |
66 |
67 | com.pyx4me
68 | proguard-maven-plugin
69 | 2.0.4
70 |
71 |
72 | net.sf.proguard
73 | proguard
74 | 4.4
75 | runtime
76 |
77 |
78 |
79 |
80 |
81 | process-classes
82 | proguard
83 |
84 |
85 |
86 |
87 |
88 |
89 | android-classes
90 | android-classes
91 | 4.4
92 |
93 |
94 | ${java.home}/lib/rt.jar
95 |
96 |
97 |
98 | proguard.cfg
99 |
100 |
101 |
102 |
103 |
104 | com.jayway.maven.plugins.android.generation2
105 |
106 | maven-android-plugin
107 | 2.8.4
108 |
109 |
110 | froyo
111 |
112 |
113 | 8
114 |
115 |
116 | true
117 |
118 | true
119 |
120 | true
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 | clojure
136 | http://build.clojure.org/releases
137 |
138 |
139 | clojars
140 | http://clojars.org/repo
141 |
142 |
143 |
144 |
145 |
146 |
147 |
--------------------------------------------------------------------------------
/proguard.cfg:
--------------------------------------------------------------------------------
1 | -dontobfuscate
2 | -dontoptimize
3 | #-dontshrink #When developing your app, I recommend that this option is enabled by default.Proguard is best used at the end,
4 | #when your app is complete and tested.
5 | #When -dontshrink -dontoptimize and -ontobfuscate are all enabled, proguard can exist in your maven configuration/workflow without having any effect on the code.
6 | -verbose
7 | -dontpreverify
8 | -printseeds seed.txt
9 |
10 | -keep public class * extends android.app.Activity
11 | -keep public class * extends android.app.Application
12 | -keep public class * extends android.app.Service
13 | -keep public class * extends android.content.BroadcastReceiver
14 | -keep public class * extends android.content.ContentProvider
15 | -keep public class com.android.vending.licensing.ILicensingService
16 |
17 | #Note, the approach taken below is not ideal.
18 | #The best practicec seems to be to derive a config file
19 | #which is able to look at the class tree and determine what is needed.
20 | #In any case, here goes:
21 | #From gen-class's doc: "Each file, fn and gen-class will produce a .class file
22 | #Each file generates a loader class of the same name with "__init" appended."
23 | #Start with the configuration below, this causes all files in the clojure codebase to be kept.
24 | #-keep class clojure.**init { *;}
25 |
26 | #1) Once you have a successful proguard run which yields a working app, take a look at proguard_seeds.txt in target/
27 | #2) Grok the file along the lines of the sed command shown below to pick up the relevant namespaces.
28 | #cat proguard_seeds.txt | sed -e s/^clojure.//g | sed -e s/\:.*$//g | uniq > ../clojure-inits.txt
29 | #3) Trim your classes from the bottom if necessary. See generated clojure-inits.txt for an example of what we want to extract.
30 | #4) With these init files, we can begin pruning the code base to yield the smallest runtime that works for us. Ideally, the proguard configuration will determine what is needed, but I have not found a way to make this work with clojure, given its dynamic nature and my limited understanding of proguard. The pruned configuration below was determined through looking at clojure.org and systematic trial and error (some of the namespaces are used internally.)
31 | #5) This approach outlined should actually work for any clojure app. The general idea is to remove unused namespaces. Using contrib libraries will have recursive dependancies. For clojure-contrib, a similar clojure.contrib.**init can be done, all unused contribs can be disabled. TODO: test this.
32 | #6) The pruning reduced the binary size from 1.2mb to 900k. Since most of the fat comes from the runtime, your app should grow much more gradually from that point.
33 |
34 | #===============Clojure related keep options===============
35 | #The following cannot be disabled---------------
36 | #Core classes
37 | -keep public class clojure.core__init {*;}
38 | -keep public class clojure.core_deftype__init {*;}
39 | -keep public class clojure.core_proxy__init {*;}
40 | -keep public class clojure.core.protocols__init {*;}
41 | -keep public class clojure.core_print__init {*;}
42 | -keep public class clojure.main__init {*;}
43 |
44 | #These are used internally.Disabling them causes the app to not load.
45 | #clojure.org has details on what they are.
46 | -keep public class clojure.zip__init {*;}
47 | -keep public class clojure.set__init {*;}
48 | -keep public class clojure.string__init {*;}
49 | -keep public class clojure.gvec__init {*;}
50 | -keep public class clojure.genclass__init {*;}
51 | -keep public class clojure.xml__init {*;}
52 |
53 | #Java interop related, all except IO should not be needed in android.
54 | #IO functions - This is needed for clojure
55 | -keep public class clojure.java.io__init {*;}
56 |
57 |
58 | #The following can be enabled if needed.---------------
59 |
60 | # Tree walker, you will probably use them for complex apps
61 | #-keep public class clojure.walk__init {*;}
62 | #Clojure oriented stack traces.
63 | #-keep public class clojure.stacktrace__init {*;}
64 | #For use in macros
65 | #-keep public class clojure.template__init {*;}
66 | #Pretty printer not usually needed.
67 | # -keep public class clojure.pprint.column_writer__init {*;}
68 | # This may be needed if you are using cl-format.
69 | # -keep public class clojure.pprint.cl_format__init {*;}
70 | # -keep public class clojure.pprint.utilities__init {*;}
71 | # -keep public class clojure.pprint.dispatch__init {*;}
72 | # -keep public class clojure.pprint.pprint_base__init {*;}
73 | # -keep ptublic class clojure.pprint__init {*;}
74 | # -keep public class clojure.pprint.pretty_writer__init {*;}
75 |
76 | #It should be ok to keep the following disabled---------------
77 | #Test libs. I feel that they do not make sense in an android app as the tests are run on the jvm prior to proguard and dexing.
78 | #-keep public class clojure.test.junit__init {*;}
79 | #-keep public class clojure.test__init {*;}
80 | #-keep public class clojure.test.tap__init {*;}
81 |
82 | #Used for inspecting clojure data structures see clojure.org
83 | #-keep public class clojure.inspector__init {*;}
84 | #Repl is not needed in android env, can be disabled.
85 | #-keep public class clojure.repl__init {*;}
86 |
87 | #Start a web browser from clojure, not needed.
88 | #-keep public class clojure.java.browse__init {*;}
89 | #Launch a subprocess
90 | #-keep public class clojure.java.shell__init {*;}
91 | #Open javadoc in repl
92 | #-keep public class clojure.java.javadoc__init {*;}
93 | #-keep public class clojure.java.browse_ui__init {*;}
94 |
95 | #End of clojure related keep options===============
96 |
97 | # For now it is recommended that you keep your classes as-is.
98 | -keep class com.hsaliak.** {*;}
99 | # Required when exposes-methods is used. Without this, exposing super class methods with
100 | #alternate method names eg onCreate -> superOnCreate, does not seem to work
101 | -keep class com.hsaliak.HelloFlashlight extends * {*;}
102 | # Tip: when in doubt, browse to targets/classes and use javap to disassemble the class.
103 |
104 |
105 |
106 |
107 |
108 | -keepclasseswithmembernames class * {
109 | native ;
110 | }
111 |
112 | -keepclasseswithmembernames class * {
113 | public (android.content.Context, android.util.AttributeSet);
114 | }
115 |
116 | -keepclasseswithmembernames class * {
117 | public (android.content.Context, android.util.AttributeSet, int);
118 | }
119 |
120 | -keepclassmembers enum * {
121 | public static **[] values();
122 | public static ** valueOf(java.lang.String);
123 | }
124 |
125 | -keep class * implements android.os.Parcelable {
126 | public static final android.os.Parcelable$Creator *;
127 | }
128 |
129 | -keep public class * extends android.view.View {
130 | public (android.content.Context);
131 | public (android.content.Context, android.util.AttributeSet);
132 | public (android.content.Context, android.util.AttributeSet, int);
133 | public void set*(...);
134 | }
135 | -keepclassmembers class * {
136 | public void *(android.view.View);
137 | }
138 |
139 | -keepclassmembers class **.R$* {
140 | public static ;
141 | }
142 |
--------------------------------------------------------------------------------
/res/drawable-hdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hsaliak/android-clojure-flashlight-example/2da9f2fabd3daf94d6a701b428b3ec30f949ef3f/res/drawable-hdpi/icon.png
--------------------------------------------------------------------------------
/res/drawable-ldpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hsaliak/android-clojure-flashlight-example/2da9f2fabd3daf94d6a701b428b3ec30f949ef3f/res/drawable-ldpi/icon.png
--------------------------------------------------------------------------------
/res/drawable-mdpi/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/hsaliak/android-clojure-flashlight-example/2da9f2fabd3daf94d6a701b428b3ec30f949ef3f/res/drawable-mdpi/icon.png
--------------------------------------------------------------------------------
/res/layout/main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | HelloFlashlight from Clojure
4 | Red
5 | Blue
6 | Green
7 | Black
8 | White
9 |
10 |
--------------------------------------------------------------------------------
/src/com/hsaliak/HelloFlashlight.clj:
--------------------------------------------------------------------------------
1 | ;; The key here is :exposes-methods. It creates a method for you called
2 | ;;superOnCreate, which is the Activity class's onCreate. Equivalent to calling
3 | ;; super.onCreate() in Java.
4 |
5 | (ns com.hsaliak.HelloFlashlight
6 | (:gen-class
7 | :extends android.app.Activity
8 | :exposes-methods {onCreate superOnCreate onDestroy superOnDestroy})
9 | (:import [android.widget Button TableLayout]
10 | [android.app Activity]
11 | [android.graphics Color]
12 | [android.os Bundle]
13 | [android.view View]
14 | [android.os Debug]))
15 |
16 | ;; View$OnClickListener is a public static interface within the View Class
17 | ;; It is meant to be instantiated as an anonymous class, The right way to
18 | ;; do this in clojure is to reify the interface and provide the onClick
19 | ;; as below. I suppose proxies can also be used but is not recommended.
20 | ;; Note that the method has a #^void type hint for the return value. Without
21 | ;; it you will get a signature mismathc where an object is returned when
22 | ;; void is expected. I have put a do block with returning nil just to be safe.
23 | (defn on-click [c table]
24 | (let [oc (reify android.view.View$OnClickListener
25 | (#^void onClick [this #^View v]
26 | (do
27 | (.setBackgroundColor table c)
28 | nil)))]oc))
29 |
30 | (defn -onDestroy [this]
31 | (Debug/stopMethodTracing)
32 | (.superOnDestroy this))
33 | (defn -onCreate [this #^android.os.Bundle bundle ]
34 | ;; The this object reference is passed as the first argument.
35 | ;; This is different from java where its availability is implicit.
36 | (Debug/startMethodTracing "flashlight")
37 | (.superOnCreate this bundle)
38 | (.setContentView this com.hsaliak.R$layout/main)
39 | ;; Clojure really shines here as we have saved a lot of verbosity.
40 | (let [[table red green blue black white]
41 | (map #(.findViewById this %)
42 | [com.hsaliak.R$id/Table
43 | com.hsaliak.R$id/ButtonRed
44 | com.hsaliak.R$id/ButtonGreen
45 | com.hsaliak.R$id/ButtonBlue
46 | com.hsaliak.R$id/ButtonBlack
47 | com.hsaliak.R$id/ButtonWhite])]
48 | (.setBackgroundColor table Color/WHITE)
49 | ;; watch out for laziness..
50 | (doall (map #(.setOnClickListener %1 (on-click %2 table))
51 | [red green blue black white]
52 | [Color/RED Color/GREEN Color/BLUE Color/BLACK Color/WHITE]))))
53 |
54 |
55 |
--------------------------------------------------------------------------------