Blog

Scalafizierte Androiden – Teil 1: Werkzeuge

Scala ist eine moderne objektfunktionale Programmiersprache, deren Hype noch immer nicht abgeklungen ist. Für die ereignisorientierte Entwicklung von Android-Applikationen scheint Scala mit seiner funktionalen Ausrichtung und dem starken Typsystem besonders gut geeignet zu sein. Mittlerweile ist die Werkzeugentwicklung so weit fortgeschritten, dass man erste Gehversuche mit Scala auf der Android-Plattform wagen kann. Die in dieser Artikelserie vorgestellte Beispiel-App soll dabei sowohl durch die IDE Eclipse mit den Android Development Tools (ADT) als auch auf der Kommandozeile mit Maven gebaut werden können.

Der Werkzeugkoffer

Zuallererst gilt es die Entwicklungsumgebung einzurichten. Neben dem Android SDK, Eclipse und Maven werden die Maven Integration für Eclipse, einige Konnektor-Plugins sowie die Android Developer Tools für Eclipse benötigt. Die folgenden Werkzeuge werden in der derzeit aktuellen Version eingesetzt:

  • jdk1.7.0_07 (32 bit)
  • apache-maven-3.0.4
  • android-sdk (20.0.3 / 16)
  • Eclipse Juno Release (32 bit)
  • Eclipse Plugins
    • Android Development Tools (ADT) (20.0.3)
    • Scala IDE for Eclipse (2.1.0.m2-2_10)
    • m2e – Maven Integration for Eclipse (1.2.0)
    • Maven Integration for Scala IDE (0.4.2)
    • APT M2E Connector (0.0.1.3)

Das vollständige Projektarchiv scala-on-android-0.0.1.zip kann direkt heruntergeladen und als Ausgangspunkt für weitere Entwicklungen genutzt werden.

Getting started

Beginnen wir mit einem klassischen Hello-World-Beispiel. Die Activity-Klasse MainActivity.scala der scala-on-android-App orientiert sich an dem Hello-World-Beispiel des ADTs und nutzt exemplarisch einige Funktionen der Scala Collections API.

package de.holisticon.scalaonandroid

import android.app.Activity
import android.os.Bundle
import android.widget.TextView

class MainActivity extends Activity {
  override def onCreate(savedInstanceState: Bundle) = {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.main)
    val words = List("Hello", "Scala", "on", "Android")
    val text = words.reduce(_ + " " + _)
    val textView = findViewById(R.id.textview).asInstanceOf[TextView]
    textView.setText(text)
  }
}

In dem Artikel Maven baut auch Android-Apps hat Sven Bunge bereits gezeigt, wie eine Android-Anwendung mit Maven erstellt werden kann. Dieses Setup werden wir aufgreifen und einige Anpassungen für die Integration mit Eclipse und Scala vornehmen. Aufgrund der unflexiblen Android-Projektstruktur des ADT-Eclipse-Plugins müssen wir auf unserem Weg leider einige unschöne Abweichungen vom Maven-Standard-Layout machen:

/assets/
/gen/
/res/
/src/main/scala/
/src/test/scala/
/pom.xml
/project.properties
/proguard.cfg

Die interessanten Abschnitte aus dem Project Object Model sind in den folgenden gekürzten Auszügen dargestellt.

<dependencies>
	<!-- Android (provided), Scala und JUnit (test) -->
<dependencies>
<properties>
	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
	<project.build.resourceEncoding>UTF-8</project.build.resourceEncoding>
	<maven.compiler.source>1.6</maven.compiler.source>
	<maven.compiler.target>1.6</maven.compiler.target>
	<android.sdk.path>PATH_TO_ANDROID_SDK</android.sdk.path>
	<android.sdk.version>16</android.sdk.version>
	<scala.version>2.10.0-M7</scala.version>
</properties>
<!-- ... -->

In den Properties werden der Pfad und die Version des Android-SDKs sowie die gewünschte Scala-Version gesetzt.

<build>
	<sourceDirectory>src/main/scala</sourceDirectory>
	<testSourceDirectory>src/test/scala</testSourceDirectory>
	<plugins>
		<plugin>
			<groupId>org.scala-tools</groupId>
			<artifactId>maven-scala-plugin</artifactId>
			<executions>
				<execution>
					<goals>
						<goal>compile</goal>
						<goal>testCompile</goal>
					</goals>
				</execution>
			</executions>
		</plugin>
		<plugin>
			<groupId>com.jayway.maven.plugins.android.generation2</groupId>
			<artifactId>android-maven-plugin</artifactId>
			<configuration>
<androidManifestFile>${project.basedir}/AndroidManifest.xml</androidManifestFile>
<assetsDirectory>${project.basedir}/assets</assetsDirectory>
<resourceDirectory>${project.basedir}/res</resourceDirectory>
<nativeLibrariesDirectory>${project.basedir}/src/main/native</nativeLibrariesDirectory>
<genDirectory>${project.basedir}/gen</genDirectory>
				<sdk>
					<platform>${android.sdk.version}</platform>
				</sdk>
			</configuration>
		</plugin>
