Shrinking the Scala library code

by Stephane Micheloud, May 2010

[Home]
[Back]

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:

Tools

Both shrinker tools can be run as a task in the Java-based build tool Ant (version 1.6.0 or higher).

Results

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.

Table 1. Project build times for Android applications written in Scala and Java.

Build time(1) ScalaJava
ProGuard
(shrink only)
ProGuard
(shrink+optimize)
YGuard ramdisk.img(2)-
ApiDemos 1m 18s1m 32s8m 18s57s19s
ContactManager 37s51s8m 24s16s5s
CubeLiveWallpaper 38s50s8m 10s17s5s
GestureBuilder 40s55s8m 23s18s5s
Home 40s56s9m 21s20s6s
JetBoy 47s1m 2s7m 44s27s13s
LunarLander 39s55s7m 50s17s5s
NotePad 38s55s9m 10s17s5s
SearchableDictionary 45s58s9m 19s18s4s
Snake 46s1m 0s8m 12s33s4s

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

Table 2. Sizes of .dex files generated for Android applications written in Scala and Java.

classes.dex ScalaJava
(Dalvik executable) ProGuard(1)
(shrink only)
ProGuard(2)
(shrink+optimize)
YGuard(1) ramdisk.img(3)-
ApiDemos 872K (409)542K785K (584)868K468K
ContactManager 286K (362)94K246K (482)32K17K
CubeLiveWallpaper 279K (353)31K279K (516)31K15K
GestureBuilder 337K (403)116K341K (521)45K20K
Home 314K (365)146K289K (484)59K32K
JetBoy 311K (369)101K280K (493)54K24K
LunarLander 351K (427)92K299K38K18K
NotePad 282K (356)95K250K38K21K
SearchableDictionary 301K (380)106K319K (548)34K15K
Snake 353K (428)114K305K38K14K

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

Table 3. Sizes of .apk files generated for Android applications written in Scala and Java.

<app>-debug.apk(1) ScalaJava
ProGuard
(shrink only)
ProGuard
(shrink+optimize)
YGuard ramdisk.img(2)-
ApiDemos 2688K2559K2337K2721K2174K
ContactManager 127K56K103K39K25K
CubeLiveWallpaper 120K31K109K35K19K
GestureBuilder 144K52K132K45K28K
Home 359K285K342K274K247K
JetBoy 1647K1569K1628K1560K1530K
LunarLander 250K155K221K142K120K
NotePad 132K62K114K49K48K
SearchableDictionary 153K79K144K61K44K
Snake 147K58K118K37K18K

(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 respectively configs/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>

About the Author

Stephane's Picture
Stéphane Micheloud is a senior software engineer. He holds a Ph.D in computer science from EPFL and a M.Sc in computer science from ETHZ. At EPFL he worked on distributed programming and advanced compiler techniques and participated for over six years to the Scala project. Previously he was professor in computer science at HES-SO // Valais in Sierre, Switzerland.
[Top]

Other Articles