Wie die Jungfrau zum Kinde: Android als Java-EE-Entwickler

PrintFriendly and PDF

Wenn man als Java-Entwickler sonst immer komplexe Geschäftsanwendungen für den Server entwirft, bietet ein Android-Projekt willkommene Abwechslung. Spannend ist die Frage, ob sich liebgewonnene Architekturstile auf Android-Apps übertragen lassen. Und siehe da! Es klappt sogar erstaunlich gut.

Das Android-Ökosystem ist zwar umfangreich, aber es ist gut dokumentiert und man findet viele Bücher, gerade für den Einstieg. Als erfahrener Java-Entwickler reichen ein paar Tage aus, um die ersten eigenen Apps zu schreiben. Für die vielen Detailfragen, auf die man unweigerlich stößt, bietet vor allem Stack Overflow jede Menge Antworten.

Spätestens, wenn die ersten Activities und Fragments am Laufen sind und komplexe Fachlogik umgesetzt werden muss, taucht die Frage auf, wie man eine Android-App am besten strukturiert. In einer Server-Anwendung hat es sich bewährt, ein System in Schichten zu untergliedern und die einzelnen Komponenten per Dependency-Injection zu verbinden. Mit Datenmappern wie JPA lässt sich das Domänenmodell komfortabel auf eine Datenbank abbilden und eine reiche Auswahl an Open-Source-Bibliotheken erfüllt fast jeden Wunsch an Zusatzfunktionalität.

Auch in einer Android-App kann man diesen Architektur-Stil beibehalten. Nur muss man sich selber um die technische Umsetzung kümmern. Denn von Haus aus bietet Android nur wenig Unterstützung, komplexe Anwendungen zu strukturieren. Gehen wir die Themen der Reihe nach durch…

Androids eigene und fremde Bibliotheken

Die Dalvik VM ist keine JVM! Das äußert sich an manchen sehr ungewohnten Ecken, beispielsweise darin, dass die Hardware-Architektur Einfluss auf die Byte-Repräsentation von Strings hat. Die Klassenbibliotheken von Android sind API-kompatibel zu den Java-Bibliotheken, die Implementierung ist jedoch komplett eigenständig und verhält sich manchmal doch anders. Java-interne Klassen wie die com.sun-Pakete fehlen vollständig.

Dies alles hat zur Folge, dass viele beliebte Open-Source-Bibliotheken zunächst einmal nicht auf Android laufen. In manchen Fällen gibt es Portierungen für Android, aber viele Bibliotheken kann man einfach nicht verwenden.

Auch die Performance hakt an vielen Stellen. Die Klassenbibliotheken sind nicht in dem Maße optimiert, wie man es von Java gewohnt ist. Zudem ist die Hardware einer typischen Android-Umgebung erheblich schwächer als die einer typischen Server-Umgebung. Das führt dazu, dass man das Thema Performance in der täglichen Entwicklung viel mehr beachten muss als auf dem Server. Eine große Schwachstelle ist beispielsweise die Performance der Reflection-API, die sich erst kürzlich verbessert hat.

Man muss sich zudem darauf einstellen, dass die Qualität der Android-Bibliotheken nicht das Niveau hat, das man von den meisten Javascript- bzw. serverseitigen UI-Frameworks gewohnt ist. Auch in den neuesten API-Versionen stößt man immer wieder auf Inkonsistenzen und Probleme, die sich wohl nur dadurch erklären lassen, dass Android mit hohem Tempo aus dem Boden gestampft wurde und bis heute darunter leidet.

Dependency-Injection für Android

Die großen Dependency-Injection-Frameworks Spring und Weld laufen nicht auf Android. Von Guice gibt es die Fassung RoboGuice, die weit verbreitet ist. RoboGuice verwendet, ebenso wie Guice, Annotationen, die zur Laufzeit per Reflection ausgewertet werden. Aufgrund der oben erwähnten Performance-Probleme bringt dieser Ansatz in größeren Anwendungen spürbare Nachteile.

