zurück zum Artikel

Projekt-Setup für die Entwicklung kommerzieller Android-Applikationen

Kai Altstaedt

Für Android-Projekte, die einen signifikanten Anteil von Business- oder Spiellogik enthalten, lohnt es sich, die Applikation in separaten Modulen zu entwickeln. Insbesondere Komponenten zum Ausführen der zentralen Abläufe auf dem PC können sinnvoll sein.

Projekt-Setup für die Entwicklung kommerzieller Android-Applikationen

Für Android-Projekte, die einen signifikanten Anteil von Business- oder Spiellogik enthalten, lohnt es sich, die Applikation in separaten Modulen zu entwickeln. Insbesondere Komponenten zum Ausführen der zentralen Abläufe auf dem PC können sinnvoll sein.

Der Android-Debugger ist gut und schnell, allerdings kann er niemals die Bequemlichkeit und Geschwindigkeit eines Code-Compile-Test-Zyklus der reinen PC-Entwicklung erreichen. Der Unterschied in der Geschwindigkeit wird noch größer, wenn man von der Entwicklung der Engine zur Entwicklung der Inhalte (Geschäftsprozesse oder Level-Design) kommt. Der Bedarf nach separaten GUI-Schichten entsteht quasi von selbst, wenn von Anfang an das Ausführen der Anwendung auf einem PC Teil der Projektanforderungen war.

Als Beispiel, um die Mechanismen zum Modularisieren des Codes in separate Projekte zu illustrieren, soll das Spiel "Fluki" dienen. Hier geht es darum, eine kleine Flunder durch Labyrinthe zu steuern. Die Grafik-Engine des Spiels nutzt OpenGL. Die strukturellen Parameter des Projekts sind:

Der Fluki-Entwicklungszyklus: Leveldesign, Test auf PC und Android-Gerät (Abb. 1)

Der Fluki-Entwicklungszyklus: Leveldesign, Test auf PC und Android-Gerät (Abb. 1)


Der Fokus des Artikels liegt auf dem Erzeugen einer passenden Eclipse-Projekt-Struktur und nicht im Zeigen einer Hardware-Abstraktion. Um jedoch die Design-Strategie der Hardware-Unabhängigkeit zu erklären, ist ein kleiner Code-Abschnitt am Ende des Artikels aufgeführt.

Aus den Anforderungen ergibt sich die Trennung des Codes und der Ressourcen in Module. Alle Module sind als eigene Eclipse-Projekte realisiert, die einander in unterschiedlicher Weise referenzieren.

Das vorgestellte Basis-Schema lässt sich auf beliebige Applikationen übertragen und hat sich in der Vergangenheit bewährt. Die Referenzen zwischen den Projekten sind dabei auf unterschiedliche Weise in Eclipse realisiert.

Mittel der Wahl zur Entwicklung von Android-Apps, ist die Eclipse-Entwicklungsumgebung mit Android- Plug-in. Zur besseren Übersicht sollen im vorliegenden Artikel alle Module als separate Eclipse-Projekte verwaltet werden. Die Referenzen zwischen den Projekten sind (bis auf einen Fall) ebenfalls mit Eclipse-Bordmitteln erzeugt.

Das Core-Modul ist ein "Pure-Java"-Projekt. Es enthält weder Ressourcen noch plattformabhängigen Code. (Anmerkung: Die Eclipse-Projekte des "Fluki"-Projekts tragen auf den Abbildungen im Namen noch den Arbeitstitel "SpaceBubble" oder "SpaceFish".)

Das Core-Modul im Eclipse Package Explorer (Abb. 2)

Das Core-Modul im Eclipse Package Explorer (Abb. 2)

Abstrakte Klassen oder Java-Interfaces kapseln das gesamte I/O. Dabei kommt eine Mischung der Singleton-, Bridge- und Facade- Design-Pattern zum Einsatz. Innerhalb der Klassen des Core-Moduls übernehmen plattformabhängige Module eine vollständige Instanziierung der Singletons. Somit ist das Core-Modul nicht alleine ausführbar, keine der Klassen hat eine main-Methode.

