Wie oft hört man heute die Worte „Dafür gibt’s doch eine App!“ – Und wenn nicht? Dann muss man wohl selbst eine entwickeln. Ob nun iOS, Android, Symbian oder gleich eine Hybrid-App, die Entscheidung kann einem keiner abnehmen. Entscheidet man sich für Android, so beginnt man in der Regel als Einsteiger in die Android-Welt nach der Installation des Android-SDK mit einer einfachen App wie einem Hello World. Gebaut, getestet und gepackt – natürlich alles aus Eclipse heraus.
Klingt nach einem ähnlichen Vorgehen, mit dem ich vor ca. 10 Jahren die Java-Entwicklung begonnen habe. Da schrieb ich Ant-Scripts und baute damit meine Java-Projekte. Aber liest man sich die offiziellen Entwickler-Tutorials durch, so wird empfohlen, das Projekt mit einem Befehl aus dem SDK auf der Kommando-Zeile zu bauen. Geht das nicht anders?
Wer beim Durchsuchen des SDKs auf das Verzeichnis „tools/ant“ stößt, merkt schnell: Doch! Und auch wer in der Dokumentation weiterliest, findet einen entsprechenden Eintrag, in dem das Bauen und Deployen mit ant beschrieben werden.
The Maven way of life
Arbeitet man aber lieber mit Maven, sucht man in der offiziellen Dokumentation vergebens. Es gibt ein „inoffizielles“ android-maven-plugin, welches das APK-Archiv erzeugt und das auch auf alle angeschlossenen Geräte (inkl. laufender Emulatoren) deployen kann.
Als Verzeichnis-Struktur wurde die folgende gewählt:
- src / main / assets
- src / main / java
- src / main / resources
- src / test / java
- AndroidManifest.xml
- pom.xml
Dieses Beispiel geht von einer leeren „Standard“-pom.xml Datei aus, die der Maven-Kurzanleitung entnommen werden kann. Die groupId und artifactId, sowie die version, url und name spielen keine Rolle und können gemäß der Maven-Richtlinien gewählt werden. Das packaging muss hingegen auf „apk“ geändert werden. Dies ist das Dateiformat, das auf das Android-Telefon gespielt werden kann.
Zum Bauen der Android-App benötigen wir zwei Plugins innerhalb des build-Knotens:
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> <encoding>UTF-8</encoding> </configuration> </plugin> <plugin> <groupId>com.jayway.maven.plugins.android.generation2</groupId> <artifactId>android-maven-plugin</artifactId> <extensions>true</extensions> <version>3.1.1</version> <configuration> <assetsDirectory>src/main/assets</assetsDirectory> <resourceDirectory>src/main/resources</resourceDirectory> <sdk> <platform>8</platform> </sdk> </configuration> </plugin> </plugins> </build>
Das maven-compiler-plugin wird verwendet, um die Java-Version auf 6.0 und das Source-Encoding auf UTF-8 festzulegen. Das zweite Plugin ist das angesprochene android-maven-plugin. Als Konfiguration wird das assets– und resource-Verzeichnis manuell festgelegt, da diese standardmäßig auf „/assets“ und „/res“ eingestellt sind. – Ich favorisiere den Maven-Weg und lege möglichst alle Bestandteile des Programms in „src/main“ ab. Für die AndroidManifest.xml habe ich bisher aber noch nicht den idealen Ort finden können.
Um die Abhängigkeiten zu dem Android-SDK einzubinden, ist noch ein Eintrag im „dependencies“-Knoten nötig:
<dependency> <groupId>com.google.android</groupId> <artifactId>android</artifactId> <version>2.2.1</version> <scope>provided</scope> </dependency>
Es gibt nicht sehr viele Android-Versionen im zentralen Maven-Repository. Die Entscheidung der hier verwendeten Version muss also an dieser Stelle abgewägt oder eine eigene Abhängigkeit in das (lokale) Repository installiert werden. Die hier genannte Version 2.2.1 entspricht der Android-Plattform 8. Diese muss beim android-maven-plugin immer separat angegeben werden.
Let’s go
Das android-maven-plugin verursachte bei mir Probleme mit Maven 2.2.1, und ich rate daher jedem, auf Maven 3.0.3 zu wechseln. Die hier beschriebene Vorgehensweise wurde mit Maven 3.0.3 getestet. Des weiteren muss vor dem Bauen sichergestellt werden, dass die System-Variable „ANDROID_HOME“ gesetzt ist und auf das Android-SDK verweist. Alternativ kann dies auch direkt am android-maven-plugin konfiguriert werden. – Dies stellt aber selbst mit nur zwei Entwicklern oftmals keinen praktikablen Weg dar.
Mittels des Aufrufs „mvn package“ auf der Kommandozeile kann das Projekt gebaut werden. Anschließend befindet sich das APK-Archiv im „target“-Verzeichnis. Um auf ein Android-Phone zu deployen, muss dieses mit einem USB-Kabel verbunden werden. In den Entwickler-Einstellungen des Telefons muss der USB-Debug-Modus aktiviert und das Telefon für die Übertragung entsperrt bleiben. Deployed wird die App mit dem Befehl „mvn android:deploy“. Startet man alternativ oder zusätzlich einen Android-Emulator oder hängt weitere Geräte an den Rechner, so wird standardmäßig auch auf diesem/diese deployed. Wer die Kommandozeile scheut, kann die Maven-Phasen und Goals natürlich auch aus einer IDE anstoßen.
Wichtig ist, dass ein Aufruf des android-maven-plugins nicht die App neu kompiliert. Es muss immer eine Phase aus dem Maven-Lifecycle höher oder gleich „package“ vorher aufgerufen werden. Berücksichtigt man dies nicht, wird erneut versucht, das vorhandene APK-Archiv auf dem Emulator oder dem Gerät zu installieren. Einen sauberen Build- und Deploy-Vorgang erreicht man mit dem Aufruf von „mvn clean package android:deploy“. Alle weiteren Build-Parameter des Plugins kann man dieser Dokumentationsseite entnehmen.
Wie von Maven gewohnt, werden auch gleich alle Unit-Tests in „src/test/java“ in den entsprechenden Maven-Test-Phasen ausgeführt; außerdem funktionieren Features wie die JavaDoc-Generierung out-of-the-box. Es handelt sich nun um ein gewöhnliches Java-Projekt, das viele bekannte Build-Pattern anwendbar macht.
Optimieren und Obfuszieren
Oft ist es nicht gewünscht, dass Fremde den Java-Code dekompilieren und dann lesen können. Er sollte somit obfusziert werden, wobei soviel Java-Code wie möglich durch abstrakte Zeichenketten ersetzt wird. Aus de.holisticon.examples.HelloWorld kann man auch a.b.c.D machen. Natürlich müssen ebenfalls manuell und automatisch erstellte Deskriptoren angepasst werden. Dies sollte man nicht nur aus Bequemlichkeit am Ende der Entwicklung machen, es sollte vielmehr das Bewusstsein darüber herrschen, dass ggf. auftretende Fehlermeldungen und Stacktraces dadurch unleserlich werden. (Wegen der Deskriptoren werden aber die Android-Activities gerne von der Obfuszierung ausgenommen, daher wurde gerade die HelloWorld-Klasse im angehängten Beispiel-Code nicht obfusziert.)
Für das Obfuszieren verwenden wir ProGuard, eine Software, die nicht nur obfusziert, sondern auch optimiert und ggf. nicht verwendeten Code entfernt. Damit sie nicht bei jedem Entwicklungs-Build aktiv ist, sollte man ein separates Maven-Profil anlegen, welches z.B. „production“ genannt wird. Das android-maven-plugin bringt von Haus aus eine gute ProGuard-Integration mit, die mit relativ wenig Aufwand die Integration der Obfuszierung ermöglicht. Die „pom.xml“ wird um Folgendes erweitert:
<profiles> <profile> <id>proguard</id> <build> <plugins> <plugin> <groupId>com.jayway.maven.plugins.android.generation2</groupId> <artifactId>android-maven-plugin</artifactId> <configuration> <proguard> <skip>false</skip> </proguard> </configuration> </plugin> </plugins> </build> </profile> </profiles>
Weiter wird eine „proguard.cfg“ im Projekt-Root-Verzeichnis benötigt, die aus den ProGuard-Beispielen (Siehe: „A complete Android application“) fast 1:1 übernommen werden kann. Ggf. kann ein „-optimizationpasses 5“ voran gestellt werden, um bessere Optimierungs-Ergebnisse zu erhalten. Gebaut, optimiert und verschleiert wird mit folgendem Maven-Aufruf: „mvn -P production clean package“. Dies dauert merklich länger als ein normaler Build-Vorgang und spricht ebenfalls dafür, ProGuard so selten wie nötig bei der Entwicklung zu starten.
Zum schnellen Nachvollziehen des Beispiels gibt es hier das Projekt-Archiv – mit ProGuard-Integration.
Und nun?
Dieser Artikel stellt nur den Anfang einer Reihe von Artikeln über das Bauen von Apps dar. Unser Ziel ist, zu zeigen, dass auch in der App-Entwicklung viele bekannte Vorgehensweisen aus der „klassischen“ Entwicklung, wie beispielsweise CI-Systeme und Code-Analysen eingesetzt werden können.