Eine sehr gute Alternative dazu ist Dagger. Dagger unterstützt die javax-inject-API. Aber im Gegensatz zu anderen Dependency-Injection-Frameworks wertet Dagger die Annotationen nicht zur Laufzeit aus, sondern schon beim Kompilieren. Ein eigener Annotation-Prozessor untersucht den Code beim Kompilieren auf alle Abhängigkeiten und generiert Source-Code, der dann zur Laufzeit ausgeführt wird, um die Objekte wie gewünscht zu verbinden. Performance-Probleme können so nicht entstehen. Und ein weiterer Nebeneffekt ist, dass schon beim Kompilieren Fehler angezeigt werden, wenn der Graph der Abhängigkeiten inkonsistent ist.

Dagger kann jedoch nur Abhängigkeiten innerhalb der eigenen Code-Basis verwalten, wo der Lebenszyklus der Objekte unter eigener Kontrolle ist, also in erster Linie für die Fachlogik. Um auch auf Androids UI-Elemente per Dependency-Injection zugreifen zu können, benötigt man eine weitere Bibliothek des Dagger-Entwicklers: Butter Knife. Butter Knife vereinfacht den Zugriff auf Android-Ressourcen, deren Lebenszyklus unter der Kontrolle von Android selber stehen.

Beide Bibliotheken in Kombination bieten viel Komfort durch Dependency-Injection, ähnlich, wie man es auf dem Server gewohnt ist. Das Programmiermodell bleibt trotzdem etwas ungewohnt und manche Funktionen (wie die Unterstützung von @PostConstruct) werden nicht unterstützt.

O/R-Mapping für Android

Jede Android-Umgebung bietet Zugriff auf eine SQLite-Datenbank. Auch wenn diese Datenbank nicht ganz mit Postgresql & Co mithalten kann, unterstützt sie viele wichtige Features wie Transaktionen und Trigger. Als Server-Entwickler, der gerne domänenorientiert arbeitet, möchte man den Komfort von O/R-Mapping wie JPA auch in Android-Apps nutzen. Zwar gibt es hier viele Bibliotheken, jedoch nur wenige ausgereifte Lösungen.

Zu empfehlen ist ORMlite, ein O/R-Mapper (nicht nur) für SQLite. Android wird bei ORMlite speziell unterstützt, und die Dokumentation bietet viel Hilfe für die Eigenheiten von Android. ORMlite nutzt proprietäre Annotationen für das Mapping der Domänenklassen. Beim Thema Performance gelten die gleichen Probleme wie beim Dependency-Injection. Hier bietet ORMlite eine zwar gewöhungsbedürftige, aber brauchbare Lösung: Die Mapping-Metadaten kann man mit einem kleinen Hilfsprogramm in eine Datei exportieren lassen. Beim Start der App liest ORMlite die Informationen aus dieser Datei und startet dann erheblich schneller als wenn Annotationen gescannt werden.

Unit-Tests mit Android

Android unterstützt Unit-Tests mit JUnit. Die Unit-Tests werden üblicherweise zusammen mit der eigentlichen App auf ein Gerät bzw. den Emulator geladen und dort laufen gelassen. UI-Tests lassen sich auf diese Art, beispielsweise mit Robotium, gut entwickeln. Tests für die Fachlogik leiden jedoch unter diesem eher schwergewichtigen Ansatz.

Eine bessere Alternative besteht darin, die Fachlogik aus dem Android-Projekt komplett in ein Library-Projekt zu extrahieren und Unit-Tests dort ohne Android-Abhängigkeiten auszuführen. Library-Projekte sind Eclipse-Projekte ohne Abhängigkeiten zu den Android-Bibliotheken, die zur Laufzeit in die Android-App eingebunden werden.

Wenn man die Trennung von UI-Code und Fachlogik genau nimmt, sollte man auch die Datenzugriffslogik in ein Library-Projekt extrahieren. Dann kann man Unit-Tests schreiben, die ohne die von Android bereitgestellte SQLite-Datenbank auskommen. Eine extrem schnelle Alternative dazu ist es, SQLite für die Unit-Tests als In-Memory-Datenbank zu verwenden. Tests mit Dutzenden von Datenbank-Roundtripps laufen dann in wenigen Millisekunden.

Fazit

Auch als Java-Enterprise-Entwickler kann man Android-Apps entwicklen, die den gewohnten Ansprüchen hinsichtlich Struktur und Performance entsprechen. Android-Apps zu programmieren macht Spaß, man leidet nur immer wieder an Unzulänglichkeiten des Android-Ökosystems. Trotzdem lassen sich damit komplexe, gute strukturierte und schnelle Apps bauen.