Ein besonderer Aspekt ist beim Verwenden von Ressourcen zu beachten; zum Beispiel beim Adressieren von Ressourcen im Core-Modul, wo unter anderem nach einem bestimmten Ereignis eine Nachricht angezeigt werden soll. Das Adressieren in der Android-Umgebung ist recht speziell und mit der Verwendung der vorkompilierten Ressource-Klassen "R.id.xxx" realisiert. Um eine zyklische Abhängigkeit zwischen den Android-Modulen und dem Core-Modul zu vermeiden, können diese Klasse nicht verwendet werden. Als Lösung lassen sich im Core-Modul Enum-Konstanten für die im Core-Projekt angesprochenen Ressourcen definieren. Die jeweiligen I/O-Schichten enthalten Funktionen, um die Konstanten zu Ressourcen aufzulösen.

Der Umfang der Ressourcen, der zu kapseln ist, hängt von der Art der Applikation ab. Für das Fluki-Projekt wurden beispielsweise Konstanten für alle Sounds und einen Teil der Texte definiert. Ebenso hängt die Anzahl der benötigten Abstraktionen auf der I/O-Seite von der Art der Applikation ab. Neben der Grafik-Ausgabe liegen die Eingaben (Klicks mit der Maus oder Berühren des Bildschirms), Datei-Input, Sound- Ausgabe und Text-Ausgaben des Projekts gekapselt vor. Für das Unterscheiden zwischen der kostenfreien und kostenpflichtigen Version gibt es eine zentrale statische Variable im Core-Modul. Auf ihr basieren alle versionsbezogenen Programmentscheidungen.

Das Android-Library-Modul enthält den gesamten plattformspezifischen Code (zum Beispiel die Implementierung des OpenGL-Layers unter Verwendung des Khronos-Pakets) und die Auflösung der Ressource-Konstanten zu den Android-Ressource-Klassen. Es wurde mit dem Android-Project-Wizard des SDKs erzeugt. Um es als "Library" für andere Projekte nutzen zu können, ist die Checkbox für "is library" beim Erstellen zu aktivieren.

Einstellungen für Android-Library-Modul (Abb. 3)

Einstellungen für Android-Library-Modul (Abb. 3)


Im Vergleich zum PC-Modul, das häufig nur ein Test- und Debugging-Treiber ist, enthält das Android- Library-Modul die komplette Benutzeroberfläche (Navigation, Layouts, Grafiken, Texte etc.) mit der Ausnahme des Splash-Screen. Wie auch das Core-Projekt ist es allein nicht lauffähig. Eine Besonderheit sind die Level-Definitionsdateien. Um sie einlesen zu können, sind sie im assets-Verzeichnis gespeichert.

Das Android-Library-Modul im Explorer (Abb. 4)

Das Android-Library-Modul im Explorer (Abb. 4)


Das Android-Library-Modul referenziert das Core-Modul über einen einfachen "Required Project"-Link in der Sektion "Java Build Path" der Projekteigenschaften.

Benutzen des Core-Modules des lib-Projekts (Abb. 5)

Benutzen des Core-Modules des lib-Projekts (Abb. 5)


"Android Free Version" ist ein Android- Projekt, das mit dem SDK-Wizard erzeugt wurde. Es referenziert das Android-Library-Modul. Die Referenz wird nicht im Build Path eingetragen, sondern in der Android- Sektion der Projekteinstellungen.

Referenzierung eines Android-Library-Projekts (Abb. 6)

Referenzierung eines Android-Library-Projekts (Abb. 6)


Das Modul enthält nur eine Launch-Activity. Sie zeigt einen einfachen Splash-Screen an, setzt die Versionsvariable auf "free" und stößt einige, im Hintergrund laufende Maßnahmen zur Datenaufbereitung an. Nach einer definierten Zeit startet die main-Activity des Android-Library-Projekts.

Das freie Projekt im Explorer (Abb. 7)

Das freie Projekt im Explorer (Abb. 7)


Es ist dabei zu beachten, dass alle Tätigkeiten des Android-Library-Moduls in der AndroidManifest-Datei des Free-Version-Moduls erneut zu definieren sind, damit sie sich in der Free-Version aufrufen lassen.

