Android-Entwicklung mit Groovy

Seite 5: Projektgröße

Inhaltsverzeichnis

Zwar hat das Groovy-Jar eine Größe von 4,5 Megabyte, doch ist ein generiertes Android Package (APK) deutlich kleiner – bei der Gr8conf-Konferenz-App beispielsweise nur noch circa zwei Megabyte. Durch das Verwenden von ProGuard lässt sie sich darüber hinaus noch einmal verringern. Durch den Einsatz untenstehender Regeln kann man sie auf circa ein Megabyte reduzieren.

-dontobfuscate
-keep class org.codehaus.groovy.vmplugin.**
-keep class org.codehaus.groovy.runtime.dgm*
-keepclassmembers class org.codehaus.groovy.runtime.dgm* {
*;
}
-keepclassmembers class ** implements
org.codehaus.groovy.runtime.GeneratedClosure {
*;
}
-dontwarn org.codehaus.groovy.**
-dontwarn groovy**

Als Beispiel für eine Verbesserung des Codes durch Groovy soll der Aufrufs eines asynchronen Tasks dienen. Zunächst der entsprechende Code in Java:

AsyncTask task = new AsyncTask<String, Integer, Long>() {
protected Long doInBackground(String... params) {
// do something
}
}
task.execute("a", "b");

Ohne weitere Arbeit lässt sich das in Groovy wie folgt notieren:

AsyncTask task = { String... params->
// do something
}
task.execute('a', 'b')

Bei der Zuweisung einer Closure zu einer Variablen vom Typ eines Interfaces mit einer Methode oder einer abstrakten Klasse mit einer abstrakten Methode wird die Closure als Implementierung der abstrakten Methode verwendet.

Noch schöner wäre:

Async.execute('a', 'b') { args ->
// do something
}

Um das zu erreichen, müssen Entwickler eine Klasse Async erstellen.

Wenn das letzte Argument vom Typ Closure ist, gilt, dass es nach der Argumentliste notiert werden kann.

class Async {
static def execute(Object... args) {
if(!(args.last() instanceof Closure))
throw new UnsupportedOperationException('Last argument has to
be a Closure')
AsyncTask task = args.last() // Typumwandlung des letzten
// Arguments
args = args - args.last() // Closure aus der Liste der
// Argumente entfernen
return task.execute(*args) // Liste als Argumente verwenden
}
}

AsyncTask besitzt allerdings noch Callback-Methoden, um beispielsweise auf das Ende oder den Abbruch der Ausführung zu reagieren. Das ist hier noch nicht berücksichtigt, lässt sich aber vergleichsweise einfach ergänzen.

Folgender Aufruf soll möglich sein:

Async.execute('a', 'b',
onCancelled: {
// do on cancel
},
onPostExecute: {
// do after execution
}
) {
// do something
}

Die Implementierung sieht nun so aus:

class Async {
static def execute(Object... args) {
// benannte Argumente landen in einer Map<String, Object> als
// erster Parameter
def map = args.first()
def cls = args.last()
if(!(cls instanceof Closure))
throw new UnsupportedOperationException('Last argument has to
be a Closure')
AsyncTask task
if(map instanceof Map) {
args = args - map // aus der Liste der Argumente
// entfernen
map.doInBackground = cls // doInBackground ist die Haupt-
// Methode eines AsyncTasks
task = map as AsyncTask // Typumwandlung der Map in einen
// AsyncTask
} else
task = cls
args = args - cls
return task.execute(*args)
}
}

Bei der Typumwandlung einer Map in ein Interface oder in eine Klasse werden die Map-Einträge mit den Namen der Methoden als deren Implementierung verwendet. Wenn man statt Async.execute('a', 'b') zum Beispiel nur async('a', 'b') schreiben möchte, ist auch das einfach mit einem Extension-Modul möglich.