Building Android applications written in Scala requires to bundle the
Scala library code together with
the generated .dex
file. The Android platform namely forbids
you to export a self-contained .jar
file, as you would do for
a true library. We present in this article two possible ways to deal with
that constraint.
Note: Starting with API level 8 of the Android framework you can create library projects to share Android source code and resources. However, those are just build-time constructs that are compiled indirectly, by referencing them from a dependent application's build path, when building an Android application.
Let us consider the following three possibilities:
The size of the "scala-library.jar
" library code is about
6 MB in Scala 2.8.0; thus the size of every Android application
written in Scala would exceed 6 MB !
Note: The
dx
tool of the Android SDK fails to generate Android bytecode for the Scala standard library. For instance the execution of the following shell command aborts with the error message "trouble writing output: format == null
" :/tmp> dx -JXmx1024M -JXms1024M -JXss4M --no-optimize --debug --dex --output=/tmp/scala-library.jar /opt/scala/lib/scala-library.jar
That issue is due to a size limit in the Dalvik VM and is not specific to Scala. It was reported by Maciek Makowski to the Android project hosted on Google Code on March 14, 2010 (issue #7147).
Both shrinker tools can be run as a task in the Java-based build tool Ant (version 1.6.0 or higher).
In this section we resume the results of measurements performed on 10 sample applications available from the Android SDK. We present processing times and shrunk code sizes obtained with the Java tools Proguard and YGuard and compare them with a runtime-based solution (see article "Tweaking the Android emulator").
In Table 1 we observe — and are badly suprised — that build times with YGuard are extremely long compared to Proguard. More generally we must acknowledge the additional time required to build Android applications written in Scala.
Build time(1) | Scala | Java | |||
---|---|---|---|---|---|
ProGuard (shrink only) |
ProGuard (shrink+optimize) |
YGuard | ramdisk.img (2) | - | |
ApiDemos | 1m 18s | 1m 32s | 8m 18s | 57s | 19s |
ContactManager | 37s | 51s | 8m 24s | 16s | 5s |
CubeLiveWallpaper | 38s | 50s | 8m 10s | 17s | 5s |
GestureBuilder | 40s | 55s | 8m 23s | 18s | 5s |
Home | 40s | 56s | 9m 21s | 20s | 6s |
JetBoy | 47s | 1m 2s | 7m 44s | 27s | 13s |
LunarLander | 39s | 55s | 7m 50s | 17s | 5s |
NotePad | 38s | 55s | 9m 10s | 17s | 5s |
SearchableDictionary | 45s | 58s | 9m 19s | 18s | 4s |
Snake | 46s | 1m 0s | 8m 12s | 33s | 4s |
(1) Average system time measured with command
"ant clean; time ant
" (default target is debug
).
(2) No code shrinking, no library code (see article "Tweaking the Android emulator").
In Table 2 we compare the code size of the generated .dex
files for the 10 sample applications. We remind here that the shrinker
tools actually process Java class files which are then compiled into
Dalvik executables.
While build times with YGuard are much worse than ProGuard's ones both shrinker tools achieve similar code size reduction (results may vary slightly depending on the enabled tool options).
classes.dex |
Scala | Java | |||
---|---|---|---|---|---|
(Dalvik executable) | ProGuard(1) (shrink only) |
ProGuard(2) (shrink+optimize) |
YGuard(1) | ramdisk.img (3) | - |
ApiDemos | 872K (409) | 542K | 785K (584) | 868K | 468K |
ContactManager | 286K (362) | 94K | 246K (482) | 32K | 17K |
CubeLiveWallpaper | 279K (353) | 31K | 279K (516) | 31K | 15K |
GestureBuilder | 337K (403) | 116K | 341K (521) | 45K | 20K |
Home | 314K (365) | 146K | 289K (484) | 59K | 32K |
JetBoy | 311K (369) | 101K | 280K (493) | 54K | 24K |
LunarLander | 351K (427) | 92K | 299K | 38K | 18K |
NotePad | 282K (356) | 95K | 250K | 38K | 21K |
SearchableDictionary | 301K (380) | 106K | 319K (548) | 34K | 15K |
Snake | 353K (428) | 114K | 305K | 38K | 14K |
(1) We give in parenthesis the number of remaining library classes (from
initially 4151 classes).
(2) We use the same optimizations as in Android example from the Proguard online manual.
(3) No code shrinking, no library code (see article "Tweaking the Android emulator").
Before its installation on a device each Android application is packaged
as a single .apk
file which includes .dex
files,
resources, assets, and manifest file.
<app>-debug.apk (1) |
Scala | Java | |||
---|---|---|---|---|---|
ProGuard (shrink only) |
ProGuard (shrink+optimize) |
YGuard | ramdisk.img (2) | - | |
ApiDemos | 2688K | 2559K | 2337K | 2721K | 2174K |
ContactManager | 127K | 56K | 103K | 39K | 25K |
CubeLiveWallpaper | 120K | 31K | 109K | 35K | 19K |
GestureBuilder | 144K | 52K | 132K | 45K | 28K |
Home | 359K | 285K | 342K | 274K | 247K |
JetBoy | 1647K | 1569K | 1628K | 1560K | 1530K |
LunarLander | 250K | 155K | 221K | 142K | 120K |
NotePad | 132K | 62K | 114K | 49K | 48K |
SearchableDictionary | 153K | 79K | 144K | 61K | 44K |
Snake | 147K | 58K | 118K | 37K | 18K |
(1) Package size includes bytecode and resources.
(2) No code shrinking, no library code (see article "Tweaking the Android emulator").
Note: The above results have been measured on a 2.0 GHz Pentium M laptop with 2 GB of memory; the machine is running either Linux Ubuntu 8.04 or Microsoft Windows XP Pro. The development environment includes the following software: Sun JDK 1.6.0_20, Eclipse 3.5.2 (with ADT 0.9.7 and Scala Plugin 2.8.0.X), Apache Ant 1.8.1 and Scala 2.8.0_RC6.
Based on the above results the winner is clearly ProGuard (assuming that a modified emulator environment is not an option); it is efficient both in time and space and adapts very well to the developer's needs.
-shrink-scala
Target
In our article "Targeting Scala to
the Android platform" we added the two targets
"compile-scala"
and "-shrink-scala"
to the
build.xml
Ant script in order to include Scala source files
in our build process.
We present here the two variants of the
"-shrink-scala"
target
we implemented in order to evaluate the ProGuard
and YGuard tools.
The "ProGuard" variant of
the "-shrink-scala"
target gets its
settings from an external configuration file; its content is based on the
template file
proguard.conf
and is determined dynamically during the build process.
<target name="-shrink-scala" depends="-shrink-if-test" unless="do.not.shrink" description="Shrink the Scala library code"> <!-- ... (skipped) --> <proguard-helper property="injars" prefix="-injars" path="${out.classes.dir}${path.separator}${scala-library.jar}" /> <!-- ... (skipped) --> <copy file="${basedir}/proguard.conf" tofile="${proguard.conf}"> <filterchain> <replacetokens> <token key="INJARS" value="${injars}"/> <token key="OUTJARS" value="${outjars}"/> <token key="LIBRARYJARS" value="${libraryjars}"/> <token key="MYAPP_PACKAGE" value="${myapp.package}"/> </replacetokens> </filterchain> </copy> <proguard configuration="${proguard.conf}" /> <touch file="${out.dir}/myapp.complete" verbose="no" /> </target>
Note: In our framework the external ProGuard configuration file is actually generated differently depending on the invoked target. Per default it is based on the template file
configs/default-debug.pro
respectivelyconfigs/default-release.pro
for the targets"debug"
and"install"
respectively"release"
and"install-release"
) and may be superseeded by a local configuration with project specific settings.
The Ant target "-shrink-scala
" based on the
YGuard Ant task looks as follows:
<target name="-shrink-scala" depends="-shrink-if-test" unless="do.not.shrink" description="Shrink the Scala library code"> <!-- ... (skipped) --> <jar destfile="${myapp.jar}" basedir="${out.classes.dir}" /> <yguard> <inoutpair in="${scala-library.jar}" out="${scala-library-shrinked.jar}" /> <externalclasses> <pathelement location="${android.jar}" /> </externalclasses> <shrink logfile="${out.dir}/yshrinklog.xml"> <entrypointjar name="${myapp.jar}" /> </shrink> </yguard> <unjar src="${scala-library-shrinked.jar}" dest="${out.classes.dir}" /> </target>