Ein gesonderter Trick ist für die Ressourcen zu nutzen, die im assets-Verzeichnis liegen. Ist geplant, eine solche Datei im Kontext des Free-Version-Moduls aufzurufen (zum Beispiel beim Laden einer Textdatei für eine Level-Definition), erwartet die App, und somit der Android-Precompiler, das assets-Verzeichnis als Teil des Free-Version-Moduls. Da der Precompiler außerhalb des Eclipse- Workspace läuft, funktionieren die Eclipse-Referenzen hier nicht. Eine Lösung stellt ein symbolischer Link auf Dateisystem-Ebene dar (in Windows ein "mklink /D assets C:\user\kai\workspace\SpaceFishAndroLib\assets" oder in der Unix-Welt ein "ln -s /... assets").

Wichtiger Hinweis: Wenn man Filesysteme innerhalb des Workspace zusammenlinkt, werden alle Metadaten des Versionskontrollsystems ebenfalls verlinkt. Im Projekt "Free Version" ist das verknüpfte assets-Verzeichnis folglich in die ignore-Liste des Systems einzutragen.

Falls der Umfang der Dateien zwischen Free und Full Version stark differiert, kann es auch Sinn machen, anstelle des Links die jeweiligen Dateien vor dem Build-Prozess zu kopieren.

Fast identisch mit dem freien ist das Modul "Android Full Version". Der einzige Unterschied ist, dass die Splash-Activity das System auf Full Version einstellt. Das PC-Modul hingegen enthält alle JUnit-Tests für die Klassen des Core-Moduls, eine Implementierung des I/O-Layers und eine Main-Class, die einen Swing-Client für die PC-Version der App startet. Das PC-Modul referenziert im Build-Path nur das Core-Modul. Für die Open-GL-Anbindung verwendet die Implementierung Java Bindings for OpenGL (JOGL).

Im Fluki-Beispiel ist der PC-Client nur ein Test-Client. Er hat keine ausgeklügelte Benutzeroberfläche, für die Navigation zwischen den Spiel-Kampagnen gibt es beispielsweise keine grafischen Buttons, sondern nur ein einfaches Drop-down-Menü. (siehe Abbildung 1)

Die Bild-, Ton- und Textressourcen liegen im Android-Modul, um die Funktionen des Betriebssystems für Internationalisierung und Unterstützung unterschiedlicher Hardware möglichst vollständig nutzen zu können. Würde man nun eine Referenz auf das Android-Library-Modul in das PC-Modul einfügen, hätte man den Android-Content in selbiges eingebracht, was nicht erwünscht ist. Anstelle einer Projekt-Referenz im Build-Path werden die Ressource-Verzeichnisse (assets, drawable, values, raw) deshalb über "Eclipse-Referenzen" in das PC-Modul eingebunden.

Da das Erzeugen und Verwenden von Referenzen in Eclipse als Feature zwar mächtig, aber wenig bekannt ist, folgt eine Anleitung:

  1. Auswählen des Verzeichnisses, in dem die Referenz erscheinen soll.
  2. Rechtsklick, um ein neues Verzeichnis zu erzeugen.
  3. Klick auf den Advanced-Button.
  4. Auswählen von "Link to alternate location".
  5. Klick auf "Variables".
  6. Auswahl der Variablen "WORKSPACE_LOCATION" und des "Extend..."-Buttons.
  7. Auswahl des Projekts und des Quellordners, der verlinkt werden soll.
  8. Klick auf "OK".
Eclipse-Ordner verlinken (Abb. 8)

Eclipse-Ordner verlinken (Abb. 8)


Die verlinkten Verzeichnisse erscheinen nun in der Ordnerliste mit einem entsprechenden Symbol.

PC-Module mit eingebundenen Ressourcen (Abb. 9)

PC-Module mit eingebundenen Ressourcen (Abb. 9)

Nachdem die Ressourcen in den src-Ordner des PC-Moduls eingebunden sind, lassen sich Bild-Dateien wie Texturen mit dem Classloader laden. Für Sound-Dateien ist zu beachten, dass das .ogg-Format auf Android das Format der Wahl ist. Ogg-Vorbis wird vom Java SDK nicht ohne weiteres unterstützt. Das Verwenden der Bibliothek easyogg ermöglicht das Abspielen ogg-codierter Sounds.

