用 Clojure 开发 Android apk 应用程序

学了点 Clojure,就忍不住想看看能不能用它来开发 Android 设备上的应用程序,实在是不喜欢 java 代码的萝莉啰嗦,搜了半天,没发现,换一个搜索关键词 “use clojure to create an Android app” 继续搜,结果发现确实有个哥们儿早在2011年就开始研究这个了,写了三篇文章,链接位置在这里[http://www.deepbluelambda.org/2011/02/]

懒得翻译了,反正也没神马生僻词,直接贴过来,做个资料备份:

第一篇:

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 theclojure.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 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.

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 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.

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.

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

No trackbacks, yet.

Trackbacks are closed for this story.

Comments
  1. On Friday, 4 Feb 2011 02:57, Iouri Goussev wrote the following:

    Hmm, yes it is interesting why clojure can not generate code at run-time? It is a dalvik restriction?

  2. On Friday, 4 Feb 2011 08:59, Daniel Solano Gómez wrote the following:

    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.

  3. On Friday, 4 Feb 2011 12:17, Scott wrote the following:

    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.

  4. On Friday, 4 Feb 2011 13:00, Mike wrote the following:

    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.

  5. On Friday, 4 Feb 2011 13:25, Daniel Solano Gómez wrote the following:

    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.

  6. On Sunday, 9 Sep 2012 02:00, Jeff wrote the following:

    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)?

  7. On Sunday, 9 Sep 2012 22:08, Daniel Solano Gómez wrote the following:

    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.

第二篇:

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:

  1. Perform automated code generation based on the application’s resources and Android Interface Definition Language1 files.
  2. Compile both the generated code and the application source code to Java class files using the standard Java compilerjavac.
  3. 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 called classes.dex.
  4. 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.

?
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.

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.

?
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}"/>
Adding the Clojure targets

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"
          failοnerrοr="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.

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.

?
1
2
<!-- Hook Clojure compile into Android build work flow  -->
<targetname="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:

  1. Remove the “Hello, world!” activity generated by Android under the src directory.
  2. Create the src/java and src/clojure directories.
  3. Install the Clojure JAR file to the libs directory.
  4. 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:

  1. Use the Android dx tool at runtime to convert Clojure’s JVM bytecode into Dalvik executables which can then be loaded.
  2. 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
  1.  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).
  2.  If you prefer working with Maven, check out android-clojure-flashlight-example.
TrackBacks

No trackbacks, yet.

Trackbacks are closed for this story.

Comments
  1. 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?

  2. 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.

  3. 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)?

  4. 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.

  5. 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.

  6. 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.

  7. On Monday, 19 Dec 2011 12:07, Jean Dupont wrote the following:

    Wow! That's a quick reply! Thanks!

  8. 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?

  9. 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.

  10. 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

  11. 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 :(

  12. 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.

  13. 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. :)

  14. 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.

第三篇

Creating Android applications with Clojure: Slimming things down with ProGuard

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.

About ProGuard

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?

Enabling ProGuard

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:

  • When running ant install or ant debug, your program will be built as usual.
  • If you want to create a ProGuard-processed debug build, simply use ant -Dproguard.enabled=true install or ant -Dproguard.enabled=true debug.

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.

Configuring ProGuard

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.

Shrinking your application with ProGuard

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:

  1. Read and evaluate the source file, if available.
  2. Check to see if the namespace was AOT-compiled by trying to load the namespace’s initialisation class and its corresponding load method.

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.

Optimizing with ProGuard

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
Obfuscating with ProGuard

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
Final Thoughts

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.

Next time

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.

TrackBacks

No trackbacks, yet.

Trackbacks are closed for this story.

Comments

No comments, yet.

Comments are closed for this story.


看了看他的其他文章,发现居然写了个 A ndroid 下的 Clojure 环境,而且我居然下载过。。。截图:



他的实例项目地址 

https://github.com/hsaliak/android-clojure-flashlight-example

转载于:https://my.oschina.net/freeblues/blog/185408

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值