<!-- ... -->

In der Plugin-Konfiguration wird das maven-scala-plugin aktiviert und die Pfade des android-maven-plugins werden auf unser Projektlayout angepasst.

Screenshot einer laufenden Scala-Anwendung im Android-Emulator

Mit diesen Einstellungen können wir bereits loslegen und eine in Scala entwickelte Android-Anwendung auf der Kommandozeile bauen. Mit dem folgenden Befehl wird das gebaute Artefakt sogleich auf allen über adb erreichbaren Geräte, also gestarteten Emulatoren oder per USB angeschlossene Debug-Geräte, installiert und gestartet.

mvn clean package android:deploy android:run

Technologische 16-Bit Hürden und langsames Dex’en

Scala-Quelltext wird durch den Scala-Compiler scalac in JVM-Bytecode übersetzt. Dieser Bytecode ist vollständig binärkompatibel mit herkömmlichen Java-Artefakten. Scalas Standardbibliothek wird ebenfalls als JVM-Bytecode in einem Java Archive (JAR) ausgeliefert. Android-Anwendungen werden hingegen auf der an die JVM angelehnten und auf Googles auf mobile Bedürfnisse optimierten DalvikVM ausgeführt.

Bei der herkömmlichen Android-Entwicklung mit Java wird der durch den Java-Compiler erzeugte Bytecode nachträglich durch den im Android-SDK beiliegenden dex-Compiler in DalvikVM-Bytecode überführt und mit dem apkbuilder in ein für die Android-Plattform ausführbares Binärpaket (APK) verschnürt. Zur Laufzeit auf der Android-Plattform steht unserer Anwendung dann eine an die Java-Standardbibliothek angelehnte Android-Bibliothek zur Verfügung.

Bisher ist es auf der Android-Plattform leider nicht möglich, neben der Standardbibliothek weitere Bibliotheken zur gemeinsamen Nutzung durch Anwendungen benutzerfreundlich zu installieren. Daher müssen wir als ersten wichtigen Punkt festhalten, dass die Scala-Bibliothek zusammen mit unserer Anwendung in einem gemeinsamen APK ausgeliefert werden muss. Die vollständige Scala-Bibliothek hat bereits eine Größe von etwa 7 Megabyte. Hinzu kommt, dass Dalviks dex-Compiler maximal 65k Methoden auf dem Klassenpfad verarbeiten kann. Wenn man sich auf die scala-library-2.10.0-M7 beschränkt, bleibt man gerade eben unter diesem Limit. Unser Minimalbeispiel einer „Hello Scala“-Anwendung hat als resultierendes APK bereits eine Größe von 2,6 Megabyte. Allein das Übersetzen der scala-library in DalvikVM-Code dauert bei jedem Build-Lauf etwa 30 Sekunden.

ProGuard schüttelt am Classpath

Android-Apps, die vom Benutzer aus dem Play Store installiert werden, sollten nicht größer sein als unbedingt nötig. Jeder von der Downloadgröße verschreckte Benutzer ist eventuell ein verlorener Kunde. Hier hilft uns das für diese Zwecke vorgesehende und vom Android-SDK mitgelieferte Werkzeug ProGuard. Neben Funktionen zum Optimieren und Obfuszieren von JVM-Bytecode unterstützt ProGuard auch den sog. Treeshake. Dabei wird der Java-Bytecode unserer App vor dessen Übersetzung in DalvikVM-Code nach unbenutzten Klassen durchsucht und erleichtert. So werden lediglich die von unserer Anwendung eingesetzten Scala-Klassen im APK ausgeliefert.

In der Dokumentation des maven-android-plugins wird eine ProGuard-Konfiguration vorgestellt, die eine sehr umfangreiche Optimierung durchführt. Die folgende proguard.cfg zeigt eine Minimalkonfiguration, die lediglich den erwähnten Treeshake durchführt und auf weitere Optimierungs- und Obfuszierungsschritte verzichtet. Das erzeugte Artefakt kann mit LogCat weiterhin debugged werden.

-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-dontwarn scala.**
-dontobfuscate
-dontoptimize

-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService

-keepclasseswithmembernames class * {
    native <methods>;
}

-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet);
}

-keepclasseswithmembers class * {
    public <init>(android.content.Context, android.util.AttributeSet, int);
}

-keepclassmembers class * extends android.app.Activity {
   public void *(android.view.View);
}

-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

-keep class * implements android.os.Parcelable {
  public static final android.os.Parcelable$Creator *;
}

Die gezeigte Methode eignet sich ausgezeichnet, um mit Maven Release-Artefakte von Android-Anwendungen zu erzeugen. Allerdings beanspruchen die Optimierungsschritte, die ProGuard vollzieht, nicht unerheblich Zeit. Daher ist diese Methode für den alltäglichen Entwicklungszyklus nicht optimal. Im zweiten Artikel dieser Serie werden wir deshalb einige Möglichkeiten kennenlernen, um eine bessere Eclipse-Integration und einen schnelleren Build-Prozess zu erzielen.

Holisticon AG — Teile diesen Artikel

Über den Autor