Bei den Texten muss man ein bisschen mehr Arbeit investieren, die aber nicht kompliziert ist. Alle Android- Text-Ressourcen sind als Name-Value-Paare in XML-Dateien gespeichert. Im PC-Modul ist deswegen ein XML-Parser enthalten, der alle XML-Dateien des /values-Verzeichnis einliest und die enthaltenen Paare in einer HashMap speichert.

Um nun eine Nachricht anzuzeigen, ruft das Core-Modul das aktuelle Message-System auf und definiert den Text über eine der Enum-Konstanten. In ihr ist der Name der Nachricht enthalten. Das PC-Message- System holt den Text aus der HashMap und zeigt ihn über eine OpenGL-Textfunktion an. Das Android- Message-System verwendet zur Anzeige hingegen eine dynamisch eingeblendete TextView.

Das Konzept der I/O-Kapselung der plattformspezifischen Teile folgt einem einfachen Grundsatz: Die Schnittstellen sollten so einfach wie möglich sein. Oder wie Kent Beck es in "Extreme Programming Explained"[1] formuliert: "Don't build frameworks".

Beispiel: Das Fluki-Spiel basiert auf OpenGL. Es würde keinen Sinn machen, die OpenGL-Basis vor der Core-Engine zu verstecken, da sowohl die Android- als auch die PC-Version OpenGL zur Implementierung verwenden. Das Resultat dieses Ansatzes ist, dass das Interface zum Grafik-Subsystem sowohl High-Level-Funktionen wie beforeLevelRender als auch Low-Level-Funktionen wie pushMatrix enthält um Duplikate der Business-Logik zu vermeiden.

Um die generelle Strategie zu zeigen, sind nachfolgend einige Code-Passagen angefügt. Zunächst sind Definition und Verwendung eines Interface zu OpenGL in der GameEngine-Klasse im Core-Modul zu hinterlegen:

package de.kaialtstaedt.fluki.core;

public class GameEngine {

private static GameEngine instance;

public static interface GLRenderer {
public void pushMatrix();
public void translate(float x, float y, float z);
}

private GlRenderer glRenderer;

public void setGLRenderer(GlRenderer glRenderer) {
this.glRenderer = glRenderer; }

public void gameLoop() {

glRenderer.pushMatrix();
glRenderer.translate(player.x(), player.y(), 0);
player.render(glRenderer);

}
}

Der GL-Renderer ist im PC-Modul mit der JOGL-Bibliothek implementiert:

package de.kaialtstaedt.fluki.pc;

include awt.glcanvas;
include jogl.GL.*;

public class FlukiPC {

class PCGLRenderer implements GLRenderer {

GL10 gl;

public void pushMatrix() {
gl.pushMatrix(); }

public void translate(float x, float y, float z) {
gl.GLTranslate(x,y,z); }
}

public static void main(String[] args) {

PCGLRenderer pcRenderer = new PCGLRenderer();
GameEngine.getInstance().setRenderer(new PCGLRenderer());


}

Die Android-Implementierung sieht fast identisch aus, nur das anstelle von JOGL die Khronos-OpenGL- Bibliothek des Android SDK verwendet wird.

Mit überschaubarem Aufwand kann man mit den Eclipse-Bordmitteln eine einfache und effiziente Gliederung eines Projekts in verschiedene Module realisieren. Der zusätzliche Aufwand, einen PC-Client zu bauen, lohnt sich, sobald man signifikante Anteile von Ablauflogik hat. Deren Debugging und das Erstellen der Regeln sind auf dem PC naturgemäß einfacher.

Kai Altstaedt
ist Diplom-Informatiker und arbeitet als System-Ingenieur bei Airbus. Er beschäftigt sich mit embedded-Anwendungen und modernen Bedienkonzepten.


Quelle:

[1] Kent Beck; Extreme Programming Explained; Pearson Education; 2004 (2. Aufl.)
(jul [1])


URL dieses Artikels:
https://www.heise.de/-1861103

Links in diesem Artikel:
[1] mailto:jul@heise.de