Entdecken Sie eBooks
Kategorien
Entdecken Sie Hörbücher
Kategorien
Entdecken Sie Zeitschriften
Kategorien
Entdecken Sie Dokumente
Kategorien
Entwicklung einer auf Scala basierenden Android-Applikation fr das Hochschul-Informationssystem Spirit der Fakultt Informatik an der Fachhochschule Schmalkalden
Zur Erlangung des akademischen Grades eines Master of Science - Media Processing and Interactive Services -
Fakultt Informatik Referent: Prof. Dr. Oliver Braun Korreferent: Dipl.-Mathematiker Michael Otto eingereicht von: Sebastian Stallenberger Matr.-Nr. 230534 Neuhauser Str. 5 97616 Bad Neustadt Schmalkalden, den 20.09.2011
Danksagung Zu Beginn bedanke ich mich bei meinen Eltern, die mich immer bei all meinen Vorhaben untersttzt haben und sehr geduldig mit mir und meinem langen Studium waren. Ich danke Prof. Dr. Oliver Braun, der mich in der Zeit, in der diese Arbeit entstanden ist, sehr gut betreut hat und mir zu jeder Zeit als Ansprechpartner zur Verfgung stand. Weiterhin danke ich dem Team von Spirit, insbesondere Benjamin Ldicke, Ronny Schleicher und Florian Schuhmann fr die exzellente Zusammenarbeit und die vielen interessanten und teilweise hitzigen Diskussionen. Mein weiterer Dank fr Untersttzung geht an Tobias Gieler und Vanessa Brech. Abschlieend danke ich auch den Autoren diverser Software-Projekte, mit denen ich per E-Mail Kontakt hatte und die mich bei Problemen beratend untersttzten. Dazu zhlen: Jan Berkel (android-plugin), Coda Hale (Jerkson) und Carsten Elton Srensen (Treeshaker).
Inhaltsverzeichnis
1 Einleitung 1.1 Spirit . . . . . . . . . . . . 1.2 Motivation . . . . . . . . . 1.3 Ziel der Arbeit . . . . . . 1.4 Vorgehensweise der Arbeit 1.5 Versionsfestlegung . . . . . 2 Vorstellung der Technologien 2.1 Android . . . . . . . . . . 2.1.1 Geschichte . . . . . 2.1.2 Architektur . . . . 2.1.3 Market . . . . . . . 2.1.4 Android SDK . . . 2.2 Scala . . . . . . . . . . . . 3 Entwicklungsumgebungen 3.1 Vorbereitungen . . . . 3.2 Konsole . . . . . . . . 3.3 Eclipse . . . . . . . . . 3.4 IntelliJ . . . . . . . . . 3.5 Wahl fr die Arbeit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 2 2 3 3
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
. . . . . .
5 . 5 . 6 . 6 . 9 . 9 . 13 14 14 15 16 17 17
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
. . . . .
4 Anforderungsanalyse 19 4.1 Erforderliche Features . . . . . . . . . . . . . . . . . . . . . . . . . . 19 4.2 Optionale Features . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 5 Entwurf 5.1 MainActivity . . . . . . . . . . . . . . 5.2 NewsMulti . . . . . . . . . . . . . . . . 5.3 NewsSingle . . . . . . . . . . . . . . . 5.4 NewsCreate . . . . . . . . . . . . . . . 5.5 TimeTable . . . . . . . . . . . . . . . . 5.6 TimeSlot . . . . . . . . . . . . . . . . . 5.7 Settings . . . . . . . . . . . . . . . . . 5.8 Entwrfe von Hintergrundprozessen . . 5.8.1 News oder TimeTable abrufen . 5.8.2 News oder Kommentar erstellen 21 22 23 24 25 26 27 27 29 29 31
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
. . . . . . . . . .
Sebastian Stallenberger
III
Inhaltsverzeichnis
6 Prototypische Implementierung 6.1 Struktur der Applikation . . . . . . . . 6.2 AsyncTasks . . . . . . . . . . . . . . . 6.3 Activity MainActivity . . . . . . . . . 6.3.1 onCreate und onResume . . . . 6.3.2 Passphrase-Eingabe fr die Beta 6.3.3 Hauptmen . . . . . . . . . . . 6.3.4 Options menu . . . . . . . . . . 6.4 Activity NewsMulti . . . . . . . . . . . 6.4.1 Konstruktion der News-Liste . . 6.4.2 Aufbau einer einzelnen News . . 6.4.3 onActivityResult . . . . . . . . 6.5 Activity NewsSingle . . . . . . . . . . . 6.6 Activity NewsCreate . . . . . . . . . . 6.7 Activity Settings . . . . . . . . . . . . 6.8 Activity Info . . . . . . . . . . . . . . . 6.9 Toilers . . . . . . . . . . . . . . . . . . 6.9.1 JsonProcessor . . . . . . . . . . 6.9.2 SpiritHttpClient . . . . . . . . . 6.9.3 SpiritConnect . . . . . . . . . . 6.9.4 ViewBuilder . . . . . . . . . . . 6.9.5 SpiritHelpers . . . . . . . . . . 7 Distribution 8 Fazit Literaturverzeichnis
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
. . . . . . . . . . . . . . . . . . . . .
A Anhang A.1 Tutorials fr die Einrichtung von Entwicklungsumgebungen A.1.1 Ergnzungen zur Einrichtung von SBT . . . . . . . A.1.2 Einrichtung von Umgebungsvariablen . . . . . . . . A.1.3 Konsole . . . . . . . . . . . . . . . . . . . . . . . . A.1.4 Eclipse . . . . . . . . . . . . . . . . . . . . . . . . . A.1.5 IntelliJ . . . . . . . . . . . . . . . . . . . . . . . . . A.1.6 Manuelles Signieren anstatt prepare-market . . . . A.2 Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . A.3 Icons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Eidesstattliche Erklrung
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
. . . . . . . . .
Sebastian Stallenberger
IV
1 Einleitung
Smartphones sind aus der heutigen Welt nicht mehr wegzudenken. Nachdem Apple mit dem iPhone gezeigt hatte, dass Smartphones durchaus bei der breiten Masse Anklang nden, strmten schnell neue Konkurrenten auf den Markt. Android ist hierbei das Betriebssystem fr Smartphones mit der aktuell grten Verbreitung1 . Eine bislang kaum beachtete Nische ist die Programmierung von Android mit der objektfunktionalen Sprache Scala. Diese Arbeit soll einen berblick ber die Entwicklung von Android-Applikationen mit Scala bieten. Betrachtet wird die Entwicklung einer Android-Applikation fr die Hochschul-Informationsplattform Spirit 2 .
1.1 Spirit
Spirit ist ein junges Projekt der Fakultt Informatik an der Fachhochschule Schmalkalden. Unter der Leitung von Prof. Dr. Oliver Braun hat man es sich zum Ziel gemacht, den Informationsuss zwischen der Fakultt, den Lehrenden und den Studenten zu optimieren. Um dieses Vorhaben in die Tat umzusetzen, wurde das Projekt in Teilbereiche mit folgenden Aufgaben unterteilt: Stundenplne mssen vor Beginn des Semesters erzeugt, validiert und verteilt werden. Ein reibungsloser bergang vom aktuellen Stundenplan-System auf die Spirit-Variante muss mglich sein. Dies erfordert zustzlich, dass die bisherigen Stundenplan-Daten in das neue System migriert werden knnen. Auch die verteilte Berechnung von Stundenplnen wird in einer Arbeit untersucht. Ein wichtiger Punkt des Projekts Spirit sind Service-Anwendungen fr Lehrende und Studierende. So ist es zum Einen ein Ziel, fr die Lehrenden ein intuitives Management ihrer Lehrveranstaltungen und Neuigkeiten zu schaen. Zum Anderen will man den Studenten ermglichen, schneller an Neuigkeiten heranzukommen und sich individuelle Stundenplne zusammenzustellen. Somit soll die Planung ihres Studiums deutlich vereinfacht werden. All dies wird durch die Entwicklung und Betreuung zweier unabhngiger Web-Plattformen realisiert. StudWeb ist hierbei das Angebot fr die Studenten, EmployeeWeb das fr die Lehrenden. Geplant sind neben den ber Browser erreichbaren Angeboten auch Applikationen fr mobile Endgerte. Hierbei werden die Plattformen (Google) Android, Microsoft Windows Phone 7 und Apple iOS bercksichtigt, um ein mglichst groes Areal des Smartphone-Marktes abzudecken.
1 2
Laut [IDC11] lag der Marktanteil von Android am 9. Juni 2011 bei 38,9%. Vgl. http://spirit.fh-schmalkalden.de
Sebastian Stallenberger
1. Einleitung
Als Fundament zur Realisierung des Projekts Spirit gilt die funktionale Programmierung. So wird versucht, wann immer es mglich ist, groe Teile des Projekts in funktionalen Sprachen umzusetzen. Statt Java wird z.B. Scala oder Haskell eingesetzt und statt C# z.B. F#. Diese Arbeit wird sich mit der Entwicklung der Android-Applikation SpiritMobile in der funktionalen Programmiersprache Scala auseinandersetzen. SpiritMobile soll den einfachen Zugri auf die diversen Services von Spirit ermglichen. Hierbei wird sich der Autor mit den dabei auftretenden Problemen und deren Lsungen befassen. Besonders der Implementierung soll dabei viel Aufmerksamkeit geschenkt werden.
1.2 Motivation
Die oziell von [Goo11c] fr die Entwicklung von Applikationen fr Android empfohlene und untersttzte Sprache ist Java3 . Warum sollte man also eine alternative Programmiersprache wie Scala einsetzen? Nach Meinung des Autors ist diese Frage vom wissenschaftlichen Standpunkt aus betrachtet falsch gestellt. So sollte man eher fragen: Warum sollte man eine alternative Programmiersprache wie Scala nicht dazu nutzen, Applikationen fr Android zu entwickeln? Grnde dafr gibt es auf den ersten Blick zumindest theoretisch nicht. Laut [Bra11, S.1] kombiniert Scala als Hybrid-Sprache die Features von objektorientierten und funktionalen Programmiersprachen. Die Programmierung sowohl mit Java als auch mit Scala liefert Java Bytecode, der auf dem Android Betriebssystem ausfhrbar ist. Zustzlich lsst sich auch jeglicher Java-Code direkt aus Scala heraus nutzen und umgekehrt[Bra11, S.2]. Dies macht Scala zu einer idealen alternativen Sprache fr die Android Programmierung. Im Kapitel 2.2 wird die Sprache Scala detaillierter betrachtet.
Vgl. http://www.java.com
Sebastian Stallenberger
1. Einleitung
1.5 Versionsfestlegung
Bei der Festlegung der Android-Version wurde auf die Erhebung von [Goo11g] zurckgegrien. Die folgenden Daten beziehen sich auf eine Periode von 14 Tagen, die am 1. August 2011 endete. Wie in der Tabelle 1.1 zu sehen, nutzen ber 80% aller Plattform API Level Android 1.5 3 Android 1.6 4 Android 2.1 7 Android 2.2 8 Android 2.3 - 2.3.2 9 Android 2.3.3 - 2.3.4 10 Android 3.0 11 Android 3.1 12 Android 3.2 13 Verteilung 1,3% 2,0% 15,2% 55,9% 0,6% 23,7% 0,4% 0,7% 0,2%
Sebastian Stallenberger
1. Einleitung
Android-Nutzer eine Version hher als oder gleich 2.2. Deshalb wurde die AndroidVersion, fr die in dieser Arbeit entwickelt wird, auf 2.2 festgelegt. Im folgenden Kapitel werden die Basis-Technologien Android und Scala nher vorgestellt.
Sebastian Stallenberger
2.1 Android
Laut [Goo11j] ist Android eine Software-Sammlung fr mobile Gerte, die ein Betriebssystem, Middleware und Schlssel-Applikationen umfasst. Das Android SDK stellt Werkzeuge und Schnittstellen bereit, die ntig sind, um mit Java fr die Android Plattform zu entwickeln. Auf das Android SDK wird im Kapitel 2.1.4 nher eingegangen. Als groer Vorteil wird bei Android oft die Oenheit genannt, mit dem es seinen Nutzern begegnet. Im Gegensatz zu Apple oder Microsoft lsst Google dem Nutzer zur Zeit einen relativ groen Freiraum und damit auch viel Kontrolle ber sein Gert. So kann ein Nutzer zum Beispiel apk-Pakete auf vielen Wegen auf sein Gert spielen und installieren. Laut [Goo11j] bringt Android unter anderem folgende Features mit sich: Applikations-Framework: Ermglicht die Wiederverwendung und den Ersatz von Komponenten. Integrierter Browser: Basiert auf der Open-Source WebKit Engine. Grak-Untersttzung: Android bringt eine angepasste 2D-Grak-Bibliothek mit sich. 3D-Graken basieren auf der OpenGL ES 1.0 Spezikation. Abhngig von der Hardware wird auch Hardwarebeschleunigung untersttzt. SQLite: Ermglicht das strukturierte Speichern von Daten. Medien-Untersttzung: Gebruchliche Datentypen fr Audio(MP3, AAC, AMR), Video(MPEG4, H.264) und Bilder(JPG, PNG, GIF) werden untersttzt. Abhngig von der Hardware ist mglich: GSM Telefonie Aufbau von Verbindungen ber Bluetooth, EDGE, 3G und WLAN Nutzung von Kameras, GPS, einem Kompass und Beschleunigungssensoren Android kann nicht nur auf Smartphones sondern zielgerichtet angepasst auf vielen Arten von Gerten eingesetzt werden. So ist geplant, Android auch z.B. in Autos, Spielekonsolen oder Set-Top-Boxen zum Einsatz zu bringen. Auf Millionen von Tablet-PCs, die derzeit den Markt erobern, luft es bereits. [BP10, S.19] Im Folgenden geht der Autor auf wichtige Punkte in Bezug auf Android ein und beginnt mit einem kurzen geschichtlichen berblick.
Sebastian Stallenberger
2.1.1 Geschichte
Ursprnglich wurde Android nicht von Google entwickelt. 2005 kaufte Google das kleine Unternehmen Android von Andy Rubin1 , der vor der Entwicklung von Android unter anderem schon bei Apple Inc. gearbeitet hatte. Im November 2007 verkndete Google, gemeinsam mit anderen Mitgliedern der Open Handset Alliance2 ein Betriebssystem fr Mobiltelefone zu entwickeln. Oziell verfgbar ist Android seit Oktober 2008 und seine Nutzerzahlen wachsen seitdem ungebremst. [Wik11]
2.1.2 Architektur
Abbildung 2.1 gibt einen berblick ber den Aufbau eines aktuellen Android-Systems. Die wichtigsten Bereiche werden in diesem Kapitel erlutert. Soweit nicht anders angegeben, gilt als Quelle fr dieses Kapitel [Goo11j].
Abbildung 2.1: Die Android Achitektur in Anlehnung an [BP10, Abb. 2-1] und [Goo11j]
1 2
Sebastian Stallenberger
Laut [BP10] ist die Basis von Android [...] ein Linux-Kernel. Dieser enthlt erforderliche Gertetreiber und besitzt einige Optimierungen, vor allem in Bezug auf Energieverbrauch und Speichermanagement. Derzeit wird ein Kernel der Version 2.6 eingesetzt. Dieser wirkt auch als eine Abstraktionsschicht zwischen der Hardware und der Android-Software-Sammlung. Bibliotheken Die Android Bibliotheken lassen sich einteilen in die Standard-Bibliotheken und die Android-Laufzeitumgebung. Die Standard-Bibliotheken sind in C/C++ geschrieben. Sie ermglichen die von Android-Anwendungen geforderten Funktionen wie z.B. Datenbankzugri, 2D- und 3D-Graken, Webzugri und Multimediaverwaltung. Zu diesen Bibliotheken zhlen zum Beispiel: LibWebCore: Liefert eine auf WebKit basierende Webbrowser-Umgebung. WebKit wird unter anderem auch in Google Chrome oder in iOS eingesetzt. SQLite: Stellt ein Datenbanksystem bereit, das sich im mobilen Bereich bewhrt hat. Media Framework: Basierend auf dem quelloenen Multimedia-Subsystem OpenCORE ist das Android Media Framework fr die Darstellung und Verarbeitung der verbreitetsten Multimedia-Formate zustndig. Die Grakdarstellungen bernehmen die Bibliotheken SGL (2D) und OpenGL 1.0 (3D). Zur Android-Laufzeitumgebung zhlen die Android Laufzeitbibliotheken und die Dalvik Virtual Machine (DVM). Fr jede unter Android ausgefhrte Anwendung wird ein eigener System-Prozess gestartet (Sandbox-Prinzip) und in diesem eine DVM. Die dadurch entstehenden Vorteile in Sachen Sicherheit und Verfgbarkeit relativieren den erhhten Ressourcen-Verbrauch. Die DVM basiert laut [BP10] auf der quelloenen JVM3 Apache Harmony, wurde aber in Aufbau und Funktionsumfang an die Anforderungen mobiler Endgerte angepasst. Im Gegensatz zur klassischen JVM nutzt die DVM Register moderner Prozessoren. Wie in Abbildung
Abbildung 2.2: Der Weg vom Java- zum Dex-Bytecode in Anlehnung an [BP10] Abb. 2-2
3
Sebastian Stallenberger
2.2 zu sehen, werden die durch den javac erzeugten *.class-Dateien (Java-Bytecode) mit dem dx-Werkzeug in *.dex-Dateien (Dex-Bytecode) umgewandelt. Diese werden anschlieend in einem *.apk-Paket zu einer Anwendung zusammengefasst. Framework Der Android-Framework stellt einige Systemklassen zur Verfgung, die den Zugri auf Hardwarekomponenten aus der Anwendung heraus erlauben. [...] Viele dieser Klassen werden als Manager bezeichnet[BP10]. Diese Klassen sind in Java geschrieben. Wichtige Manager sind laut [Goo11j] zum Beispiel: View System: Wird genutzt, um die grasche Oberche einer Applikation aufzubauen. Diese kann z.B. Listen, Grids, Buttons oder einen einbettbaren Webbrowser enthalten. Auf weitere grasche Elemente sowie die verschiedenen Layouts wird im Kapitel 6 eingegangen. Resource Manager: Stellt den Zugri auf Ressourcen bereit, die nicht im Code deniert sind. Dies knnen z.B. Strings, Graken und Layout Dateien sein. Notication Manager: Ermglicht den Applikationen, selbst denierte Nachrichten in der Status-Bar anzuzeigen. Activity Manager: berwacht und kontrolliert den Lifecircle von Applikationen. Neben den gerade vorgestellten Manager-Klassen knnen alle Android-Anwendungen vier Basis-Komponenten nutzen: Activity: Dienen Activities augenscheinlich zur Darstellung und Verwaltung von Oberchen, haben sie auch Aufgaben, die ber die reine Darstellung hinausgehen. Beispiele fr den Aufbau und die Verwendung von Activities nden sich im Kapitel 6. Service: Operationen, die keine Oberche bentigen und im Hintergrund ablaufen knnen/sollen, knnen in sogenannten Services realisiert werden. Content Provider: Sie dienen zur Abstraktion der unter einer Applikation liegenden Persistenzschicht. Darber knnen Daten gespeichert und geladen werden. Broadcast Reciever: Die sogenannten Broadcast Reciever sind verantwortlich fr die Kommunikation mit dem System. Sie empfangen Systemnachrichten wie z.B. ber Strungen der Internetverbindung oder einen niedrigen Akkustand und reagieren darauf. Anwendungsebene Zur Anwendungsebene zhlen sowohl Anwendungen, die von Google mitgeliefert werden (Home-Screen, Telefonie, Browser, usw.), als auch selbstgeschriebene Anwendungen oder Anwendungen von Drittanbietern aus dem Market, der im folgenden Abschnitt genauer vorgestellt wird.
Sebastian Stallenberger
2.1.3 Market
Der Android Market4 ist eine Plattform, ber die Entwickler ihre Applikationen vertreiben und Nutzer Applikationen kaufen und/oder downloaden knnen. Im Kapitel 7 wird dieser detailliert vorgestellt, es werden Alternativen aufgezeigt und ebenso die Schritte zur Verentlichung einer Applikation erlutert. Um fr den Market oder andere Vertriebsplattformen Anwendungen zu entwickeln, stellt Google das Android SDK bereit, welches im folgenden Abschnitt vorgestellt wird.
Vgl. https://market.android.com
Sebastian Stallenberger
Die SDK Tools beinhalten diverse Werkzeuge, die das Debuggen und Testen einer Anwendung ermglichen. Man ndet diese im Unterverzeichnis tools des SDK. Folgend werden einige dieser Werkzeuge kurz vorgestellt. Soweit nicht anders angegeben, gilt als Quelle fr diesen Abschnitt [Goo11i]. android: Ermglicht die Verwaltung der Virtual-Devices (Erzeugen, Lschen, Betrachten), der Android-Projekte (Erzeugen, Aktualisieren) und des SDK (Hinzufgen von Platforms, Add-ons und Dokumentation). ddms (Dalvik Debug Monitor Server): Ist ein Debugging-Tool. Es beinhaltet die Mglichkeit, den Ressourcen-Verbrauch einzelner Prozesse einzusehen. die Speicher-Belegung von Objekten zu tracken. Dateien mit dem virtuellen oder angeschlossenen Device auszutauschen. Informationen ber laufende Threads zu bekommen. Method-Proling oder den LogCat zu nutzen. Eigenschaften des Emulators zu verndern (z.B. den Verbindungsstatus oder die Verbindungsgeschwindigkeit). eingehende Anrufe oder SMS zu simulieren. die Location des Device zu setzen. Draw 9-patch: Ein WYSIWYG-Editor, um aus normalen Graken NinePatchGraken zu erstellen. NinePatch-Graken zeichnen sich dadurch aus, dass eine Grennderung verlustfreier durchgefhrt werden kann, unter der Voraussetzung, man nutzt eine geeignete Grak. Abbildung 2.4 zeigt die Aufteilung einer NinePatchGrak. Die Flche A wird bei einer Grennderung horizontal und vertikal gestreckt. Fr diesen Bereich ist eine einfarbige Flche oder ein einfacher Verlauf zu empfehlen. Die Flchen B werden nur vertikal und die Flchen C nur horizontal gestreckt. Die Eck-Flchen D bleiben ungestreckt. Ideal ist diese Technik z.B. fr Buttons.
Sebastian Stallenberger
emulator (Android Emulator): Ein auf QEMU5 basierender Emulator, auf dem ein Android-System luft und der zum Testen einer Applikation genutzt werden kann. Alle in dieser Arbeit enthaltenen Screenshots von SpiritMobile wurden mit diesem Emulator erstellt. hierarchyviewer (Hierarchy Viewer): Werkzeug mit grascher Oberche zum Debuggen und Optimieren von GUIs 6 in Android Applikationen. Die Abbildung 2.5 zeigt den Hierarchy Viewer am Beispiel des Haupmens von SpiritMobile.
Abbildung 2.5: Der Hierarchy Viewer Monkey & monkeyrunner: Monkey feuert im Emulator pseudo-zufllige UserEvents ab wie z.B. Klicks, Berhrungen, Gesten oder System-Level-Events. Monkey kann fr Stress-Tests von Applikationen eingesetzt werden. Ein Beispiel fr einen Aufruf von Monkey ist: adb shell monkey -p org.unsane.spirit -v 500 Dadurch wird die Applikation org.unsane.spirit gestartet und es werden 500 pseudozufllige Aktionen ausgelst. Monkeyrunner stellt eine API bereit, ber die ein Android Device ferngesteuert werden kann. zipalign: Nachdem eine *.apk-Datei signiert wurde, sollte zipalign darauf angewandt werden, da es an dieser weitere Optimierungen durchfhrt. Weitere enthaltene Werkzeuge, die an dieser Stelle nicht vorgestellt werden, sind: dmtracedump, hprof-conv, mksdcard, ProGuard, sqlite3, layoutopt und traceview. SDK Platform-tools Die SDK Platform-tools enthalten ebenfalls Werkzeuge, mit denen eine AndroidApplikation entwickelt und debuggt werden kann. Sie sind im Unterverzeichnis platform-tools des SDK verfgbar. Einige dieser Werkzeuge (aidl, aapt, dexdump und dx) werden normalerweise nicht direkt aufgerufen. Der Aufruf erfolgt meist
5 6
Sebastian Stallenberger
ber die Android-build-tools oder Android-Development-Tools (ADT). Im Folgenden wird eines dieser Werkzeuge - die Android-Debug-Bridge (adb) - kurz vorgestellt. Soweit nicht anders angegeben, gilt [Goo11b] als Quelle fr dieses Unterkapitel. Die Android-Debug-Bridge ist ein Kommandozeilen-Tool. Sie ermglicht es, mit einem Emulator oder einem verbundenen Device zu kommunizieren. Sie besteht dabei aus drei Komponenten: Dem Client, der auf dem Entwicklungs-Rechner luft. Er kann aus Eclipse heraus ber das ADT-Plugin oder aus dem DDMS heraus genutzt werden. Ebenso ist ein direkter Aufruf aus der Konsole ber den Befehl adb mglich. Dem Daemon, der als Hintergrundprozess in einem Emulator oder einem angeschlossenen Gert luft. Dem Server, der im Hintergrund auf dem Entwicklungs-Rechner luft. Dieser regelt die Kommunikation zwischen den Clients und dem Daemon. Die ADB kann zum Beispiel dazu genutzt werden, eine bersicht ber die aktuell verfgbaren Devices zu bekommen (adb devices), Befehle an spezielle Devices zu senden (adb -s <IdDesDevice> <Befehl>), Applikationen zu installieren (adb install <PfadZumAPK>) und Dateien auf ein oder von einem Device zu kopieren (adb pull <entfernterPfad> <lokalerPfad> bzw. adb push <lokalerPfad> < entfernterPfad>). Ebenso knnen Befehle direkt auf dem Device ber eine RemoteShell ausgefhrt werden. Eine vollstndige Auistung aller verfgbaren Befehle ist auf [Goo11b] unter dem Punkt Listing of adb Commands verfgbar. Android platforms Auf der Android Developers-Website7 sind mehrere Versionen der Android Platform verfgbar (derzeit Version 1.1 - 3.2). ber den AVD Manager knnen diese in beliebiger Anzahl installiert werden. Hierbei besitzt jede Platform ihre eigene Android Bibliothek, ein System-Image, Beispiel-Codes und Emulator-Skins. Derzeit ist es von Google so vorgesehen, dass die Android Platforms der Version 2.x fr Smartphones und die der Version 3.x fr Tablets genutzt werden. Diese Spaltung soll mit Version 4.x aufgehoben werden, die laut [Tom11] im Oktober 2011 auf den Markt kommen soll. Diese Version soll dann einheitlich sowohl auf Smartphones als auch auf Tablets und anderen Gerten eingesetzt werden knnen. USB Driver for Windows Will man unter Windows seine Applikation auf einem externen Device ber USB debuggen, muss man den im SDK enthaltenen USB-Treiber installieren. Unter Mac OS X oder Linux entfllt dieser Schritt.
Vgl. http://developer.android.com/sdk/adding-components.html
Sebastian Stallenberger
Diese Verzeichnisse enthalten diverse Code- und Applikations-Beispiele sowie die aktuelle Dokumentation fr jede Android-Development-Platform. Nachdem nun ein berblick ber Android gegeben wurde, geht der Autor im folgenden Kapitel 2.2 auf die objektfunktionale Programmiersprache Scala ein.
2.2 Scala
Wie schon mehrfach in dieser Arbeit angesprochen, ist Scala eine objektfunktionale Programmiersprache. Was dies genau heit und welche Features Scala mit sich bringt, wird in diesem Kapitel kurz erlutert. Soweit nicht anders angegeben, ist die Quelle fr dieses Kapitel [lan08]. Der Name Scala leitet sich von scalable language ab. Laut [OSV08, S. 3] wurde die Sprache so genannt, weil sie mit den Anforderungen der Nutzer wchst. Von kleinen Scripten bis hin zu groen Programmen ist alles mglich. In [Bra11, S. 1] ndet sich ein kurzer geschichtlicher berblick: Die Entwicklung begann 2001 an der cole polytechnique fdrale de Lausanne (EPFL) in der Schweiz von einem Team um Professor Martin Odersky. Das erste Release wurde bereits 2003 verentlicht. 2006 folgte die Version 2.0. Inzwischen ist Scala bei Version 2.98 angelangt. Nach [lan08] ist Scala eine Multi-Paradigmen Sprache, die entworfen wurde, um gebruchliche Entwurfsmuster auf eine mglichst knappe, elegante und typsichere Weise umzusetzen. Die Objektorientierung wurde hierbei sehr streng umgesetzt. So sind alle Werte Objekte. Das betrit auch numerische Werte oder Funktionen. Beschrieben werden diese Objekte in Klassen oder sogenannten Traits. Traits sind den Interfaces in Java hnlich. Anders als in Java-Interfaces knnen in Traits Methoden implementiert werden. Dadurch wird eine Realisierung von Rich-Interfaces ermglicht. Im Gegensatz zu Klassen drfen Traits allerdings keine Konstruktor-Parameter haben. [Bra11, S. 2] Laut [Bra11, S. 2] nennt Odersky Scala nach einigen Diskussionen im Web eine postfunktionale Sprache. So knnen Funktionen sowohl Argumente als auch Ergebnisse von anderen Funktionen sein. Ebenso ist die Verschachtelung von Funktionen mglich. Weiterhin untersttzt Scala Pattern-Matching und Currysierung. Die statische Typisierung sieht [OSV08, S. 12-19] als einen groen Vorteil von Scala gegenber Sprachen mit dynamischer Typisierung. So gibt es ein System von ineinander verschachtelten Typen, es knnen aber auch Typen mit Generics parametrisiert werden. Weiterhin machen es Intersections mglich, Typen zu kombinieren. Abstrakte Typen ermglichen es, Typ-Details zu verstecken. Tiefer in die Theorie von Scala einzusteigen, wrde den Umfang dieser Arbeit sprengen. Fr detaillierte Informationen ber Scala empehlt der Autor [Bra11], [OSV08] und [lan08] zur Lektre. Nachdem im aktuellen Kapitel die beiden Technologien Android und Scala kurz vorgestellt wurden, stellt der Autor im nchsten Kapitel Entwicklungsumgebungen fr Android und Scala vor.
8
Vgl. http://www.scala-lang.org/downloads
Sebastian Stallenberger
3 Entwicklungsumgebungen
Die Entwicklung einer Software mit einer Kombination von Technologien, die vom Standard abweicht, stellt einen gleich zu Beginn vor das Problem der Auswahl einer geeigneten Entwicklungsumgebung. Netbeans 1 entfllt als Mglichkeit, da es derzeit keine stabile2 Scala-Untersttzung bietet. Die einfachste Methode ist der Weg ber die Konsole. Hierbei entfllt zwar aller Ballast, aber auch jegliche Hilfestellung, die moderne Entwicklungsumgebungen mit sich bringen. Als Editor kann jeder beliebige Texteditor genutzt werden, z.B. vim 3 (alle Plattformen) oder Notepad++4 (Windows). Der dem Entwickler gebotene Komfort hngt vom genutzten Editor ab. Mit Eclipse 5 und IntelliJ 6 bieten sich zwei Entwicklungsumgebungen an, die sich besonders in der Java-Entwicklung einen Namen gemacht haben. Eclipse wird von Google fr die Android-Programmierung empfohlen und gilt somit als ozielle AndroidEntwicklungsumgebung. Die Untersttzung fr Android ist dementsprechend umfangreich. Von einem professionellen GUI-Editor ber Code-Completion bis hin zur Visualisierung des AndroidManifest bietet Eclipse alles, was das Herz eines AndroidEntwicklers begehrt. Fr Scala existieren wenige Eclipse-Plug-ins. Das bekannteste ist Scala IDE for Eclipse 7 . IntelliJ galt lange als beste Entwicklungsumgebung fr die Scala-Programmierung. Das Scala-Plug-in ist vorinstalliert und auch fr SBT (Simple Build Tool) und Android nden sich entsprechende Plug-ins, die unabhngig voneinander betrachtet gute Dienste leisten. Wie sich die drei Methoden jeweils in der Praxis in Bezug auf Android und Scala verhalten, wird in den folgenden Abschnitten kurz dargestellt. Ausfhrlichere Anleitungen benden sich im Anhang A.1.
3.1 Vorbereitungen
Voraussetzung fr alle in diesem Kapitel angesprochenen Mglichkeiten sind das JDK8 , Scala SDK9 und Android SDK10 . Die Konguration erfolgte unter Windows 7.
http://www.netbeans.org Vgl. http://www.scala-lang.org/node/353 Not perfect, but fairly stable 3 http://www.vim.org 4 http://notepad-plus-plus.org 5 http://www.eclipse.org 6 http://www.jetbrains.com 7 Vgl. http://www.scala-ide.org 8 http://www.oracle.com/technetwork/java/javase/downloads/index.html 9 http://http://www.scala-lang.org/downloads 10 http://developer.android.com/sdk/index.html
2 1
Sebastian Stallenberger
3. Entwicklungsumgebungen
Unter Linux oder MacOs knnen die Ergebnisse abweichen. Als Root-Pfad fr alle SDKs gilt C:\scalandroid. Nach dem Entpacken aller SDKs im oben genannten Verzeichnis sollte man das Android SDK updaten und kongurieren. Dies kann ber den im Android SDK enthaltenen SDK Manager erledigt werden. Nach den Updates erstellt man ein Device SpiritDevice11 mit Android 2.2 - API-Level 8 12 . Damit ist die Einrichtung der SDKs abgeschlossen und man kann sich der Auswahl eines Build Tools zuwenden. Ein bei Scala-Entwicklern sehr beliebtes Tool ist hierbei das SBT. Auch wird es in dieser Arbeit als Build-Tool eingesetzt werden, da es sich unabhngig von der Entwicklungsumgebung in Kombination mit Android untersttzenden Plug-Ins als sehr gut funktionierendes Werkzeug bewiesen hat. SBT kann man auf der ProjektHomepage13 herunterladen. Abgelegt wurde das SBT fr unser Projekt auch im Root-Ordner. Anschlieend wird das SBT nach der Anleitung auf der Projekt-Homepage14 eingerichtet. Der detailliertere Einsatz von SBT wird im Kapitel A.1.3 erlutert. Fr das Scala SDK, Java SDK und SBT sollten gltige Umgebungsvariablen angelegt werden, falls noch nicht geschehen. Details dazu nden sich im Kapitel A.1.2.
3.2 Konsole
Um mit dem SBT Scala-Android-Projekte zu erstellen, ist ein SBT-Plugin von Nten. Vorgefertigt ndet man ein solches in GIT-Hub15 . Im Ordner android-plugin\ script ndet man ein Shellscript, das man unter Windows natrlich nicht ohne weiteres ausfhren kann. Nachdem vorherige Versuche mit MinGW16 und Cygwin17 fehlschlugen, fhrte eine Abnderung des Shellscripts zum Erfolg. Die nderung kann in Kapitel A.1.3 nachgelesen werden. Damit konnte ohne Probleme ein vollstndiges Android-Projekt18 erstellt werden. Das erzeugte Android-Projekt weist die in Listing 3.1 gezeigte Struktur auf. Die Kongurations-Daten fr SBT benden sich in der Datei project\build\SpiritMobile.scala. Im nchsten Schritt kann der Android-Emulator per emulator -avd SpiritDevice gestartet werden. Die folgende Reihenfolge sollte bei der ersten Verwendung von SBT eingehalten werden. sbt update - Ld die bentigten Abhngigkeiten herunter. Muss nur einmalig bei der ersten Verwendung oder bei nderungen in der SBT-Konguration ausgefhrt werden.
11
Der Name kann hier frei gewhlt werden. In dieser Arbeit wird allerdings immer auf SpiritDevice verwiesen. 12 Warum genau diese Version, wurde im Kapitel 1.5 schon erlutert. 13 http://code.google.com/p/simple-build-tool/downloads/list 14 Vgl. http://code.google.com/p/simple-build-tool/wiki/Setup 15 Im Root-Ordner git clone git://github.com/jberkel/android-plugin.git ausfhren 16 http://sourceforge.net/projects/mingw/ 17 Vgl. http://www.horstmann.com/articles/cygwin-tips.html 18 Vgl. http://zegoggl.es/2009/12/building-android-apps-in-scala-with-sbt.html
Sebastian Stallenberger
3. Entwicklungsumgebungen
sbt package-debug - fhrt den Build-Vorgang durch und erzeugt ein *.apkPaket. sbt reinstall-emulator - Installiert das Paket in den Emulator. Listing 3.1: Die Verzeichnisstruktur des mit android-plugin erzeugten Projekts. | - - project | | - - build | | -- SpiritMobile . scala | | - - build . properties | -- plugins | -- Plugins . scala | - - src | | - - main | | | - - AndroidManifest . xml | | | - - assets | | | - - java | | | - - res | | | | - - drawable | | | | - - layout | | | | - - values | | | -- xml | | -- scala | | -- Activity . scala | -- test | -- scala | -- Specs . scala -- tests Hat man vorher nicht die Umgebungsvariable ANDROID_SDK_HOME gesetzt, teilt einem SBT das nun mit. Die apk Pakete wurden somit erzeugt, zwischengespeichert und anschlieend auf dem laufenden Emulator installiert. Dort kann man in der Programmauswahl die Anwendung SpiritMobile whlen und ausfhren. Ein einfaches hello, world wird ausgegeben. Das bestehende Projekt kann nun weiterentwickelt werden. Schritte zur Distribution der Anwendung werden im Kapitel 7 behandelt.
3.3 Eclipse
In diesem Kapitel wird nur eine funktionierende Methode zur Einrichtung von Eclipse vorgestellt. Andere missglckte Versuche sind im Anhang in Kapitel A.1.4 dokumentiert. Die Quellen fr dieses Kapitel sind [S11b] und [S11a]. Treeshaker nennt sich ein Projekt, das durch Einfgen eines Build-Schritts die Nutzung von Scala auf Android ermglicht. In diesem Build-Schritt werden die erzeugten Java und Scala *.class-Dateien auf ihre Abhngigkeit die Scala Bibliothek betreend untersucht. Diese Abhngigkeiten werden dann in das bin-Verzeichnis exportiert und von den Android-Build-Tools in das erzeugte *.apk-Paket mit aufgenommen.
Sebastian Stallenberger
3. Entwicklungsumgebungen
Im Folgenden werden die ntigen Schritte zur Einrichtung des Treeshaker-Plugins erlutert19 . Voraussetzung fr Treeshaker sind eine funktionierende Android SDK und Eclipse Installationen. Anschlieend installiert man die Scala IDE 20 in Eclipse. Das zweite zu installierende Plugin ist Treeshaker21 selbst. Nun kann ein neues Android Projekt erzeugt werden. Dabei ist darauf zu achten, eine Activity namens Main erzeugen zu lassen. Im nchsten Schritt wird das Verzeichnis src in _src umbenannt und die Scala- und Treeshaker-Nature22 zum Projekt hinzugefgt. In den Projekteigenschaften sollte jetzt die Build-Reihenfolge auf ihre Korrektheit berprft werden: Android Resource Manager Android Pre Compiler Scala Builder Treeshaker Android Package Builder Nachdem die Datei Main.java durch eine Main.scala ersetzt wurde, kann mit dem Programmieren in Scala begonnen werden. Anmerkung: Kurz vor Fertigstellung dieser Arbeit entdeckte der Autor eine weitere Methode, Scala, Android und Eclipse zusammenzubringen. Diese kann im eSCALAtion blog23 eingesehen werden, wurde aber nicht vom Autor getestet.
3.4 IntelliJ
Neben Eclipse bietet sich mit IntelliJ eine weitere Entwicklungsumgebung an, die sowohl Android- als auch Scala-Programmierung untersttzt. Auch hier bestehen wieder diverse Mglichkeiten, die Umgebung fr die Entwicklung von AndroidApplikationen mit Scala einzurichten. Der Autor geht allerdings nur auf eine funktionierende Methode nher ein. Die Methode basiert auf dem mit dem android-plugin24 erstellten Projekt aus dem Kapitel 3.2. Nachdem das Projekt erstellt wurde, mssen in IntelliJ einige Anpassungen vorgenommen werden. Die Schritt-fr-Schritt-Anleitung und Lsungen fr eventuell auftretende Probleme sind im Anhang in Kapitel A.1.5 zu nden.
Basierend auf den am Anfang des Kapitels genannten Quellen. Es ist hierbei wichtig, nicht Scala 2.9 zu nutzen, da dies noch Probleme mit den Android-BuildTools auslst. Fr aktuelle Informationen bitte die Quelle betrachten. 21 Eclipse-Update-URL: http://treeshaker.googlecode.com/svn/trunk/update_site/ 22 ber das Kontextmen des Projekts. 23 Vgl. https://dgronau.wordpress.com/2011/08/20/scala-fur-android-mit-eclipse-leicht-gemacht/ 24 https://github.com/jberkel/android-plugin
Sebastian Stallenberger
3. Entwicklungsumgebungen
traten Fehler auf, die auf die Scala IDE zurckzufhren waren. Auch nach EmailKontakt mit Carsten Elton Srensen, dem Entwickler von Treeshaker, konnten diese damals nicht gelst werden. Mit der neuen, in Installation und Konguration sehr vereinfachten Treeshaker-Methode, die jetzt im August 2011 ein weiteres Mal getestet wurde, funktioniert zumindest das Demo-Projekt reibungslos. Das fortgeschrittene SpiritMobile-Projekt konnte aus Zeitgrnden leider nicht mehr in Eclipse zum Laufen gebracht werden. Im Mrz hat sich der Autor aus den oben genannten Grnden fr IntelliJ entschieden. Es bot zum Zeitpunkt des Starts der Implementation den reibungslosesten Workow. Gelegentlich traten allerdings auch hier Probleme auf, die man alle aber mehr oder weniger gut umgehen kann. Es sei nochmal ausdrcklich gesagt, dass IntelliJ nicht die perfekte Wahl darstellt. Wer mag, kann auch einfach die Konsole und SBT nutzen, da IntelliJ im Fall des vorgestellten Projekts nichts weiter macht, als SBT fernzusteuern und eine angenehme Programmierumgebung bereitzustellen. Ein groer Vorteil der Nutzung von SBT (egal ob ber IntelliJ oder Konsole) ist, dass Proguard25 immer mit ausgefhrt wird. Proguard reduziert den Code auf die ntigen Elemente und optimiert ihn, indem es z.B. ungenutzte Klassen und Methoden aus dem Code entfernt. Nachdem in diesem Kapitel eine geeignete Entwicklungsumgebung fr SpiritMobile gesucht wurde, werden im folgenden Kapitel die Anforderungen an die Anwendung analysiert und festgelegt.
25
Vgl. http://proguard.sourceforge.net/
Sebastian Stallenberger
4 Anforderungsanalyse
Da fr diese Arbeit kein externer Auftraggeber existiert, wurden die Anforderungen in Team-Besprechungen und in Absprache mit Prof. Dr. Braun deniert. Ziel ist die Entwicklung des Android-Client SpiritMobile. Hierbei soll statt der fr Android typischen Sprache Java die objekt-funktionale Sprache Scala eingesetzt werden. Im Folgenden werden nun die geforderten Features dargestellt.
Sebastian Stallenberger
4. Anforderungsanalyse
Student oder Professor) hat der Nutzer bestimmte Privilegien. Auch das HauptMen soll sich abhngig von der Rolle ndern. Einstellungen: Dem Nutzer soll es mglich sein, persnliche Einstellungen (wie Login-Daten, News-Filter usw.) ber ein Einstellungsmen festzulegen. Dieses Men soll vom Hauptmen aus erreicht werden knnen. Filter: News sollen zum Beispiel nach einem bestimmten Semester, Studiengang oder Lehrenden geltert werden knnen, um den Nutzer nicht mit Informationen zu belstigen, die er nicht bentigt. Intuitives Design: Die GUI der Applikation sollte mglichst schlicht und intuitiv gestaltet werden. Auch weniger technikane Nutzer sollen sie ohne zustzliche Informationen bedienen knnen. Mehrsprachigkeit: Die Default-Sprache der Applikation ist Deutsch. Alternativ sollte fr englische Systeme Englisch angeboten werden. Android stellt hierfr einfache Mechanismen bereit, die genutzt werden knnen. Damit knnten in Zukunft ohne Probleme auch weitere Sprachen eingebunden werden.
Sebastian Stallenberger
5 Entwurf
In diesem Kapitel wird darauf eingegangen, wie die geforderten Features mit Android und Scala umgesetzt werden knnen. Hinter SpiritMobile steckt ein modulares Konzept. Features, die in Zukunft neu hinzukommen werden, sollen mglichst einfach in die bestehende Anwendung eingegliedert werden knnen. Die Android Activities bilden hierzu die Basis. Beim Aufbau der Applikation wurde eine Art Baum-Struktur gewhlt, die aber trotzdem Verbindungen zwischen den einzelnen sten zulsst. Eine alternative Anordnung wre, das Hauptmodul wegzulassen und die Module parallel anzuordnen. Diese knnten dann durch ein Wischen mittels eines sogenannten Spinner 1 gewechselt werden, hnlich des Wechsels der verschiedenen Desktops unter Android. Aus zwei Grnden entschied sich der Autor fr die Baum-Struktur: Zum einen erzwingt die parallele Anordnung eine Reihenfolge. Will der Nutzer also zum TimeTable, muss er vorher erst alle anderen Module durch-scrollen. Zum Anderen ist die Wisch-Methode ideal, um im TimeTable zwischen den einzelnen Tagen zu navigieren und soll daher dafr vorbehalten werden. Die Root-Activity MainActivity, die beim Start der Applikation geladen wird, enthlt das Hauptmen.
Abbildung 5.1: Mglichkeiten der Nutzer-Navigation durch die Module Wie man in Abbildung 5.1 sieht, laufen von MainActivity aus ste zu NewsMulti, NewsCreate, TimeTable und Settings. Wird in NewsCreate erfolgreich eine News angelegt, wird der Nutzer zu NewsMulti weitergeleitet. Das Anklicken einer News in NewsMulti fhrt den Nutzer zu NewsSingle. In den folgenden Unterkapiteln wird auf die einzelnen Module inklusive der MainActivity eingegangen.
Vgl. http://developer.android.com/reference/android/widget/Spinner.html
Sebastian Stallenberger
5. Entwurf
5.1 MainActivity
Die MainActivity enthlt neben dem Hauptmen auch das Spirit Logo. Es bestehen mehrere Mglichkeiten der Anordnung, wie die Abbildung 5.2 zeigt.
Abbildung 5.2: Vorschlge zur Anordnung der Men-Elemente In beiden Anordnungen ist das Logo ber dem Men positioniert. In Anordung A sind die Men-Elemente in einer zweispaltigen Tabelle angeordnet. In Android kann man das entweder mit einem TableLayout2 oder aber mit einer GridView3 realisieren. Vorteil an dieser Anordnung ist der Platz, der in den einzelnen Elementen fr z.B. Symbole bereitsteht. In Anordnung B sind die Elemente wie eine Liste untereinander angeordnet. Realisieren lsst sich so etwas in Android mit einem RelativeLayout4 , einem LinearLayout5 oder einfach mit einer ListView6 . Vorteilhaft ist diese Anordnung bei Men-Elementen, in denen Texte dominieren, und wenn viele Men-Elemente dargestellt werden mssen. Fr die Arbeit wurde Anordnung A gewhlt. Zum einen ist die Anzahl der MenElemente relativ gering. Zum anderen sind quadratische Flchen fr den Nutzer einfacher zu treen. Die Mglichkeit des Einsatzes groer Symbole ist ein weiterer Bonus. Umgesetzt werden soll das Layout mit einer GridView. Je nach Rolle werden im Men folgende Elemente angezeigt: Nicht eingeloggt: Neues (NewsMulti) Rolle Student: Neues (NewsMulti), Stundenplan (TimeTable) Rolle Professor: Neues (NewsMulti), Neues verfassen (NewsCreate), Stundenplan (TimeTable)
2 3
Sebastian Stallenberger
5. Entwurf
Die Einstellungen (Settings) werden ins Options menu 7 ausgelagert, das sich net, wenn der Nutzer die Menu-Taste des Smartphones bettigt. Im nchsten Abschnitt wird auf die Activity NewsMulti eingegangen.
5.2 NewsMulti
Da sich die Anzeige der News vertikal stark ausdehnen kann, sollte dafr ein BasisLayout mit eingebauter Scroll-Funktion genutzt werden. Alternative dazu ist der Einsatz einer ScrollView8 . Der Autor hat sich fr eine Kombination aus ScrollView und LinearLayout entschieden. Der dynamische Aufbau der Layouts in SpiritMobile wird manuell durchgefhrt. Alternative wre der Einsatz einer ListView mit Adaptern9 und LayoutInatern10 , was die Aufgabe deutlich vereinfachen wrde. Der manuelle Aufbau hat allerdings die groen Vorteile, dass man exibler ist und die Konstruktion vonstatten geht, da der Aufbau an die darzustellenden Objekte perfekt angepasst ist. Die einzelnen News sollen jeweils aus einem RelativeLayout bestehen. Dies hat den Vorteil, dass Elemente nicht nur vertikal, horizontal oder in Grid-Form angeordnet werden knnen, sondern in fast beliebigen Kombinationen. Dabei wird fr jedes Element mindestens eine Abhngigkeit zu einem anderen Element angegeben. Auch Abhngigkeiten zu Eltern-Elementen sind mglich.
Wird im Kapitel 6.3.4 detailliert erlutert. Vgl. http://developer.android.com/reference/android/widget/ScrollView.html 9 Vgl. http://developer.android.com/reference/android/widget/Adapter.html 10 Vgl. http://developer.android.com/reference/android/view/LayoutInater.html
8
Sebastian Stallenberger
5. Entwurf
Wie man in Abbildung 5.3 sieht, ist ber den News-Elementen ein Element geplant, das dem Nutzer den Zeitpunkt des letzten Updates zeigt. Sind mehr News-Elemente anzuzeigen als auf den Bildschirm passen, wird der komplette Inhalt vertikal gescrollt. Ein einzelnes News-Element besteht aus vier Bereichen: Dem Header, dem Bereich fr DegreeClass, dem Content und dem Footer. Den Header teilen sich der Title (die berschrift der News) und das Datum der letzten nderung. Sollte der Title lnger sein als der Platz, der ihm zusteht, wird er gekrzt und bekommt ... als Abschluss. Unter dem Header ist eine Zeile fr DegreeClass reserviert. DegreeClass sind die Nutzergruppen, die die News betrit, zum Beispiel MAI3 oder BaI4. Im Mittelteil des News-Elements bendet sich der Content11 . Dieser ist in der MultiNews-Ansicht allerdings auf eine Zeile beschrnkt und wird bei lngeren Texten gekrzt und mit ... abgeschlossen. Der Footer enthlt den Owner (Eigentmer) der News und die Anzahl der Kommentare, die zu der News abgegeben wurden. Die Krzung der Elemente mit berlnge ermglicht einen kompakten, einheitlichen berblick ber alle News-Elemente. Filterkriterien fr News sind das Zeitintervall, der Owner und die Zielgruppen12 , an die die News gerichtet sind. Die Auswahl der Filter kann entweder in den allgemeinen Settings oder in einem Option Menu erfolgen. News des aktuell eingeloggten Nutzers werden farblich hervorgehoben, um zwischen eigenen und fremden News einfacher unterscheiden zu knnen. Eigene News knnen vom Nutzer gelscht werden. Klickt der Nutzer lang auf seine eigene News, fragt ihn ein Dialog, ob er die News auch wirklich lschen will. Die News werden in folgenden Situationen neu abgerufen: Der Nutzer kommt von MainActivity. Der Nutzer bettigt den Reload-Button im Option Menu. Der Nutzer hat in NewsSingle einen neuen Comment angelegt und kehrt zu NewsMulti zurck. Der Nutzer hat eine neue News in NewsCreate erfolgreich angelegt. Ist zum Zeitpunkt des Reloads keine Internetverbindung vorhanden, wird dies dem Nutzer in einem Toast13 mitgeteilt. Im nchsten Abschnitt wird auf die NewsSingle-Activity eingegangen, die ein Nutzer sieht, wenn er eine News anklickt.
5.3 NewsSingle
Die Einzel-Ansicht NewsSingle unterscheidet sich von der NewsMulti schon dadurch, dass Title und Content nicht mehr gekrzt werden. Beide Felder erweitern sich dynamisch angepasst an den Text vertikal. Sonst entspricht der Aufbau des NewsElements dem in NewsMulti.
11 12
also der Inhalt der News im Datenmodell DegreeClass genannt 13 Vgl. http://developer.android.com/reference/android/widget/Toast.html
Sebastian Stallenberger
5. Entwurf
Abbildung 5.4: Aufbau von NewsSingle, des Comment-Blocks und eines einzelnen Comment-Elements NewsSingle ist dabei aufgeteilt in einen Bereich fr die News und einen Bereich fr die dazugehrigen Comments. Im Comment-Bereich werden alle Comments der Nutzer mit den Daten Owner, Date und Content aufgelistet, wie aus der Abbildung 5.4 zu entnehmen ist. Ist der Nutzer eingeloggt und hat die dementsprechende Berechtigung, erscheint unter der Comments-Auistung ein Eingabefeld, ber das ein neuer Comment eingetragen werden kann. Der Send-Button bendet sich unter diesem Eingabefeld. Comments des aktuell eingeloggten Nutzers werden auch hier farblich hervorgehoben. Eigene Comments knnen auf dieselbe Art und Weise wie bei den News gelscht werden. Wurde ein Comment angelegt, wird die dazugehrige News komplett neu geladen und die Ansicht in NewsSingle aktualisiert. Bei Rckkehr zu NewsMulti wird auch dort ein Reload durchgefhrt, um die Anzahl der Comments korrekt anzeigen zu knnen. Schlgt das Senden eines Comments fehl, wird der Nutzer darber in einem Toast informiert. Das folgende Kapitel befasst sich mit der Activity NewsCreate, in der neue News angelegt werden knnen.
5.4 NewsCreate
Ist dem aktuell eingeloggten Nutzer die Rolle Professor zugeordnet, wird ihm im Hauptmen das Men-Element fr NewsCreate angezeigt. Wie in Abbildung 5.5 dargestellt, wird als Basis-Layout ein LinearLayout gewhlt. Der Nutzer kann im EditText14 Subject den Betre und im EditText Message die Nachricht eingeben. Die betreenden DegreeClasses und das ExpireDate werden
14
Vgl. http://developer.android.com/reference/android/widget/EditText.html
Sebastian Stallenberger
5. Entwurf
Abbildung 5.5: Aufbau von NewsCreate in Buttons angezeigt. Ein Klick auf den DegreeClass-Button net ein Popup mit einer Mehrfachauswahl, die alle verfgbaren DegreeClasses enthlt. Beim Klick auf ExpireDate net sich ein Popup mit einem DatePicker. Ein Klick auf den Send-Button sendet die News an den Server. War das Eintragen erfolgreich, wechselt die Ansicht zu NewsMulti. Schlug es fehl, wird der Nutzer darber in einem Toast informiert und die Ansicht mit allen Eingaben bleibt erhalten.
5.5 TimeTable
Die technischen Hintergrundprozesse des TimeTable gestalten sich verhltnismig einfach, da der TimeTable nur angezeigt werden soll und keine eigenen Appointments eingetragen werden knnen. Wie in Abbildung 5.6 dargestellt, unterscheidet sich der Aufbau des TimeTable je nach Lage des Gerts: Vertikale Lage: Es wird eine Ein-Tages-Ansicht dargestellt. Am oberen Rand des Bildschirms bendet sich eine Navigations-Leiste mit dem Datum des angezeigten Tags sowie Vor- und Zurck-Buttons. Will man den Tag wechseln, kann das durch Klicken der Buttons oder aber durch einfaches Wischen in die entsprechende Richtung bewirkt werden. Die Ansicht eines einzelnen Tages ist in Zeitabschnitte (folglich genannt TimeSlots) unterteilt. Die Zeitintervalle fr die TimeSlots wurden nach dem Vorbild des Stundenplans des Fachbereich Informatik festgelegt. Fr jeden TimeSlot knnen bis zu drei Appointments (Termine) angezeigt werden. Ein Klick auf einen TimeSlot net die Detailansicht, auf die in Kapitel 5.6 eingegangen wird. Horizontale Lage: hnlich wie bei der Ein-Tages-Ansicht gibt es in der WochenAnsicht eine Navigationsleiste am oberen Bildschirmrand. Die Bedienung entspricht dabei der der Ein-Tages-Ansicht. Diese Ansicht ist rein dazu gedacht, sich einen berblick zu verschaen. Ein Klick auf die Tage oder Appointments bewirkt nichts.
Sebastian Stallenberger
5. Entwurf
Abbildung 5.6: Aufbau des TimeTable in vertikaler und horizontaler Lage Im folgenden Abschnitt wird der Entwurf eines Timeslots detaillierter dargestellt.
5.6 TimeSlot
In einem Header wird das zum TimeSlot gehrige Datum und das Zeitintervall angezeigt. Darunter werden alle Appointments, die den Timeslot belegen, aufgelistet. Daten der einzelnen Appointments sind, wie in Abbildung 5.7 zu sehen: Name des Events, zu dem das Appointment gehrt. Status des Appointments. Hier wre auch eine farbliche Kennzeichnung mglich (z.B. ausgegraut fr canceled). Die Location des Appointments. Der Lecturer des Appointments.
5.7 Settings
Fr die Settings-Activity wird nicht Activity als Basis gewhlt. Android stellt mit der PreferenceActivity ein ideales Grundgerst fr eine Activity bereit, ber die Einstellungen gettigt werden sollen. Was die PreferenceActivity von Activity unterscheidet, wird im Kapitel 6 nher beleuchtet. Die Settings-Activity ist in vier Abschnitte unterteilt: Login section: Enthlt Eingabefelder fr die FHS-Id und das Passwort. Hier knnte auch noch der Login-Status angezeigt werden.
Sebastian Stallenberger
5. Entwurf
Abbildung 5.7: Aufbau der Detailansicht eines Timeslots News section: Bietet Filter-Optionen fr die News an. TimeTable section: Hier kann der Nutzer Einstellungen treen, die den TimeTable betreen. Other: Enthlt sonstige Einstellungen wie zum Beispiel eine Checkbox fr den Standalone-Modus.
Abbildung 5.8: Aufbau von Settings Ein schematischer Entwurf von Settings ist in Abbildung 5.8 zu sehen. Neben den bisher vorgestellten Activities, die der Nutzer sehen kann, gibt es auch einige Hintergrundprozesse, die im Folgenden nher beschrieben werden.
Sebastian Stallenberger
5. Entwurf
Abbildung 5.9: Abruf-Algorithmus in NewsMulti geht von einer der im Kapitel 5.2 genannten Nutzer-Aktionen aus. Im ersten Schritt wird berprft, ob eine Internetverbindung vorhanden ist. Sollte dies nicht der Fall sein, wird geprft, ob im Speicher des Gerts schon ein lterer JSON-String vorhanden ist. Ist dies auch nicht der Fall, sieht der Nutzer eine Fehlermeldung. Ist im Speicher ein JSON-String vorhanden, wird dieser geladen, in ein Objekt gewandelt und zurckgegeben.
15
Vgl. https://secure.wikimedia.org/wikipedia/de/wiki/Overhead_%28EDV%29
Sebastian Stallenberger
5. Entwurf
Ist eine Internetverbindung vorhanden, stellt SpiritMobile eine Anfrage an den Server. Wenn diese fehlschlgt, wird dem Nutzer wieder eine Fehlermeldung angezeigt. Ist die REST-Abfrage allerdings erfolgreich, passieren drei Dinge: Der JSON-String wird fr den Oine-Gebrauch im Speicher abgelegt. Sollte einmal ein Service fr den regelmigen News-Abruf implementiert werden, kann dieser String auch dazu genutzt werden, um zu entscheiden, ob neue News vorhanden sind oder nicht. Das Datum des letzten Abrufs wird festgesetzt. Wie schon beschrieben, wird dieses Datum in der Header-Zeile von NewsMulti angezeigt. Der JSON-String wird in ein Objekt umgewandelt und dies wird zurckgegeben. Der Abruf einzelner News erfolgt nur, wenn ein neuer Kommentar angelegt wurde. Der Ablauf der dazugehrigen Abfrage ist hnlich und wird daher hier nicht noch einmal skizziert. Allerdings fallen dabei alle Speicheroperationen und das Speichern des letzten Abruf-Datums weg. Sowohl beim Einzelabruf als auch beim Abruf mehrerer News wird das Layout anschlieend mit den neuen Daten neu geladen. Der Abruf der TimeTable-Daten ist vom Prinzip her mit dem Abruf der News weitgehend identisch, weshalb an dieser Stelle nicht weiter darauf eingegangen wird.
Sebastian Stallenberger
5. Entwurf
Sind alle Voraussetzungen erfllt, wird eine REST-Anfrage an den Server geschickt. Hat diese Erfolg, wird dem Nutzer eine Erfolgs-Meldung prsentiert und ein Reload veranlasst, um die gerade eingetragenen Daten anzuzeigen. Ist eine der Voraussetzungen nicht erfllt oder schlgt die Anfrage fehl, wird auch hier dem Nutzer eine Fehlermeldung angezeigt. Bei Kommentaren wird nur auf die Internetverbindung und Message geprft. Nachdem in diesem Kapitel ausfhrlich Entwrfe fr die verschiedenen Vorgnge in der Applikation SpiritMobile vorgestellt wurden, wird nun im Kapitel 6 detailliert betrachtet, ob und wie ausgewhlte Problemstellungen in Scala umgesetzt werden knnen, die bei der prototypischen Implementierung der Applikation auftraten.
Sebastian Stallenberger
6 Prototypische Implementierung
In diesem Kapitel werden ausgewhlte Probleme, die bei der Implementierung von SpiritMobile auftraten, und deren Lsungen vorgestellt. Um es dem Leser besser verstndlich zu machen, werden prgnante Stellen aus dem Code der Activities und Klassen von SpiritMobile gezeigt und erlutert. Der komplette Code des Prototypen von SpiritMobile ist auf Github1 verfgbar. Der Code kann sich in der Implementierung allerdings von den im aktuellen Kapitel enthaltenen Code-Auszgen unterscheiden. Das Feature TimeTable ist in der aktuellen Version von SpiritMobile noch nicht implementiert. Jedoch unterscheidet dieses sich technisch nicht von den in diesen Kapiteln beschriebenen Features und kann deshalb vernachlssigt werden. Viele Code-Beispiele sind noch nicht auf Mehrsprachigkeit ausgelegt und enthalten deshalb teilweise Strings statt Verweise auf res/values.
6.2 AsyncTasks
Eine der goldenen Regeln bei der Android Programmierung ist, den UI 3 -Thread nicht zu blockieren. Tut man dies trotzdem, bricht nach 5 Sekunden die Anwendung mit dem Dialog Die Anwendung reagiert nicht mehr ab. Um das zu vermeiden, gibt es diverse Mglichkeiten. Die komfortabelste dabei ist der sogenannte AsyncTask. Ein AsyncTask ist ein von Android bereitgestelltes Konstrukt, das es ermglicht, Operationen im Hintergrund auszufhren und die Ergebnisse anschlieend wieder im UI-Thread anzuzeigen. Dabei mssen keine Threads oder Handler manipuliert werden.
1 2
Sebastian Stallenberger
6. Prototypische Implementierung
Die vier Methoden von AsyncTask sind in ihrer logischen Reihenfolge: onPreExecute: In dieser Methode werden Operationen ausgefhrt, die vor dem eigentlichen Ausfhren der Hintergrund-Operation im UI-Thread gestartet werden sollen. So kann hier zum Beispiel ein ProgressDialog oder eine ProgressBar initialisiert werden. doInBackground: Nachdem onPreExecute ausgefhrt wurde, wird der Inhalt dieser Methode vom Hintergrund-Thread ausgefhrt. Die an den AsyncTask bergebenen Parameter werden an diese Methode weitergeleitet. Der Return-Wert dieser Methode wird an onPostExecute bergeben. onProgressUpdate: Wird in doInBackground die Methode publishProgress aufgerufen, kann mit onProgressUpdate der UI-Thread aktualisiert werden. So kann zum Beispiel bei einem Download der prozentuale Fortschritt in einer ProgressBar aktualisiert werden. onPostExecute: Wird wieder vom UI-Thread aufgerufen, nachdem die HintergrundOperation abgeschlossen ist. Bekommt das Ergebnis von doInBackground als Parameter. Neben diesen hat AsyncTask noch die Methode onCancelled, auf die an dieser Stelle aber nicht weiter eingegangen wird.[Goo11d] Der Anfang eines solchen Async-Tasks in Java wrde folgendermaen aussehen: Listing 6.1: Die ersten zwei Zeilen eines AsyncTasks in Java. Quelle: [Goo11d] private class DownloadFilesTask extends AsyncTask < URL , Integer , Long > { protected Long doInBackground ( URL ... urls ) { [...] Das Problem ist, dass Scala im Fall von AsyncTasks aktuell keine varargs4 annimmt. Will man also einen AsyncTask in Scala nutzen, muss man laut [Sil11] eine WrapperKlasse in Java schreiben, welche die varargs auf ein einzelnes arg reduziert. Wie dies im Fall von SpiritMobile umgesetzt wurde, kann man in Listing 6.2 sehen. Listing 6.2: Die AsyncTask Wrapper-Klasse in Anlehnung an [Sil11] public abstract class AsyncTaskAdapter < T1 , T2 , T3 > extends AsyncTask < T1 , T2 , T3 > { protected T3 doInBackground ( T1 ... param ) { return doInBackground ( param [0]) ; } abstract protected T3 doInBackground ( T1 param ) ; } Doch wie sieht denn jetzt eigentlich eine solche Umsetzung eines AsyncTasks in Scala aus? In der aktuellen Version von SpiritMobile hat jeder Hintergrundprozess aus Grnden der bersichtlichkeit seinen eigenen AsyncTask. Diese AsyncTasks sind NewsReloadTask, NewsLoadSingleTask, NewsCreateTask, NewsCommentCreateTask und VerifyLoginDataTask.
4
Sebastian Stallenberger
6. Prototypische Implementierung
Am Beispiel des in Listing 6.3 aufgefhrten NewsLoadSingleTask wird nun der prinzipielle Aufbau erlutert. Die Besonderheiten anderer AsyncTasks werden spter erlutert. Listing 6.3: Code des AsyncTask NewsReloadTask class NewsLoadSingleTask ( context : Context ) extends AsyncTaskAdapter [ String , Unit , News ] { val progressDialog : ProgressDialog = ProgressDialog . show ( context , " Bitte warten " , " Die News wird aktualisiert . " , true , false ) override protected def onPreExecute { progressDialog . show } override protected def doInBackground ( param : String ) : News = { SpiritConnect . getSingleNews ( context , param ) } override protected def onPostExecute ( param : News ) { progressDialog . dismiss val act = context . asInstanceOf [ Activity ] val intent = act . getIntent intent . addFlags ( Intent . FLAG _ ACTIVITY _ NO _ ANIMATION ) intent . putExtra ( " newSingleNews " , param ) act . overridePendingTransition (0 , 0) act . finish act . startActivity ( intent ) } } Als Parameter nimmt die Klasse den context der aufrufenden Activity entgegen. Zu Beginn wird ein ProgressDialog deniert und in der Methode onPreExecute genet. Darauf folgend ruft doInBackground die Methode SpiritConnect.getSingle News auf. Diese wird im Kapitel 6.5 erlutert. doInBackground bergibt das empfangene News-Objekt an onPostExecute. Anschlieend wird der Dialog geschlossen, dem Intent5 der Activity die gerade empfangene News als Extra bergeben und die Activity neu geladen. Die Methode overridePendingTransition(0,0) bewirkt, dass beim Beenden und Neustarten keine Animationen ausgefhrt werden. Nachdem nun die Grundlagen fr das Verstehen von Hintergrundprozessen in Android gelegt wurden, werden in den folgenden Kapiteln die Activities an sich behandelt. Den Anfang macht dabei die MainActivity.
http://developer.android.com/reference/android/content/Intent.html
Sebastian Stallenberger
6. Prototypische Implementierung
Abbildung 6.1: Lebenszyklus einer Activity in Anlehnung an [Goo11a] Laut [Goo11a] hat eine Activity vier Zustnde: Wenn sie im Vordergrund auf dem Bildschirm ist, ist sie aktiv oder laufend. Wenn sie den Fokus verloren hat, aber noch sichtbar ist, ist sie pausiert. Eine pausierte Activity ist komplett am Leben, kann aber gewaltsam beendet werden, wenn das System mehr Speicher bentigt. Eine komplett von einer anderen Anwendung verdeckte Activity ist gestoppt. Alle Zustands-Informationen bleiben erhalten, aber auch hier kann die Activity gewaltsam beendet werden, wenn Speichermangel herrscht. Nach dem Stoppen aufgrund von Speicherproblemen kann eine Activity wieder neu gestartet und wiederhergestellt werden. Nachdem nun die allgemeinen Ablufe in einer Activity bekannt sind, werden im Folgenden die onCreate- und onResume-Methoden der MainActivity betrachtet.
Sebastian Stallenberger
6. Prototypische Implementierung
Sebastian Stallenberger
6. Prototypische Implementierung
ViewBuilder.getTrialView statt, welche ein RelativeLayout zurckliefert. Wie in den Listings 6.5, 6.6 und 6.7 zu sehen, kann die Methode in drei Abschnitte gegliedert werden: das Anlegen und Kongurieren der einzelnen Elemente, das Setzen der Layout-Attribute und den Zusammenbau des gesamten Layouts. Listing 6.5: Der Code fr den Aufbau des Beta-Screens (Teil 1) def getTrialView ( context : Context ): RelativeLayout = { val activity = context . asInstanceOf [ Activity ] val mainlay = new RelativeLayout ( context ) { val id _ logo = 2020201 [...] val id _ button = 2020204 val logo = new ImageView ( context ) { setImageDrawable ( getResources . getDrawable ( R . drawable . logo _ spirit _ transp ) ) setAdjustViewBounds ( true ) setPadding (0 , 20 , 0 , 30) setId ( id _ logo ) } val tv = new TextView ( context ) { setText ( " Beta Passphrase " ) setId ( id _ text ) } val edit = new EditText ( context ) { setMinimumWidth (200) setId ( id _ edit ) } val button = new Button ( context ) { setText ( " Unlock " ) setId ( id _ button ) setOnClickListener ( new OnClickListener { def onClick ( p1 : View ) { SpiritHelpers . setPrefs ( activity , " trialPassphrase " , edit . getText . toString , false ) var intent : Intent = activity . getIntent activity . finish activity . overridePendingTransition (0 , 0) activity . startActivity ( intent ) } }) } Zu Beginn werden beliebig gewhlte Integer-Werte als Ids fr die Elemente festgesetzt. Danach folgt das Logo, das in eine ImageView gebettet ist. Als Ressource dafr dient das Drawable logo_spirit_transp, das im Verzeichnis res/drawable abgelegt ist. Das Logo ist eigentlich grer als der Bildschirm, wird aber passend, mit gleichbleibendem Seitenverhltnis, skaliert. Doch auch mit der Skalierung wr-
Sebastian Stallenberger
6. Prototypische Implementierung
de normalerweise Platz fr die Originalgre des Bilds reserviert werden und somit den folgenden Inhalt unschn vertikal verschieben. Um das zu vermeiden, ist das Attribut setAdjustViewBounds auf true gesetzt. Mit setPadding kann dann der gewnschte innere Abstand6 der View Links, Oben, Rechts und Unten gesetzt werden. Letztendlich setzt setId die Id des Elements. Fr die TextView werden die Attribute Text und Id gesetzt. Gleich darauf folgend fr das EditText die minimale Breite und die Id. Beim Button wird neben dem Text erstmals ein onClickListener gesetzt. Bei einem Klick auf den Button wird zum einen die Funktion SpiritHelpers.setStringPrefs aufgerufen und zum anderen die aktuelle Activity neu gestartet. Auf die Methode SpiritHelpers.setStringPrefs wird im Kapitel 6.9 genauer eingegangen. Listing 6.6: Der Code fr den Aufbau des Beta-Screens (Teil 2) var lp _ iv = new RelativeLayout . LayoutParams ( -2 , -2) . asInstanceOf [ RelativeLayout . LayoutParams ] lp _ iv . addRule ( RelativeLayout . CENTER _ HORIZONTAL ) lp _ iv . addRule ( RelativeLayout . ALIGN _ PARENT _ TOP ) var lp _ tv = new RelativeLayout . LayoutParams ( -2 , -2) . asInstanceOf [ RelativeLayout . LayoutParams ] lp _ tv . addRule ( RelativeLayout . BELOW , id _ logo ) lp _ tv . addRule ( RelativeLayout . CENTER _ HORIZONTAL ) var lp _ edit = new RelativeLayout . LayoutParams ( -2 , -2) . asInstanceOf [ RelativeLayout . LayoutParams ] lp _ edit . addRule ( RelativeLayout . BELOW , id _ text ) lp _ edit . addRule ( RelativeLayout . CENTER _ HORIZONTAL ) var lp _ button = new RelativeLayout . LayoutParams ( -2 , -2) . asInstanceOf [ RelativeLayout . LayoutParams ] lp _ button . addRule ( RelativeLayout . BELOW , id _ edit ) lp _ button . addRule ( RelativeLayout . CENTER _ HORIZONTAL ) Der zweite Abschnitt der Methode enthlt Regeln fr den Aufbau des RelativeLayouts. Hier kann angegeben werden, welches Element sich abhngig von den anderen Elementen oder vom Eltern-Element wo im Aufbau benden soll. Die -2 in den LayoutParams steht fr WRAP_CONTENT, was bedeutet, dass das Element nur so gro ist, wie sein Inhalt. Eine andere Option wre FILL_PARENT7 (als Int: -1), was dann die gesamte Bildschirm-Hhe bzw. -Breite ausfllt.
6 7
also dem Abstand zwischen dem Inhalt und den ueren Rndern in API Level 8 und hher wurde dies in MATCH_PARENT umbenannt
Sebastian Stallenberger
6. Prototypische Implementierung
Listing 6.7: Der Code fr den Aufbau des Beta-Screens (Teil 3) addView ( logo , lp _ iv ) addView ( tv , lp _ tv ) addView ( edit , lp _ edit ) addView ( button , lp _ button ) } mainlay } Im dritten Abschnitt werden die einzelnen Elemente zum RelativeLayout hinzugefgt und das Layout wird zurckgegeben. Nach dem Aufbau des Beta-Screens geht der Autor auf den des Hauptmens ein.
6.3.3 Hauptmen
Das Hauptmen sollte in seiner Implementierung sollte so gestaltet sein, dass es mglichst einfach und dynamisch erweiterbar ist. Der Autor hat sich fr eine GridView in Kombination mit einem BaseAdapter8 entschieden, da dieser durch das Android SDK schon bereitgestellt wird und genau die gewnschte Funktionalitt bietet. Das Men wird in der Methode buildMenu aufgebaut. Listing 6.8: Die Abfrage der Rolle und die Denition des Men-Arrays. val role = SpiritHelpers . getStringPrefs ( MainActivity . this , " role " , false ) val dataList = role match { case " professor " = > List ( new MenuEntry ( R . drawable . menu _ news _bs , getString ( R . string . menu _ news ) , " readNews " , true ) , new MenuEntry ( R . drawable . menu _ writenews _bs , getString ( R . string . menu _ createNews ) , " writeNews " , true ) , new MenuEntry ( R . drawable . menu _ timetable _bs , getString ( R . string . menu _ timetable ) , " timeTable " , false ) ) case " student " = > List ( new MenuEntry ( R . drawable . menu _ news _bs , getString ( R . string . menu _ news ) , " readNews " , true ) , new MenuEntry ( R . drawable . menu _ timetable _bs , getString ( R . string . menu _ timetable ) , " timeTable " , false ) ) case _ = > List ( new MenuEntry ( R . drawable . menu _ news _bs , getString ( R . string . menu _ news ) , " readNews " , true ) ) } Zu Beginn wird die Rolle des aktuell eingeloggten Nutzers ber die Methode Spirit Helpers.getStringPrefs9 abgefragt. Der Inhalt des eigentlichen Mens wird in einer Liste von MenuEntry-Elementen deniert. Ein MenuEntry-Element enthlt dabei
8 9
Sebastian Stallenberger
6. Prototypische Implementierung
jeweils die Drawable-Id, den Text, einen String, der die beim Klick auszufhrende Aktion beschreibt und einen Boolean-Wert, der deniert, ob das Element aktiv oder inaktiv ist. Abhngig von der abgefragten Rolle wird dataList mit verschiedenen Listen und somit das Men nach dem Aufbau mit verschiedenen Elementen gefllt. ber die Methode findViewById holt man sich die GridView aus dem externen XML-Layout. ber setAdapter wird der in Listing 6.11 denierte Adapter der GridView zugewiesen. In Listing 6.9 wird auerdem das onClick-Ereignis der einzelnen Elemente abgefangen. Listing 6.9: Die Verbindung zwischen dataList und der GridView. val grid = findViewById ( R . id . grid ) . asInstanceOf [ GridView ] grid . setAdapter ( new MainMenuAdapter ( MainActivity . this , dataList ) ) grid . setOnItemClickListener ( new OnItemClickListener { def onItemClick ( parent : AdapterView [_] , p2 : View , itemId : Int , p4 : Long ) { val clickedItem = parent . getItemAtPosition ( itemId ) . asInstanceOf [ MenuEntry ] if (! clickedItem . enabled ) { Toast . makeText ( MainActivity . this , getString ( R . string . err _ notImplementedYet ) , Toast . LENGTH _ SHORT ) . show () } else { menuHandler ( MainActivity . this , clickedItem . functionName ) } } } ) Ist ein Item aktiv, wird der menuHandler (Listing 6.10) angewiesen, die in dataList fr dieses Element denierte Funktion auszufhren. TimeTable ist in der prototypischen Implementation noch nicht vorhanden, weshalb auch die Methode dafr im menuHandler fehlt. Listing 6.10: Die Methode menuHandler. def menuHandler ( context : Context , functionName : String ) { functionName match { case " readNews " = > val intent = new Intent ( context , classOf [ NewsMulti ]) intent . putExtra ( " load " , true ) context . startActivity ( intent ) case " writeNews " = > startActivity ( new Intent ( this , classOf [ NewsCreate ]) ) } }
Sebastian Stallenberger
6. Prototypische Implementierung
Wie schon geschrieben, erweitert der MainMenuAdapter den BaseAdapter. Zu Beginn werden erforderliche Methoden implementiert, die fr den Zugri des Adapters auf die Liste dataList ntig sind. Listing 6.11: Der MainMenuAdapter. class MainMenuAdapter ( context : Context , menuItems : List [ MenuEntry ]) extends BaseAdapter { override def getCount : Int = { menuItems . size } override def getItem ( position : Int ): Object = { menuItems ( position ) } override def getItemId ( position : Int ): Long = { position } override def getView ( position : Int , convertView : View , parent : ViewGroup ): View = { var mev : MenuEntryView = null if ( convertView == null ) { mev = new MenuEntryView ( context , menuItems ( position ) ) } else { mev = convertView . asInstanceOf [ MenuEntryView ] } mev } } In der Klasse MenuEntryView werden der Aufbau und das Design der einzelnen Men-Elemente festgelegt. In Listing 6.11 sind allerdings nur die neuen und relevanten Elemente von MenuEntryView zu sehen, da der Rest jedem anderen LayoutAufbau per Code hnelt. Das MenuEntryView basiert erneut auf einem RelativeLayout. Nach dem Erzeugen der ImageView und der TextView werden die Drawables fr den Hintergrund und das Men-Element-Icon geladen. Nachdem der Alpha-Wert des Hintergrunds auf 60% gesetzt wurde, wird die Farbe der TextView auf SpiritWhite gesetzt, das vorher in einer externen XML-Datei deniert wurde. Wirklich interessant ist der mittlere Teil des Listings 6.12, in dem ein ColorFilter10 fr die Drawables gesetzt wird. Laut [Guy09] haben Drawables derselben Ressource, die an verschiedenen Stellen im Code geladen werden, einen sogenannten Constant state. ndert man zum Beispiel den Alpha-Wert eines Drawables an Stelle 1, ist ab diesem Zeitpunkt der Alpha-Wert auch fr das Drawable an Stelle 2 und 3 gesetzt.
10
Vgl. http://developer.android.com/reference/android/graphics/ColorFilter.html
Sebastian Stallenberger
6. Prototypische Implementierung
Listing 6.12: Auszge aus der Klasse MainMenuAdapter. [...] var iv : ImageView = new ImageView ( context ) var tv : TextView = new TextView ( context ) var iconDraw = getResources . getDrawable ( menuEntry . drawableId ) var menuItemBackground = getResources . getDrawable ( R . drawable . optmenubg ) menuItemBackground . setAlpha (60) tv . setTextColor ( getResources . getColor ( R . color . SpiritWhite ) ) if (! menuEntry . enabled ) { menuItemBackground . mutate () . setColorFilter ( 0 x77000000 , Mode . SRC _ ATOP ) iconDraw . mutate () . setColorFilter ( 0 x77000000 , Mode . SRC _ ATOP ) iconDraw . mutate () . setAlpha (50) tv . setTextColor ( getResources . getColor ( R . color . SpiritLightBlue ) ) } setBackgroundDrawable ( menuItemBackground ) iv . setImageDrawable ( iconDraw ) [...] Fr unser Men heit das, wenn Men-Element 1 deaktiviert ist und damit das Drawable des Hintergrunds einen ColorFilter bekommt und sein Alpha-Wert auf 50% gesetzt wird, dann sind diese nderungen auch fr die Hintergrnde der anderen Men-Elemente gesetzt. Abhilfe schat die Drawable-Methode mutate. Dadurch bekommt jedes Drawable aus der gleichen Ressource einen anderen Constant state und damit auch seinen eigenen Farblter und Alpha-Wert. Im nchsten Abschnitt wird das Android Options menu betrachtet, das an einigen Stellen in SpiritMobile eingesetzt wird.
Sebastian Stallenberger
6. Prototypische Implementierung
Abbildung 6.2: Screenshot der Activity MainActivity mit genetem Optionsmen Listing 6.13: Der Aufbau eines Optionsmen (Teil 1) override def onCreateOptionsMenu ( menu : Menu ) = { val inflater : MenuInflater = getMenuInflater inflater . inflate ( R . menu . options , menu ) true } Diese Datei options.xml ist der zweite Teil. Das Root-Element ist der menu-Tag. Fr jedes Element, das im Men angezeigt werden soll, wird ein item-Tag angelegt. Dieser enthlt die Informationen id, icon und title. Listing 6.14: Der Aufbau eines Optionsmen (Teil 2) <? xml version =" 1.0 " encoding =" utf -8 " ? > < menu xmlns : android =" http : // schemas . android . com / apk / res / android " > < item android : id ="@+ id / userdata " android : icon ="@ drawable / optmenuuser _l " android : title =" Einstellungen " / > < item android : id ="@+ id / help " android : icon ="@ drawable / optmenuhelp _l " android : title =" Hilfe " / > </ menu > Der dritte Teil ist die Methode onOptionsItemSelected, die bestimmt, was geschieht, wenn auf die einzelnen Elemente des Mens geklickt wird.
Sebastian Stallenberger
6. Prototypische Implementierung
Listing 6.15: Der Aufbau eines Optionsmen (Teil 3) override def onOptionsItemSelected ( item : MenuItem ) = { item . getItemId match { case R . id . userdata = > val optUserDataIntent = new Intent ( this , classOf [ Settings ]) startActivity ( optUserDataIntent ) true case R . id . help = > val optInfoIntent = new Intent ( this , classOf [ Info ]) startActivity ( optInfoIntent ) true case _ = > super . onOptionsItemSelected ( item ) } } So wird anhand der Id das Event zugewiesen und entweder die Settings- oder die InfoActivity gestartet. Das waren die wichtigsten Elemente der Activity MainActivity. Im Kapitel 6.4 wird nun die Activity NewsMulti vorgestellt.
Sebastian Stallenberger
6. Prototypische Implementierung
de ViewBuilder.constructNewsMultiMainLay, welche in Listing 6.18 zu sehen ist, aufgebaut. Allerdings diesmal mit aktuellen Daten. Listing 6.17: Die Methode checkInternetConnection in SpiritConnect def checkInternetConnection ( context : Context ): Boolean = { val connectivityManager = context . getSystemService ( Context . CONNECTIVITY _ SERVICE ) . asInstanceOf [ ConnectivityManager ] if ( && && connectivityManager . getActiveNetworkInfo != null connectivityManager . getActiveNetworkInfo . isAvailable connectivityManager . getActiveNetworkInfo . isConnected ) {
Sebastian Stallenberger
6. Prototypische Implementierung
Nach dem Laden des XML-Basis-Layouts (res/layout/newsmulti.xml) mit der TextView fr die Anzeige des letzten Updates und des LinearLayouts als Container fr die News wird der vorher gespeicherte News-JSON-String mittels der Methode SpiritHelpers.loadString geladen. Dieser JSON-String wird anschlieend an die Funktion JsonProcessor.jsonStringToNewsList bergeben, welche ein List [News]-Objekt zurckliefert. Beide Methoden werden im Kapitel 6.9 erklrt. Das Feld des letzten Updates wird mit dem Datum aus den Preferences gefttert, welches im Vorgang des News-Updates gesetzt wurde. Letztendlich wird aus dem List[News]-Objekt ber eine foreach-Schleife die Liste der News aufgebaut. Da sich an dieser Stelle kein Margin oder Padding setzen lie, wurde als Platzhalter zwischen den News jeweils ein FrameLayout mit einer festen Hhe eingefgt.
Sebastian Stallenberger
6. Prototypische Implementierung
Listing 6.20: Die Konstruktion einer einzelnen News in der Funktion ViewBuilder.buildNewsView (Teil 2) if ( SpiritHelpers . getStringPrefs ( activity , " editFhsId " , false ) . equals ( news . owner . fhs _ id ) ) { val filterColor = Color . rgb (199 , 21 , 133) newsbackgroundheader . mutate () . setColorFilter ( filterColor , Mode . MULTIPLY ) } val headerRelativeLayout = new RelativeLayout ([...]) { val titleTextView = new TextView ( context ) { setText ( news . title ) setTextAppearance ( context , android . R . style . TextAppearance _ Medium ) [...] setLines (1) [...] val ta : TruncateAt = TruncateAt . END setEllipsize ( ta ) } [...] setBackgroundDrawable ( newsbackgroundheader ) setId ( id _ header ) addView ( titleTextView ) addView ( dateTextView ) } Anschlieend wird der Text des Titels festgelegt und die Textgre eingestellt. Da die News in der News-Liste ja mglichst kompakt angezeigt werden sollen, wird die Hhe der Header-Zeile auf maximal eine Zeile festgelegt. Sollte der Titel zu lang fr diese Zeile sein, wird er per setEllipsize gekrzt und mit drei Punkten beendet. Die restlichen Elemente werden ebenfalls entsprechend gefllt, konguriert und in Listing 6.21 zu der View der News hinzugefgt. Am Ende der Methode werden die zwei Events onClickListener und onLongClick Listener abgefangen. Der Inhalt der onClickListeners ist im folgenden Kapitel 6.5 im Listing 6.23 zu sehen. Bei einem langen Klick auf eine News wird der onLongClickListener ausgelst. An dieser Stelle ist bei SpiritMobile aktuell noch nichts implementiert. Angedacht ist, dem Nutzer zu ermglichen, mit einem langen Klick seine eigene News zu editieren oder zu lschen. Aktuell wird dem Nutzer nur ein Toast anstelle dieser Funktionen angezeigt. In der Activity NewsMulti wird erstmals die Methode onActivityResult behandelt. Wann diese ausgelst wird und was sie bewirkt, wird im folgenden Abschnitt erlutert.
Sebastian Stallenberger
6. Prototypische Implementierung
Listing 6.21: Die Konstruktion einer einzelnen News in der Funktion ViewBuilder.buildNewsView (Teil 3) addView ( headerRelativeLayout ) addView ( degreeclassTextView ) addView ( contentTextView ) addView ( footerRelativeLayout ) [...] setOnClickListener ( new View . OnClickListener () { override def onClick ( v: View ) { [...] }} [...] if ( SpiritHelpers . getStringPrefs ( activity , " editFhsId " , false ) . equals ( news . owner . fhs _ id ) ) { setOnLongClickListener ( new OnLongClickListener { def onLongClick ( p1 : View ) = { Toast . makeText ( context , " DELETE " , Toast . LENGTH _ LONG ) . show () true }}) }} newsRelativeLayout }
6.4.3 onActivityResult
Wenn in NewsSingle ein neuer Kommentar eingetragen wurde, muss sich auch die Anzahl der in NewsMulti angezeigten Kommentare der entsprechenden News ndern. Das knnte man ber die onResume-Methode lsen. Dann wrde allerdings immer, wenn der Nutzer zu NewsMulti zurck navigiert, ein Reload ausgelst werden. Da dies nicht nur unntig ist, sondern auch sehr strend fr den Nutzer sein kann, wurde das Problem anders gelst. Listing 6.22: Die Methode onActivityResult in NewsMulti override def onActivityResult ( requestCode : Int , resultCode : Int , data : Intent ) { super . onActivityResult ( requestCode , resultCode , data ) val extras = data . getExtras if ( extras != null ) { if ( extras . containsKey ( " newSingleNews " ) ) { [...] } } }
Sebastian Stallenberger
6. Prototypische Implementierung
ber die Methode onActivityResult kann auf ein Ergebnis aus NewsSingle reagiert werden, weil NewsSingle aus NewsMulti nicht mit dem normalen startActivity gestartet wird sondern mit startActivityForResult13 . Das Ergebnis newSingleNews wird in NewsSingle dann gesetzt, wenn ein neuer Kommentar angelegt wurde. Der groe Vorteil ist also nun, dass der Reload bei der Rckkehr von NewsSingle nur ausgelst wird, wenn auch ein neuer Kommentar angelegt wurde. Wie das genau funktioniert, wird jetzt im folgenden Kapitel 6.5 noch genauer erklrt.
Sebastian Stallenberger
6. Prototypische Implementierung
Listing 6.24: Die Activity NewsSingle class NewsSingle extends Activity { var text : StringBuilder = new StringBuilder () override def onCreate ( savedInstanceState : Bundle ) { super . onCreate ( savedInstanceState ) setContentView ( R . layout . newssingle ) val intent = getIntent val extras : Bundle = intent . getExtras () . asInstanceOf [ Bundle ] if ( extras == null ) { Toast . makeText ( NewsSingle . this , getString ( R . string . err _ erroroccured ) , Toast . LENGTH _ LONG ) . show () } else { val news = if ( extras . containsKey ( " newSingleNews " ) ) { extras . get ( " newSingleNews " ) . asInstanceOf [ News ] } else { extras . get ( " singleNewsJson " ) . asInstanceOf [ News ] } setResult ( Activity . RESULT _OK , intent ) val newsMain = findViewById ( R . id . newsSingleMain ) . asInstanceOf [ LinearLayout ] newsMain . setOrientation ( LinearLayout . VERTICAL ) newsMain . addView ( ViewBuilder . buildNewsViewSingle ( NewsSingle . this , news ) ) val commList = news . newsComment . toList . asInstanceOf [ List [ NewsComment ]] newsMain . addView ( ViewBuilder . buildCommentsView ( NewsSingle . this , commList , news . news _ id ) ) } } } Der Aufbau der News ist dem einer einzelnen News in NewsMulti sehr hnlich. Allerdings ist hier weder der Inhalt des Titels noch der des Inhalts auf eine bestimmte Zeilenanzahl begrenzt. Man kann in dieser Ansicht also uneingeschrnkt die komplette News lesen. Der Aufbau der Kommentar-Liste erfolgt in ViewBuilder.buildCommentsView. Der Methode wird eine List[NewsComment] bergeben und mithilfe der Methode single CommentView wird ber eine Schleife die Liste der Kommentare als Layout konstruiert. Der Bereich zum Anlegen neuer Kommentare bendet sich direkt unter der Kommentar-Liste. ber die Abfrage in Listing 6.27 wird gesichert, dass nur Nutzer mit der Rolle professor oder student diesen Bereich angezeigt bekommen. Der Bereich an sich besteht aus einem EditText und einem Button.
Sebastian Stallenberger
6. Prototypische Implementierung
Listing 6.25: Die Methode buildCommentsView zur Konstruktion der KommentarListe. (Teil 1) def buildCommentsView ( context : Context , commentList : List [ NewsComment ] , newsId : Int ): LinearLayout = { [...] commentList . foreach ( element = > linLay . addView ( singleCommentView ( context , element ) ) ) Listing 6.26: Die Methode buildCommentsView zur Konstruktion der KommentarListe. (Teil 2) val newCommentView = new RelativeLayout ( context ) { val newCommEdit : EditText = new EditText ( context ) { [...] setOnClickListener ( new View . OnClickListener () { override def onClick ( v: View ) { newCommButton . setVisibility ( View . VISIBLE ) }}) } val newCommButton = new Button ( context ) { [...] setVisibility ( View . GONE ) setOnClickListener ( new View . OnClickListener () { override def onClick ( v: View ) { if ( newCommEdit . getText . toString . equals ( " " ) ) { Toast . makeText ( context , R . string . string _ commess _ must , Toast . LENGTH _ SHORT ) . show () } else { val fhsId = SpiritHelpers . getStringPrefs ( context . asInstanceOf [ Activity ] , " editFhsId " , false ) new NewsCommentCreateTask ( context ) . execute ( Array ( newsId . toString , fhsId , newCommEdit . getText . toString ) ) }}}) } [...] } Der Button ist zu Beginn allerdings noch nicht zu sehen und wird erst eingeblendet, wenn der Nutzer in das EditText klickt. Erreicht werden kann das ber die Attribute Visibility, die mit der Methode setVisibility wahlweise auf View.VISIBLE oder View.GONE gesetzt werden. Wird der Button gedrckt und das EditText hat keinen Inhalt, wird das dem Nutzer ber einen Toast signalisiert. Hat das EditText Inhalt, wird dieser zusammen mit der newsId und der fhsId des Nutzers an den Task NewsCommentCreateTask bergeben. In dessen doInBackground-Methode werden diese Parameter an die Methode SpiritConnect.createNewsComment weitergegeben.
Sebastian Stallenberger
6. Prototypische Implementierung
Listing 6.27: Die Methode buildCommentsView zur Konstruktion der KommentarListe. (Teil 3) val role = SpiritHelpers . getStringPrefs ( context . asInstanceOf [ Activity ] , " role " , false ) if ( role . equals ( " student " ) || role . equals ( " professor " ) ) { linLay . addView ( newCommentView ) } [...] } Listing 6.28: Auszge aus dem Task NewsCommentCreateTask. class NewsCommentCreateTask ( context : Context ) extends AsyncTaskAdapter [ Array [ String ] , Unit , Boolean ] { [...] override protected def doInBackground ( params : Array [ String ]) : Boolean = { newsId = params (0) . toInt SpiritConnect . createNewsComment ( context , newsId , params (1) , params (2) ) } override protected def onPostExecute ( success : Boolean ): Unit = { [...] if ( success ) { Toast . makeText ( context , " Kommentar erfolgreich eingetragen ! " , Toast . LENGTH _ LONG ) . show new NewsLoadSingleTask ( context ) . execute ( newsId . toString ) } else { Toast . makeText ( context , " Fehler beim Eintragen des Kommentars ! " , Toast . LENGTH _ LONG ) . show }} [...] } Der Rckgabewert der Methode createNewsComment ist ein Boolescher Wert. Ist dieser true, war das Eintragen des Kommentars erfolgreich. Dies wird dem Nutzer durch einen Toast angezeigt und der Task NewsLoadSingleTask wird mit der newsId ausgefhrt. Ist der Rckgabewert false, wird das dem Nutzer ebenfalls in einem Toast gezeigt und er hat die Mglichkeit, seine Eingabe zu verbessern oder es noch einmal zu probieren. Listing 6.29: Auszge aus dem Task NewsLoadSingleTask. class NewsLoadSingleTask ( context : Context ) extends AsyncTaskAdapter [ String , Unit , News ] { [...] override protected def onPostExecute ( param : News ) { [...] intent . putExtra ( " newSingleNews " , param ) [...]}
Sebastian Stallenberger
6. Prototypische Implementierung
Die durch getSingleNews empfangene News wird als Extra unter dem Schlssel newSingleNews gesetzt und anschlieend wird die Activity NewsSingle mit der aktuellen News neu gestartet. In den vorhergehenden Kapiteln wurde behandelt, wie der Aufbau der Activities, die fr die News-Anzeige verantwortlich sind, gelst wurde. Im folgenden Kapitel dreht sich nun alles um die Activity NewsCreate, ber die eine neue News angelegt werden kann.
Sebastian Stallenberger
6. Prototypische Implementierung
< EditText android : layout _ width =" fill _ parent " android : inputType =" textMultiLine " android : layout _ height =" wrap _ content " android : minLines =" 5 " android : layout _ below ="@+ id / tvMessage " android : layout _ alignLeft ="@+ id / tvMessage " android : layout _ alignRight ="@+ id / tvMessage " android : id ="@+ id / editMessage " android : gravity =" top " android : layout _ above ="@+ id / degreeText " / > < TextView android : text =" Betrifft :" android : layout _ height =" wrap _ content " android : layout _ width =" fill _ parent " android : id ="@+ id / degreeText " android : layout _ above ="@+ id / datePexpiredegreeText " android : background = "@ drawable / newsbackgroundsmall2 " / > < TextView android : text =" Gueltig bis : " android : layout _ height =" wrap _ content " android : layout _ width =" fill _ parent " android : id ="@+ id / datePexpiredegreeText " android : layout _ alignParentBottom =" true " android : background = "@ drawable / newsbackgroundsmall2 " / > </ RelativeLayout > < Button android : id ="@+ id / createButton " android : layout _ height =" wrap _ content " android : layout _ width =" fill _ parent " android : text =" Senden " android : layout _ alignParentBottom =" true " / > </ RelativeLayout > Wie im Kapitel 5.4 beschrieben, kann der Nutzer sowohl Betre und Nachricht eingeben als auch die betroenen DegreeClasses und das Ablaufdatum festsetzen. Das vertikal in der Gre vernderbare Element ist im Aufbau das EditText fr die Nachricht. Am Anfang der Activity NewsCreate werden Ids fr die Dialoge vergeben und leere Arrays angelegt, die zum Befllen des DegreeClass-Dialogs ntig sind. Listing 6.31: Auszug aus der Activity NewsCreate. class NewsCreate extends Activity { val DEGREEDIALOGID = 0 val DATEPICKERDIALOGID = 1 var selArrForDialog = new Array [ Boolean ](0) var degreeDropDownArr = new Array [ CharSequence ](0) In der onCreate-Methode werden die zur Vorbereitung der Dialoge ntigen Daten geladen. Zum einen sind dies alle verfgbaren DegreeClasses14 aus dem gespeicherten String degreeJsonString. Dieser wird in der Methode JsonProcessor.
14
Sebastian Stallenberger
6. Prototypische Implementierung
jsonStringToDegreeList zu einer List[DegreeClassExt] und anschlieend in der Methode SpiritHelpers.getDegreeMap zu einer Map geformt, die die Anzeigenamen und dazugehrige Ids beinhaltet. Zum anderen werden aus der Preference selectedCreationDegrees die letzten im Dialog selektierten DegreeClasses geladen. In mehreren Schritten werden die geladenen Daten in die Form transferiert, die der Dialog zur korrekten Anzeige bentigt. Listing 6.32: Laden der fr den DegreeClass-Dialog bentigten Daten. val degreeJsonString = SpiritHelpers . loadString ( NewsCreate . this , " degreeJsonString " ) val getAllDegreesObject = JsonProcessor . jsonStringToDegreeList ( degreeJsonString ) . asInstanceOf [ List [ DegreeClassExt ]] val degreeMap = SpiritHelpers . getDegreeMap ( this , getAllDegreesObject , " " ) degreeDropDownArr = SpiritHelpers . getDegreeTitleArr ( degreeMap ) selArrForDialog = new Array [ Boolean ]( degreeDropDownArr . length ) val selectedDegreesString = SpiritHelpers . getStringPrefs ( NewsCreate . this , " selectedCreationDegrees " , false ) if ( selectedDegreesString . equals ( " N / A " ) ) { SpiritHelpers . setPrefs ( NewsCreate . this , " selectedCreationDegrees " , " AllClasses " , false ) } val selectedDegreesDiffArr = selectedDegreesString . split ( " <; > " ) for ( i <- 0 until degreeDropDownArr . length ) { val degreeClass = degreeDropDownArr ( i ) if ( selectedDegreesDiffArr . contains ( degreeClass ) ) { selArrForDialog ( i ) = true } else { selArrForDialog ( i ) = false } } Der Dialog bentigt dabei ein String-Array aller verfgbaren DegreeClasses und dazugehrig ein Boolean-Array, in dem festgehalten ist, welche Elemente selektiert sind. Die selektierten Elemente sind hierbei als ein String mit dem Trennzeichen <;> zwischen den Elementen abgelegt. Nach allen Umformungen sind im Array degreeDropDownArr alle im Dialog anzuzeigenden Elemente vorhanden und im Array selArrForDialog die dazugehrigen Selektionen. Beide Arrays mssen gleich gro sein. Weiterhin werden in der onCreate-Methode Events, wie z.B. onClick abgefangen. Bei Klick auf die DegreeClasses net sich ein Dialog, ber den die DegreeClasses ausgewhlt werden knnen. Bei Klick auf das ExpireDate net sich dementsprechend ein Dialog zur Auswahl des Datums.
Sebastian Stallenberger
6. Prototypische Implementierung
Listing 6.33: Setzen der onClick-Events fr die GUI-Elemente. (Teil 1) val degreeTv = findViewById ( R . id . degreeText ) . asInstanceOf [ TextView ] degreeTv . setOnClickListener ( new OnClickListener { def onClick ( p1 : View ) { showDialog ( DEGREEDIALOGID ) } }) val expireDateTv = findViewById ( R . id . datePexpiredegreeText ) . asInstanceOf [ TextView ] expireDateTv . setOnClickListener ( new OnClickListener { def onClick ( p1 : View ) { showDialog ( DATEPICKERDIALOGID ) } }) Beim Klick auf den Senden-Button wird geprft, ob der Betre und die Nachricht ausgefllt wurden. Ist eines von beiden leer, wird dies dem Nutzer ber einen Toast signalisiert. Sind beide Felder ausgefllt, werden die zuvor gesammelten Daten FhsId, Betre, Nachricht, Ablaufdatum und betroene DegreeClasses dem NewsCreateTask bergeben, der diese als neue News eintrgt. Listing 6.34: Setzen der onClick-Events fr die GUI-Elemente. (Teil 2) val sendButton = findViewById ( R . id . createButton ) . asInstanceOf [ Button ] [...] val message = findViewById ( R . id . editMessage ) . asInstanceOf [ EditText ]. getText sendButton . setText ( getString ( R . string . string _ send _ as ) + fhsId + " ) " ) sendButton . setOnClickListener ( new OnClickListener { def onClick ( p1 : View ) { fhsId = SpiritHelpers . getStringPrefs ( NewsCreate . this , " editFhsId " , false ) expireDate = SpiritHelpers . getStringPrefs ([...]) degreeclass = SpiritHelpers . getStringPrefs ([...]) if ( subject . toString . equals ( " " ) || message . toString . equals ( " " ) ) { Toast . makeText ( NewsCreate . this , R . string . string _ subjMess _ must , Toast . LENGTH _ SHORT ) . show () } else { new NewsCreateTask ( NewsCreate . this ) . execute ( Array ( fhsId , subject . toString , message . toString , expireDate , translateDegreeNameToId ( degreeclass , degreeMap ) ) ) }}}) updateDisplay ()
Sebastian Stallenberger
6. Prototypische Implementierung
Das Vorgehen in NewsCreateTask unterscheidet sich kaum von dem beim Anlegen eines neuen Kommentars, weshalb an dieser Stelle nicht weiter darauf eingegangen wird. Nach dem erfolgreichen Eintragen der News wird NewsCreate beendet und die Activity NewsMulti aufgerufen. Da die REST-Schnittstelle als DegreeClasses jeweils die Ids entgegennimmt und nicht, wie in dieser Activity verwendet, die Namen, muss der String der selektierten DegreeClasses vor dem Versand gewandelt werden. Dies geschieht in der Methode translateDegreeNameToId. Listing 6.35: Die Methode translateDegreeNameToId. def translateDegreeNameToId ( degreeName : String , map : ListMap [ String , Int ]) : String = { val degreeNameArr = degreeName . split ( " <; > " ) var returnString = " " for ( singleDegreeName <- degreeNameArr ) { if ( singleDegreeName . equals ( degreeNameArr . last ) ) { returnString = returnString + map . getOrElse ( singleDegreeName , " " ) . toString } else { returnString = returnString + map . getOrElse ( singleDegreeName , " " ) . toString + " <; > " } } returnString } Der bergebene String wird anhand der Trennzeichen aufgesplittet. Anschlieend wird in der Map, die in der onCreate-Methode erzeugt wurde, nach dem Namen gesucht und fr jedes einzelne Element die Id zurckgegeben. Aus den Ids wird wieder ein String mit dem Trennzeichen <;> zusammengebaut und von der Funktion zurckgegeben. In Listing 6.34 wird in der letzten Zeile die Methode updateDisplay aufgerufen. Diese sorgt dafr, dass die TextViews fr die Anzeige der DegreeClasses und des ExpireDates mit aktuellen Daten versehen werden. Dies ist zum Beispiel nach einer nderung der Daten ber die Dialoge ntig. Wichtige Bestandteile dieser Activity sind die zwei Dialoge, die in diesem Kapitel bereits wiederholt erwhnt wurden. Aufgerufen werden diese ber die Methoden showDialog bei den weiter oben erwhnten onClick-Events. Verarbeitet wird der Aufruf von showDialog in der in Listing 6.36 dargestellten Methode onCreate Dialog. Listing 6.36: Die Methode onCreateDialog. override def onCreateDialog ( id : Int ): Dialog = { val c: Calendar = Calendar . getInstance () val nowYear = c . get ( Calendar . YEAR ) val nowMonth = c . get ( Calendar . MONTH ) val nextMonth = nowMonth + 1 val nowDay = c . get ( Calendar . DAY _ OF _ MONTH )
Sebastian Stallenberger
6. Prototypische Implementierung
id match { case DEGREEDIALOGID = > new AlertDialog . Builder ( this ) . setTitle ( getString ( R . string . string _ regardingDegrees ) ) . setMultiChoiceItems ( degreeDropDownArr , selArrForDialog , new DialogInterface . OnMultiChoiceClickListener { [...] }) . setPositiveButton ( " OK " , new DialogInterface . OnClickListener { def onClick ( dialog : DialogInterface , clicked : Int ): Unit = { clicked match { case DialogInterface . BUTTON _ POSITIVE = > returnSelectedDegrees }} }) . create case DATEPICKERDIALOGID = > new DatePickerDialog ( this , mDateSetListener , nowYear , nextMonth , nowDay ) } } Darin wird je nach Dialog-Id entschieden, welcher Dialog angezeigt wird: Fr den DegreeClass-Dialog wurde ein normaler AlertDialog15 mit Mehrfachauswahl gewhlt. Nach dem Selektieren der gewnschten DegreeClasses wird durch Klick auf den OkButton die Methode returnSelectedDegrees aufgerufen, die die Auswahl in einen String umwandelt und in den Preferences abspeichert. Listing 6.37: Die Methode returnSelectedDegrees. def returnSelectedDegrees : Unit = { { var i: Int = 0 var selectedDegreesString = " " while ( i < degreeDropDownArr . size ) { if ( selArrForDialog ( i ) ) { selectedDegreesString = selectedDegreesString + degreeDropDownArr ( i ) + " <; > " } i += 1 } if ( selectedDegreesString . equals ( " " ) ) { selectedDegreesString = getString ( R . string . string _ all ) + " <; > " }
15
Vgl. http://developer.android.com/reference/android/app/AlertDialog.html
Sebastian Stallenberger
6. Prototypische Implementierung
selectedDegreesString = selectedDegreesString . substring (0 , selectedDegreesString . length () - 3) SpiritHelpers . setPrefs ( NewsCreate . this , " selectedCreationDegrees " , selectedDegreesString , false ) updateDisplay () } } Der Dialog fr das ExpireDate ist ein DatePickerDialog16 , der nach der DatumsAuswahl ebenfalls das Datum wandelt und als Preference ablegt. Dies geschieht diesmal allerdings ber einen DatePickerDialog.OnDateSetListener namens mDate SetListener. Listing 6.38: Der Listener mDateSetListener. val mDateSetListener : DatePickerDialog . OnDateSetListener = new DatePickerDialog . OnDateSetListener () { def onDateSet ( view : DatePicker , year : Int , monthOfYear : Int , dayOfMonth : Int ) { val month = monthOfYear + 1 SpiritHelpers . setPrefs ( NewsCreate . this , " createNewsExpireDate " , year . toString + " -" + fillStringWithNull ( month . toString ) + " -" + fillStringWithNull ( dayOfMonth . toString ) + " 00 : 00 : 01 " , false ) updateDisplay () } } Damit sind die wichtigsten Elemente der Activity NewsCreate erlutert worden. Im folgenden Kapitel stellt der Autor die Activity Settings vor, die die Auswahl der Eigenschaften beinhaltet.
Sebastian Stallenberger
6. Prototypische Implementierung
Tags fr das Anlegen von Kategorien zustndig. Diese bilden eine Art von berschriften fr die anderen Elemente. In der Kategorie Login wurden zwei EditTextPreferences fr die Eingabe der Nutzerdaten angelegt. Die folgende CheckBoxPreference soll dem Nutzer dann signalisieren, ob er eingeloggt ist, weshalb die Attribute android:enabled und android:clickable auch auf false stehen. Fr die News und den Stundenplan sind in der prototypischen Implementierung noch keine Elemente vorhanden. Im Abschnitt Debugging kann der Nutzer whlen, ob die Applikation die Daten im Standalone-Modus oder vom Server beziehen soll. Listing 6.39: Die XML-Datei fr die Settings. <? xml version =" 1.0 " encoding =" utf -8 " ? > < PreferenceScreen xmlns : android =" http : // schemas . andro ... " android : layout _ width =" fill _ parent " android : layout _ height =" fill _ parent " > < PreferenceCategory android : title =" Login " > < EditTextPreference android : name =" FhsId " android : summary =" Gib hier deine FhsId an . " android : defaultValue =" " android : title =" FhsId " android : key =" editFhsId " / > < EditTextPreference android : name =" Passwort " android : summary =" Gib hier dein Passwort an . " android : defaultValue =" " android : title =" Passwort " android : key =" editPassword " android : password =" true " / > < CheckBoxPreference android : key =" loggedIn " android : title =" Eingeloggt ? " android : enabled =" false " android : defaultValue =" false " android : clickable =" false " / > </ PreferenceCategory > < PreferenceCategory android : title =" News " > </ PreferenceCategory > < PreferenceCategory android : title =" Stundenplan " > </ PreferenceCategory > < PreferenceCategory android : title =" Debugging " > < CheckBoxPreference android : title =" Standalone Mode " android : defaultValue =" true " android : summary =" Zum Testen der Anwendung mit fiktiven Daten . " android : key =" standalone " / > </ PreferenceCategory > </ PreferenceScreen >
Sebastian Stallenberger
6. Prototypische Implementierung
Wie der Nutzer diesen Aufbau sieht, kann man in Abbildung 6.3 in einem Screenshot sehen.
Abbildung 6.3: Screenshot der PreferenceActivity Settings Die eigentliche PreferenceActivity ist in den folgenden Listings 6.40, 6.41 und 6.42 dargestellt. Nachdem per addPreferencesFromResource die XML-Datei festgelegt wurde, werden in der onCreate-Methode die DefaultSharedPreferences und die SharedPreferences des PreferenceScreen geladen. Die DefaultSharedPreferences sind ntig, da die Preference role nicht ber die Einstellungen nderbar ist und somit auch keine Preference der SharedPreferences der PreferenceActivity Settings ist. Listing 6.40: Die PreferenceActivity Settings. (Teil 1) class Settings extends PreferenceActivity with OnSharedPreferenceChangeListener { override def onCreate ( savedInstanceState : Bundle ) { super . onCreate ( savedInstanceState ) addPreferencesFromResource ( R . xml . preferences ) ; val pref : SharedPreferences = PreferenceManager . getDefaultSharedPreferences ( this ) val sp : SharedPreferences = getPreferenceScreen () . getSharedPreferences () val name = findPreference ( " editFhsId " ) . asInstanceOf [ Preference ] if ( name . asInstanceOf [ EditTextPreference ]. getText . equals ( " " ) ) { name . setSummary ( getString ( R . string . string _ notSetYet ) ) } else { name . setSummary ( sp . getString ( " editFhsId " , " " ) ) }
Sebastian Stallenberger
6. Prototypische Implementierung
val password = findPreference ( " editPassword " ) . asInstanceOf [ Preference ] if ( password . asInstanceOf [ EditTextPreference ]. getText . equals ( " " ) ) { password . setSummary ( getString ( R . string . string _ notSetYet ) ) } else { password . setSummary ( sp . getString ( " editPassword " , " " ) . replaceAll ( " . " , " * " ) ) } val role = SpiritHelpers . getStringPrefs ( Settings . this , " role " , false ) val loggedIn = findPreference ( " loggedIn " ) . asInstanceOf [ CheckBoxPreference ] if ( role . equals ( " " ) ) { loggedIn . setSummary ( getString ( R . string . string _ notLoggedIn ) ) } else { loggedIn . setSummary ( getString ( R . string . string _ loggedInAs ) + role . toString ) } } Die Summary eines Elements kann als erluternder Text deniert werden. Im Fall der EditTexts von name (FhsId) und password wird sie dazu genutzt, dem Nutzer zu signalisieren, ob schon eine FhsId und ein Passwort gesetzt ist. Im Falle des Passworts werden alle Zeichen fr die Anzeige durch * ersetzt. Abhngig davon, ob eine role vorhanden ist bzw. ob der Nutzer eingeloggt ist, wird dem Nutzer der Login-Status und seine Rolle ber eine CheckBox und ihre Summary angezeigt. In den folgenden Methoden onPause und onResume wird der onSharedPreference ChangeListener ab- und angemeldet. Die Anmeldung ist ntig, damit die Methode onSharedPreferenceChanged eingesetzt werden kann. Die Abmeldung dagegen, um nicht unntig Ressourcen zu verbrauchen, wenn der Listener nicht bentigt wird. Listing 6.41: Die PreferenceActivity Settings. (Teil 2) override def onPause () { getPreferenceScreen () . getSharedPreferences () . un r eg is te rO nS ha re dP re fe re nc eC ha ng eL is t en er ( Settings . this ) finish () super . onPause () } override def onResume () { super . onResume () getPreferenceScreen () . getSharedPreferences () . re gi ste rO nSh ar edP re fer en ceC ha nge Li ste ne r ( Settings . this ) }
Sebastian Stallenberger
6. Prototypische Implementierung
Da in der PreferenceActivity Settings die einzelnen Preferences teilweise nicht einfach nur abgespeichert werden sollen, wird jede Eingabe des Nutzers ber die in Listing 6.42 dargestellte Methode onSharedPreferenceChanged berwacht und individuell darauf reagiert. So soll das Passwort nicht im Klartext abgespeichert werden. Wird das Passwort vom Nutzer gendert, wird es durch die Methode SpiritHelpers.encryptString verschlsselt und dann erst abgelegt. Hat der Nutzer sowohl eine FhsId als auch ein Passwort eingegeben, werden diese dem Task VerifyLoginDataTask zur Verizierung bergeben. In Listing 6.43 kann man sehen, dass der Task bisher nur auf eine fest vorgegebene Menge von FhsIds prft und die entsprechende Rolle zurckgibt. Dies liegt daran, dass der REST-Service zur Verizierung von Nutzerdaten noch nicht existiert. Dem Nutzer wird ber Toasts angezeigt, ob die Verizierung erfolgreich oder nicht erfolgreich war. Listing 6.42: Die PreferenceActivity Settings. (Teil 3) override def onSharedPreferenceChanged ( sharedPreferences : SharedPreferences , key : String ) { val pref = findPreference ( key ) . asInstanceOf [ Preference ] if ( pref . isInstanceOf [ EditTextPreference ]) { val etp = pref . asInstanceOf [ EditTextPreference ] if ( pref . getKey () . equals ( " editPassword " ) ) { val encryptedPass = SpiritHelpers . encryptString ( etp . getText ) etp . setText ( encryptedPass ) // Instant encryption pref . setSummary ( etp . getText () . replaceAll ( " . " , "*")); } else { pref . setSummary ( etp . getText () ) ; } if ( pref . getKey () . equals ( " editFhsId " ) || pref . getKey () . equals ( " editPassword " ) ) { val un = findPreference ( " editFhsId " ) . asInstanceOf [ EditTextPreference ]. getText val pwd = findPreference ( " editPassword " ) . asInstanceOf [ EditTextPreference ]. getText if (! un . equals ( " " ) && ! pwd . equals ( " " ) ) { val role = new VerifyLoginDataTask ( Settings . this ) . execute ( Array ( un , SpiritHelpers . decryptString ( pwd ) ) ) . get () if ( role . equals ( " " ) ) { val loggedIn = findPreference ( " loggedIn " ) . asInstanceOf [ CheckBoxPreference ] loggedIn . setChecked ( false ) SpiritHelpers . setPrefs ( this , " role " , " " , false ) loggedIn . setSummary ( getString ( R . string . string _ notLoggedIn ) ) } else { val loggedIn = findPreference ( " loggedIn " ) . asInstanceOf [ CheckBoxPreference ]
Sebastian Stallenberger
6. Prototypische Implementierung
loggedIn . setChecked ( true ) SpiritHelpers . setPrefs ( this , " role " , role . toString , false ) loggedIn . setSummary ( getString ( R . string . string _ loggedInAs ) + role . toString ) } } else { val loggedIn = findPreference ( " loggedIn " ) . asInstanceOf [ CheckBoxPreference ] loggedIn . setChecked ( false ) SpiritHelpers . setPrefs ( this , " role " , " " , false ) loggedIn . setSummary ( getString ( R . string . string _ enterUnAndPass ) ) }}}}} Nachdem nun dank der Rckgabe des Tasks die Rolle des Nutzers feststeht, wird diese und der Login-Status dem Nutzer nun angezeigt. Listing 6.43: Die doInBackground-Methode des Tasks VerifyLoginDataTask. override protected def doInBackground ( param : Array [ String ]) : String = { val username = param (0) if ( username . equals ( " braun " ) || username . equals ( " otto " ) || username . equals ( " knolle " ) ) { " professor " } else if ( username . equals ( " stallen3 " ) ) { " student " } else { " " }} Von der PreferenceActivity Settings kommt der Autor im folgenden Kapitel nun zur Activity Info, die fr die Anzeige einer Hilfe zustndig ist. Sie basiert auf einem FrameLayout, welches nur ein einziges Element enthalten darf. In diesem Fall ist das eine WebView18 .
Vgl. http://developer.android.com/reference/android/webkit/WebView.html
Sebastian Stallenberger
6. Prototypische Implementierung
In der onCreate-Methode der Activity wird die WebView zugnglich gemacht und anschlieend ihre Methode loadData aufgerufen. Als Parameter bekommt diese Methode drei Strings: Den anzuzeigenden Inhalt (data), den Datentyp (mimeType) und das Encoding. Der Inhalt (HTML-Seite) soll allerdings in res/raw (bzw. res/rawde) ausgelagert werden, da so die Mehrsprachigkeit sehr einfach umgesetzt werden kann19 . Listing 6.45: Der Inhalt der onCreate-Methode der Activity Info. setContentView ( R . layout . info ) val webView = findViewById ( R . id . infowebview ) . asInstanceOf [ WebView ] webView . loadData ( readTextFromResource ( R . raw . info ) , " text / html " , " UTF -8 " ) Hat man die HTML-Seiten in die raw-Ordner ausgelagert, muss man sie einlesen, um den entsprechenden String fr die WebView verwenden zu knnen. Dies erledigt die Methode readTextFromResource. Sie nimmt die Id der Ressource entgegen, liest diese aus und gibt den Inhalt als String zurck. Listing 6.46: Die Methode readTextFromResource. def readTextFromResource ( resourceID : Int ): String = { val raw = getResources () . openRawResource ( resourceID ) val stream = new ByteArrayOutputStream () var i: Int = 0 try { i = raw . read () while ( i != -1) { stream . write ( i ) i = raw . read () } raw . close () } catch { case e: IOException = > Log . e ( " ERROR " , e . getMessage . toString ) } stream . toString () } Das Anzeigen der Hilfe ist also nichts weiter als das Laden einer Website in einer WebView und ist daher einfach zu realisieren. Das in der Activity Info angewandte Verfahren wird auch von Anwendungen genutzt, mit denen einfache Android Applikationen per HTML und JavaScript programmiert werden knnen. Im folgenden Abschnitt werden die Toilers vorgestellt.
19
raw enthlt dann die englischsprachige Datei und raw-de die deutschsprachige. Je nachdem welche Sprache im System eingestellt ist, wird der entsprechende Ordner automatisch gewhlt.
Sebastian Stallenberger
6. Prototypische Implementierung
6.9 Toilers
Um die Funktionalitt mglichst von den Activities zu trennen, wurden die sogenannten Toilers (deutsch: Arbeiter) eingefhrt. Die wichtigsten Toilers sind hierbei: JsonProcessor, SpiritConnect, SpiritHttpClient, ViewBuilder und SpiritHelpers. Diese sollen in diesem Kapitel erlutert und ihre wichtigsten Methoden dargestellt werden.
6.9.1 JsonProcessor
Das Object JsonProcessor enthlt sowohl alle Case-Classes fr die Objekte, in denen die empfangenen Daten gelagert werden, als auch die Methoden, die zur Umwandlung der JSON-Strings in diese Objekte bentigt werden. In Listing 6.47 sind die gerade angesprochenen Case-Classes zu sehen. Laut [OSV08, 263-265] sind CaseClasses in Scala eine sehr einfache Mglichkeit, Pattern-Matching auf Objekte anzuwenden. Das Schlsselwort case fhrt dazu, dass der Compiler folgende drei Aktionen fr die Klasse durchfhrt: Er fgt eine Factory-Methode mit dem Namen der Klasse hinzu. Fr die Klasse entfllt also das Schlsselwort new. Er stellt vor die Argumente der Parameter-Liste jeweils das Schlsselwort val, welches die Parameter zu Feldern macht. Er fgt die Methoden toString, hashCode und equals hinzu, welche immer fr den ganzen Objekt-Baum gelten. [OSV08, 263-265] Listing 6.47: Die Case Classes im Objekt JSON-Processor. case class NewsList ( news : java . util . List [ News ]) case class News ( news _ id : Int , title : String , content : String , owner : Owner , expireDate : String , creationDate : String , lastModified : String , degreeClass : java . util . List [ DegreeClassExt ] , newsComment : java . util . List [ NewsComment ]) case class Owner ( fhs _ id : String , displayedName : String , memberType : String ) case class NewsComment ( comment _ id : Int , content : String , creationDate : String , owner : Owner )
Sebastian Stallenberger
6. Prototypische Implementierung
case class DegreeClassExt ( class _ id : Int , parent : Parent , title : String , classType : String , mail : String , subClasses : java . util . List [ DegreeClassExt ]) case class DegreeClassList ( degreeClass : java . util . List [ DegreeClassExt ]) case class Parent ( class _ id : Int , title : String ) Die einzelnen Methoden, in denen die JSON-Strings geparsed werden sind: json StringToNewsList, jsonStringToDegreeList und jsonStringToSingleNews. Anhand von jsonStringToSingleNews wird in Listing 6.48 gezeigt, wie der Vorgang des Parsens abluft. Listing 6.48: Die Methode jsonStringToSingleNews. def jsonStringToSingleNews ( jsonString : String ) = { var workJsonString = jsonString val gson = new Gson () . asInstanceOf [ Gson ] var gsonObject = gson . fromJson ( workJsonString , classOf [ NewsList ]) var newsJavaList = gsonObject . news . asInstanceOf [ java . util . List [ News ]] var newsScalaList = newsJavaList . toList newsScalaList } Als Parser wird derzeit der Java-Parser GSON von Google genutzt. Nach dem Erzeugen einer Instanz von GSON wird dieser der JSON-String und die KlassenBeschreibung von NewsList bergeben. Man erhlt von GSON eine Java-Liste zurck. Diese wird anschlieend in eine Scala-Liste umgewandelt und zurckgegeben. GSON ist ab API-Level 11 (Android 3.0.x) im Android-Framework als JsonReader20 enthalten. Neben GSON gibt es noch einige andere JSON-Parser, die sich fr diese Aufgabe eignen wrden. Programmiert man nativ in Scala, wre die erste Wahl lift-json aus dem Lift-Framework. Leider funktionierte diese Bibliothek nicht in Kombination mit Android. Eine Alternative wre die Scala Bibliothek Jerkson21 , welche auf dem JSON-Prozessor Jackson 22 basiert. Diese konnte auch nach Email-Kontakt mit dem Autor Coda Hale zum Implementations-Zeitpunkt nicht zum Laufen gebracht
20 21
Sebastian Stallenberger
6. Prototypische Implementierung
werden. Laut [Sha09, 36m53s] kann durch den Einsatz von Stream-Parsern statt Tree-Parsern unter Android Akku-Kapazitt gespart werden. Jackson ist wie GSON ein Stream-Parser und Jerkson wre somit eine ideale Wahl fr Scala.
6.9.2 SpiritHttpClient
Da der Server von Spirit HTTPS mit einem nicht verizierten Zertikat verwendet, muss statt des nativen HttpClient eine modizierte Version mit eigenem Keystore verwendet werden. Ohne diese ist die Verbindung nicht mglich. Listing 6.49: Die Klasse SpiritHttpClient. class SpiritHttpClient ( var context : Context ) extends DefaultHttpClient { override protected def createClientConnectionManager () : ClientConnectionManager = { val registry = new SchemeRegistry () registry . register ( new Scheme ( " http " , PlainSocketFactory . getSocketFactory , 80) ) registry . register ( new Scheme ( " https " , newSslSocketFactory () , 443) ) new SingleClientConnManager ( getParams , registry ) } private def newSslSocketFactory () : SocketFactory = { try { var trusted = KeyStore . getInstance ( " BKS " ) val in : InputStream = context . getResources . openRawResource ( R . raw . mykeystore ) try { trusted . load ( in , " mysecret " . toCharArray ) } finally { in . close () ; } val sf = new SSLSocketFactory ( trusted ) sf . setHostnameVerifier ( SSLSocketFactory . STRICT _ HOSTNAME _ VERIFIER ) sf } catch { case e: Exception = > throw new AssertionError ( e ) ; } } } Die Klasse SpiritHttpClient basiert auf dem DefaultHttpClient. In der berschriebenen Methode createClientConnectionManager wird festgelegt, dass Verbindungen, die ber den Post 443 stattnden, ber die SocketFactory aus newSslSocket Factory stattnden sollen. In der Methode newSslSocketFactory wird der Typ des KeyStore deniert und anschlieend ber getResources.openRawResource der im Ordner res/raw liegende KeyStore mykeystore als InputStream eingelesen. Nach-
Sebastian Stallenberger
6. Prototypische Implementierung
dem auch das Passwort mysecret bergeben wurde, wird die kongurierte SocketFactory mit dem eigenen Keystore bergeben. Das Zertikat wurde vorher von Hand vom Server geladen und in den KeyStore abgelegt. Mit dem SpiritHttpClient besteht nun die Mglichkeit, Verbindungen zum Spirit-Server aufzubauen.
6.9.3 SpiritConnect
Das Object SpiritConnect enthlt die Methoden, die fr den Verbindungsaufbau bzw. die Beschaung der JSON-Strings ntig sind. Die Basis-Methoden sind hierbei getJsonFromUrl und putJsonToUrl. In der Methode getJsonFromUrl wird zu Beginn kontrolliert, ob der StandaloneModus aktiviert ist, indem die Preference standalone ausgelesen wird. Ist dies der Fall, wird die Aufgabe, den JSON-String zu beschaen, an die Methode Standalone Ops.getJsonFromUrl weitergegeben. Diese gibt zu Testzwecken einen einfachen, xen JSON-String zurck. Ist der Standalone-Modus deaktiviert, wird die Verbindung ber den SpiritHttpClient aufgebaut. ber die setHeader-Methode von HttpGet wird dem Server mitgeteilt, ob man den String im JSON- oder XML-Format empfangen mchte. Nach dem Verbindungsaufbau wird die Antwort des Servers als String zurckgegeben. Listing 6.50: Die Methode getJsonFromUrl. def getJsonFromUrl ( context : Context , restUrl : String ): String = { var returnString = " " if ( SpiritHelpers . getBoolPrefs ( context . asInstanceOf [ Activity ] , " standalone " ) ) { try { returnString = StandaloneOps . getJsonFromUrl ( context , restUrl ) } catch { case e: Exception = > Log . e ( " EXCEPTION " , e . getMessage ) } } else { val basicHttpContext = new BasicHttpContext val spiritHttpClient = new SpiritHttpClient ( context ) val httpGet = new HttpGet ( restUrl ) httpGet . setHeader ( " Accept " , " application / json " ) try { val response = spiritHttpClient . execute ( httpGet , basicHttpContext ) val inputStream = response . getEntity . getContent returnString = SpiritHelpers . convertStreamToString ( inputStream ) } catch { case e: Exception = > Log . e ( " EXCEPTION " , e . getMessage ) }} returnString }
Sebastian Stallenberger
6. Prototypische Implementierung
In Listing 6.51 ist die Methode putJsonToUrl zu sehen. hnlich, wie in Listing 6.50 in der Methode getJsonFromUrl, wird auch hier eine Verbindung zum Spirit-Server aufgebaut. Allerdings kommt diesmal ein HttpPut-Objekt zum Einsatz. Zurck gibt diese Methode ebenfalls die Antwort des Servers. Listing 6.51: Die Methode putJsonToUrl. def putJsonToUrl ( context : Context , restUrl : String , putJson : String ): String = { var returnString = " " val basicHttpContext = new BasicHttpContext () val spiritHttpClient = new SpiritHttpClient ( context ) spiritHttpClient . getParams . setParameter ( CoreProtocolPNames . HTTP _ CONTENT _ CHARSET , HTTP . UTF _ 8) val httpPut = new HttpPut ( restUrl ) val stringEntity = new StringEntity ( putJson ) httpPut . setEntity ( stringEntity ) httpPut . setHeader ( " Accept " , " application / json " ) httpPut . setHeader ( " Content - Type " , " application / json " ) try { val response = spiritHttpClient . execute ( httpPut , basicHttpContext ) returnString = EntityUtils . toString ( response . getEntity ) } catch { case e: Exception = > Log . e ( " EXCEPTION " , e . getMessage ) } returnString } Um in SpiritMobile zu berprfen, ob eine Internet-Verbindung besteht, wurde die Methode checkInternetConnection genutzt, die in Listing 6.52 zu sehen ist. Listing 6.52: Die Methode checkInternetConnection. def checkInternetConnection ( context : Context ): Boolean = { val connectivityManager = context . getSystemService ( Context . CONNECTIVITY _ SERVICE ) . asInstanceOf [ ConnectivityManager ] if ( connectivityManager . getActiveNetworkInfo != null && connectivityManager . getActiveNetworkInfo . isAvailable && connectivityManager . getActiveNetworkInfo . isConnected ) { true } else { false } }
Sebastian Stallenberger
6. Prototypische Implementierung
Stellvertretend fr alle Methoden, die ber die Basis-Methoden agieren, wird nun getNews besprochen. Zu Beginn wird die URL23 zusammengesetzt und die Anfrage an den Server gestellt. Das Ergebnis wird anschlieend auf Strings geprft, die auf einen Fehler in der Abfrage hinweisen. Listing 6.53: Die Methode getNews. def getNews ( context : Context ): Boolean = { var returnBoolean = false val newsUrl = " https : // " + context . getString ( R . string . ipadress ) + ":" + context . getString ( R . string . port ) + " / fhs - spirit / news " val newsJsonString = SpiritConnect . getJsonFromUrl ( context , newsUrl ) . asInstanceOf [ String ] if ( newsJsonString ==" " || newsJsonString ==" Bad URI4 " || newsJsonString == " Bad " ) { returnBoolean = false } else { SpiritHelpers . saveString ( context , " newsJsonString " , newsJsonString ) SpiritHelpers . setLastNewsDate ( context . asInstanceOf [ Activity ]) returnBoolean = true } returnBoolean } War die Abfrage erfolgreich, wird der String als newsJsonString abgespeichert, das aktuelle Datum als Datum des letzten Updates gesetzt und true zurckgegeben. War die Abfrage nicht erfolgreich, wird false zurckgegeben. Die Methode createNews ist der gerade vorgestellten sehr hnlich und wird daher hier nicht gelistet. Der Basis-Methode wird hier allerdings ein vorher zusammengesetzter JSON-String bergeben, welchen diese an den Server schickt. Anschlieend wird wieder auf Erfolg geprft. Es gibt fr jede Kommunikation mit dem Server eine eigene Methode.
6.9.4 ViewBuilder
Das Object ViewBuilder beinhaltet die Methoden, die fr den Aufbau der diversen Views in SpiritMobile zustndig sind. Diese sind sich alle sehr hnlich. Teilweise wurde auf diese in den vorhergehenden Kapiteln schon eingegangen, weshalb eine erneute Behandlung an dieser Stelle vermieden werden soll.
23
Sebastian Stallenberger
6. Prototypische Implementierung
6.9.5 SpiritHelpers
Im Objekt SpiritHelpers sind Methoden ausgelagert, die an mehreren Stellen im Programm genutzt werden, aber nicht zu einer Kategorie zusammengefasst werden knnen. Die wichtigsten dieser Methoden werden in diesem Abschnitt erlutert. Zum Einzeiler convertStreamToString gibt es nicht viel zu sagen. Die Methode vereinfacht die Umwandlung eines InputStream zu einem String. Listing 6.54: Die Methode convertStreamToString. def convertStreamToString ( is : InputStream ): String = { scala . io . Source . fromInputStream ( is ) . getLines . mkString } In Listing 6.55 sind die Methoden loadString und saveString zu sehen. ber diese knnen einfach Strings in Textdateien gespeichert und aus Textdateien geladen werden. Listing 6.55: Die Methoden loadString und saveString. def loadString ( context : Activity , filename : String ): String = { var string = " " try { string = convertStreamToString ( context . openFileInput ( filename ) ) } catch { case e: Exception = > Log . e ( " EXCEPTION " , e . getMessage . toString ) } string } def saveString ( context : Context , filename : String , string : String ) { var fos : FileOutputStream = null try { fos = context . openFileOutput ( filename , Context . MODE _ PRIVATE ) fos . write ( string . getBytes ) } catch { case e: Exception = > Log . e ( " EXCEPTION " , e . getMessage ) } finally { if ( fos != null ) { fos . close () } } } Durch die bergabe von MODE_PRIVATE wird gewhrleistet, dass von anderen Applikationen auf die Strings nicht zugegrien werden kann. Eine hnliche Funktion haben die Funktionen setStringPrefs, getStringPrefs und getBoolPrefs. Allerdings erleichtern diese nicht den Zugri auf Textdateien im
Sebastian Stallenberger
6. Prototypische Implementierung
Datei-System sondern auf die SharedPreferences von Android. Die beiden Methoden fr das Schreiben und Lesen von Strings sind in Listing 6.56 zu sehen. Listing 6.56: Die Methoden setStringPrefs und getStringPrefs. def setStringPrefs ( activity : Activity , key : String , paravalue : String , encrypt : Boolean ) { val pref : SharedPreferences = PreferenceManager . getDefaultSharedPreferences ( activity ) val edit : Editor = pref . edit () var value = paravalue if ( encrypt ) { value = encryptString ( value ) } edit . putString ( key , value ) edit . commit () } def getStringPrefs ( activity : Activity , key : String , decrypt : Boolean ): String = { val context = activity . asInstanceOf [ Context ] PreferenceManager . setDefaultValues ( context , R . xml . preferences , false ) val pref : SharedPreferences = PreferenceManager . getDefaultSharedPreferences ( activity ) var value = pref . getString ( key , " N / A " ) if ( decrypt ) { value = decryptString ( value ) } value } Sie beschaen sich eine Referenz auf den PreferenceManager bzw. die DefaultSharedPreferences. ber den Booleschen Parameter encrypt bzw. decrypt kann die Verschlsselung bzw. Entschlsselung aktiviert werden. Diese wird dann von den Methoden encryptString bzw. decryptString ausgefhrt. Derzeit sind diese Methoden allerdings noch leer, da die Verschlsselungs-Algorithmen zu BuerOverowExceptions fhrten. ber die Methode getBoolPrefs bekommt man Boolesche Werte aus den Preferences, wie z.B. ob der Standalone-Modus aktiv oder nicht aktiv ist. Listing 6.57: Die Methode getBoolPrefs. def getBoolPrefs ( activity : Activity , key : String ): Boolean = { val context = activity . asInstanceOf [ Context ] PreferenceManager . setDefaultValues ( context , R . xml . preferences , false ) val pref : SharedPreferences = PreferenceManager . getDefaultSharedPreferences ( activity ) pref . getBoolean ( key , true ) }
Sebastian Stallenberger
6. Prototypische Implementierung
Sie bedient sich hierbei der gleichen Prinzipien wie die vorhergehenden Funktionen, greift allerdings mit getBoolean statt mit getString auf die Werte zu. In Listing 6.58 ist die Methode zu sehen, die fr das Setzen des aktuellen Datums als Datum des letzten Updates verantwortlich ist. Listing 6.58: Die Methode setLastNewsDate. def setLastNewsDate ( context : Context ) { val df : SimpleDateFormat = new SimpleDateFormat ( " dd . MM . yyyy HH : mm : ss " ) SpiritHelpers . setStringPrefs ( context . asInstanceOf [ Activity ] , " LastNewsDate " , df . format ( new Date () ) . toString + context . getString ( R . string . string _ oclock ) , false ) } In Abschnitt 6.6 wurde auf die Methode getDegreeMap verwiesen, die eine Liste von DegreeClassExt-Objekten in eine ListMap[String, Int] umwandelt. In Listing 6.59 ist diese zu sehen. Listing 6.59: Die Methode getDegreeMap. def getDegreeMap ( context : Context , list : List [ DegreeClassExt ] , prefix : String ): ListMap [ String , Int ] = { var degreeMap = new ListMap [ String , Int ] list . foreach ( element = > { val degreeClassExt = element . asInstanceOf [ DegreeClassExt ] var title = degreeClassExt . title if ( degreeClassExt . title . equals ( " AllClasses " ) ) { title = context . getString ( R . string . string _ all ) } if ( degreeClassExt . classType . equals ( " Group " ) ) { title = prefix + " - " + context . getString ( R . string . string _ group ) + " " + title } degreeMap += title -> degreeClassExt . class _ id if ( degreeClassExt . subClasses != null ) { degreeMap = degreeMap ++ getDegreeMap ( context , degreeClassExt . subClasses . toList , title ) } } ) degreeMap = ListMap ( degreeMap . toList . sortBy { _._1 }: _ *) degreeMap }
Sebastian Stallenberger
6. Prototypische Implementierung
Dazu werden fr alle Elemente des Parameters list der Name ausgelesen und geprft, ob ein Parent-Objekt vorhanden ist und ob es sich um eine Gruppe handelt. Hat es ein Parent-Objekt, wird der Name mit dem Prx NameDesParentObjects - erweitert. Ist das Element eine Gruppe, bekommt es als direkten Prx Gruppe . Anschlieend wird der Name zusammen mit der Id als Tupel in der ListMap abgelegt. Bevor die ListMap zurckgegeben wird, wird sie noch nach Name sortiert. Im aktuellen Kapitel wurden die wichtigsten Elemente aus der prototypischen Implementierung von SpiritMobile gezeigt. Im folgenden Kapitel 7 wird nun gezeigt, welche Voraussetzungen eine Applikation fr eine Verentlichung erfllen muss und wie dies im Android Market vor sich geht.
Sebastian Stallenberger
7 Distribution
Neben dem oziellen Android Market1 gibt es noch andere Mglichkeiten, die Applikation an den Nutzer zu bringen. So stellen alternative Markets2 wie z.B. AndroidPIT App Center3 oder SlideME4 eine Mglichkeit dar, die bei kostenpichtigen Apps sogar einen Vorteil bringen kann. Will man im oziellen Market eine kostenpichtige App erwerben, ist fr die Bezahlmethode Google Checkout aktuell eine Kreditkarte unabdingbar. Alternative Markets bieten neben Kreditkarte oft andere Zahlungsoptionen wie Paypal oder ClickandBuy an. Im Rahmen dieser Masterarbeit soll betrachtet werden, wie eine Applikation in dem Android Market bereitgestellt werden kann. Um eine App im oziellen Android Market anzubieten, bestehen gewisse Voraussetzungen. Soweit nicht anders angegeben gilt fr dieses Kapitel [Goo11h] als Quelle. 1. Der Entwickler muss ber seinen Google Account einen Publisher Account erstellen 5 . Dieser kostet einmalig 25$ und hat keine weiteren Folgekosten. 2. Die App muss mit einem gltigen Schlssel signiert sein. Das Ablaufdatum des Schlssels muss nach dem 22. Oktober 2033 liegen. 3. Es mssen folgende Attribute im <manifest>-Tag des Android Manifest deniert sein: android:versionCode und android:versionName. 4. Im <application>-Tag mssen android:icon und android:label deniert sein. Zustzlich zu den gerade genannten Voraussetzungen gibt es auf Android Developers6 eine ausfhrliche Checkliste zur Vorbereitung einer Verentlichung. SpiritMobile7 steht derzeit als Closed Beta zum Download ber den Market bereit. Der Weg dorthin soll nun kurz beschrieben werden. Wichtig ist es, das Attribut debug im Android Manifest auf false zu setzen. Das darauf hin ber SBT erzeugte *.apk-Paket ist unsigniert. Es bendet sich bei Nutzung von SBT mit android-plugin in <ProjektVerzeichnis>\target\scala_2.x.x. ber die Konsole kann man mit dem einfachen Befehl sbt prepare-market das Paket signieren. Das android-plugin geht dabei davon aus, dass der Keystore mit den SignaturDaten im Nutzerverzeichnis des Betriebssystems liegt und .keystore heisst. Der Pfad muss unter Umstnden angepasst werden. Danach muss zipalign darauf angewendet werden. Sollte dies fehlschlagen, knnen alle Schritte allerdings auch ma1 2
https://market.android.com Eine ausfhrliche Auistung gibt es unter http://de.wikipedia.org/wiki/Android_Market 3 http://www.androidpit.de/de/android/market/appcenter 4 http://slideme.org/ 5 Vgl. https://market.android.com/publish 6 Vgl. http://developer.android.com/guide/publishing/preparing.html 7 https://market.android.com/details?id=de.fhs.spirit
Sebastian Stallenberger
7. Distribution
nuell durchgefhrt werden. Eine Anleitung dafr steht im Kapitel A.1.6 im Anhang zur Verfgung. [Ber11] Das aus diesen Schritten resultierende Paket ist nun bereit fr den Upload in den Market. Nachdem man sich als Developer im Market8 eingeloggt hat, sieht man eine bersicht der bestehenden Projekte, falls vorhanden. Hier kann man sein gerade signiertes Paket hochladen und nach erfolgreicher berprfung mehr Informationen zur Applikation bereitstellen. Zu diesen Informationen gehren unter anderem: Inhalte Screenshots: Zwei Screenshots im PNG- oder JPG-Format sind Picht. Symbol (Icon): Ein Icon der Gre 512*512px. Werbegrak, Funktionsgrak, Werbevideo: Optionale zustzliche Graken und Videos. Details Sprache: Hier knnen mehrere Sprachen angegeben und eine Sprache als Default-Sprache ausgewhlt werden. Die folgenden mit x gekennzeichneten Punkte mssen jeweils fr jede Sprache ausgefllt werden. Titel (x): Der Titel der Applikation. Description (x): Die Beschreibung der Applikation. Recent Changes (x): nderungen seit der letzten Version. Promo Text (x) App-Typ: Auswahl zwischen Apps/Spiele. Kategorie: Die Kategorie, in der die Applikation gelistet werden soll. Verentlichungsoptionen Inhaltsbewertung: Einteilung der Applikation in eine Jugendschutz-Stufe. Preis: Der Preis fr die Applikation. Lnder: Lnder, in denen die Applikation verfgbar sein soll. App-Typ: Auswahl zwischen Apps/Spiele. Kategorie: Die Kategorie, in der die Applikation gelistet werden soll. Nachdem man diese Informationen angegeben hat, kann die App im Market verentlicht werden. Obwohl Google kein Review der Apps durchfhrt, kann dies durchaus eine Weile9 dauern. Wenn man seine App updaten will, ldt man das neue *.apk hoch, deaktiviert das alte *.apk und aktiviert das neue. Die Verteilung an die Gerte wird dann automatisch durchgefhrt. Der Nutzer erhlt eine Meldung, dass eine neue Version der App verfgbar sei und kann diese updaten. Seit neuestem ist es auch mglich, fr eine App mehrere *.apk-Pakete einzustellen. Dies kann sinnvoll sein, wenn sich zum Beispiel die App-Version fr Smartphones von der fr Tablets stark unterscheidet. Im folgenden Kapitel wird ein Fazit gezogen und ein Ausblick auf die mgliche Zukunft von SpiritMobile gegeben.
8 9
Sebastian Stallenberger
8 Fazit
Diese Arbeit hat gezeigt, dass es derzeit mglich ist, eine Android-Anwendung fast1 komplett in Scala zu schreiben. Allerdings fhlt sich die Entwicklung mit Java und Eclipse deutlich ausgereifter und produktiver an. Das liegt zum einen daran, dass das Android-SDK in Java geschrieben und fr Java optimiert wurde. Zum anderen ist Scala in Verbindung mit Android bisher ungengend in aktuelle Entwicklungsumgebungen integriert. Hier hat sich in den letzten Monaten allerdings einiges getan. Laut Meinung des Autors wird es auch zuknftig deutliche Fortschritte geben, da - verfolgt man Nutzer-Anfragen in Foren und Blog-Eintrge - die Entwicklung von Android-Anwendungen mit Scala immer mehr Interessenten hat. Entwickelt man mit Java eine Android-Applikation, so nutzt diese die Java-Libraries, die in Android vorhanden sind. Nutzt man Scala, wird auf die Scala-Libraries zurckgegrien, die nicht in Android vorhanden sind. Dies hat zur Folge, dass in Scala geschriebene Anwendungen mehr Speicherplatz bentigen, da die Scala-Libraries mit in das *.apk-Paket gepackt werden mssen. Der Einsatz von Proguard minimiert dieses Problem allerdings dahingehend, dass nur die Scala-Libraries gepackt werden, die auch bentigt werden. Der Unterschied im Speicherverbrauch wird somit vernachlssigbar klein. Ozielle Studien, die Java und Scala hinsichtlich der Performance vergleichen, liegen dem Autor nicht vor. Verentlicht wurde 2009 lediglich ein Testlauf von [Das09]. Dort schnitt Scala noch deutlich schlechter ab als Java. Es liegen leider keine Ergebnisse vor, die mit aktuellen Scala-Versionen erzielt wurden. Die Entwicklung in Scala macht nach Meinung des Autors wirklich Spa und Java erscheint einem in vielen Situationen pltzlich sehr umstndlich. Leider trben immer wieder auftretende Integrations-Probleme den Workow und daher ist leider derzeit von einem produktiven Einsatz noch abzuraten. Ideen fr die zuknftige Entwicklung von SpiritMobile gibt es viele. So wre die Entwicklung eines Authentizierung-Moduls in der REST-Schnittstelle und die dazu passende Implementierung einer Nutzerdaten-Verizierung in der Applikation ein wichtiger Punkt, der fr einen produktiven Einsatz der Applikation unabdingbar ist. Auch ein regelmiger Abruf der News im Hintergrund ber einen Service wre mglich und wnschenswert. Sollten neue Informationen vorliegen, knnte das dem Nutzer ber eine Status-Bar-Notication2 signalisiert werden. Des Weiteren knnte der Einsatz eines Widgets3 in Betracht gezogen werden. So htte der Nutzer direkt auf einem seiner Screens die letzten News im Blickfeld, ohne extra die Applikation starten zu mssen.
1 2
bis auf wenige Details, wie z.B. das Problem mit dem AsyncTask Vgl. http://developer.android.com/guide/topics/ui/notiers/notications.html 3 Vgl. http://developer.android.com/reference/android/widget/package-summary.html
Sebastian Stallenberger
8. Fazit
Denkt man noch einen Schritt weiter, knnte das Smartphone mit mobiler Applikation irgendwann den Studentenausweis ersetzen oder zumindest sinnvoll ergnzen. So knnte in Mensen damit gezahlt werden oder Studenten knnten bei Prfungen leichter erfasst und authentiziert werden. Fr neue Studenten knnten virtuelle Campus-Fhrungen stattnden oder es knnte per Augmented Reality gezeigt werden, wie der Campus frher einmal aussah. Auch Informationen zu einzelnen Gebuden, wie zum Beispiel sich hug wechselnde nungszeiten, knnten dem Studenten nach Abruf eines QR-Codes angezeigt werden. Alles in allem steckt noch sehr viel Potential in SpiritMobile, unabhngig davon, welches Betriebssystem man betrachtet. Mobile Applikationen werden einmal Grundvoraussetzung fr die einfache Kommunikation zwischen Studenten und Hochschulen sein. Sie erleichtern sowohl den Studenten das Studieren als auch den Hochschulen die Verwaltung.
Sebastian Stallenberger
Literaturverzeichnis
[ass11] assembla.com: scala-android-ant-snippet.txt. https://www.assembla. com/spaces/scala-ide/documents/ajkjugweOr34DAeJe5cbCb/ download/scala-android-ant-snippet.txt, Juli 2011. abgerufen am: 15.09.2011 [Ber11] Berkel, Jan: android-plugin Readme. https://github.com/jberkel/ android-plugin/blob/master/README.markdown, Juli 2011. abgerufen am: 17.08.2011 [BP10] Becker, Arno ; Pant, Marcus: Android 2, 2. Auage. dpunkt.verlag GmbH, 2010. Heidelberg [Bra11] Braun, Oliver: SCALA Objektfunktionale Programmierung, 1. Auage. Carl Hanser Verlag, 2011. www.hanser.de [Bre11] Brechtel, James: Building Android Apps with Scala - IntelliJ. http://nevercertain.com/blog/2011/02/18/ scala-android-intellij-win-part-3/, Februar 2011. abgerufen am: 15.08.2011 [Dar10] Darcey, Lauren: Working with NinePatch Stretchable Graphics in Android. http://www.developer.com/ws/other/article.php/3889086/ Working-with-NinePatch-Stretchable-Graphics-in-Android.htm, Juni 2010. abgerufen am: 11.08.2011 [Das09] Dashrath, Akshay: Results: Scala Vs Java. http://www. akshaydashrath.com/2009/11/results-scala-vs-java.html, November 2009. abgerufen am: 13.09.2011 [Goo11a] Google: Activity. http://developer.android.com/reference/ android/app/Activity.html, August 2011. abgerufen am: 28.08.2011 [Goo11b] Google: Android Debug Bridge. http://developer.android.com/ guide/developing/tools/adb.html, August 2011. abgerufen am: 11.08.2011 [Goo11c] Google: Application Fundamentals. http://developer.android. com/guide/topics/fundamentals.html, August 2011. abgerufen am: 07.09.2011 [Goo11d] Google: AsyncTask. http://developer.android.com/reference/ android/os/AsyncTask.html, August 2011. abgerufen am: 28.08.2011
Sebastian Stallenberger
80
Literaturverzeichnis
[Goo11e] Google: Installing the SDK. http://developer.android.com/sdk/ installing.html, Juni 2011. abgerufen am: 10.08.2011 [Goo11f] Google: Intent. http://developer.android.com/reference/ android/content/Intent.html, August 2011. abgerufen am: 31.08.2011 [Goo11g] Google: Platform Versions. http://developer.android.com/ resources/dashboard/platform-versions.html, August 2011. abgerufen am: 08.08.2011 [Goo11h] Google: Publishing on Android Market. http://developer.android. com/guide/publishing/publishing.html, August 2011. abgerufen am: 17.08.2011 [Goo11i] Google: SDK Tools. http://developer.android.com/guide/ developing/tools/index.html#tools-sdk, August 2011. abgerufen am: 11.08.2011 [Goo11j] Google: What is Android? http://developer.android.com/ guide/basics/what-is-android.html, August 2011. abgerufen am: 09.08.2011 [Guy09] Guy, Romain: Drawable mutations. http://www.curious-creature. org/2009/05/02/drawable-mutations/, Mai 2009. abgerufen am: 29.08.2011 [IDC11] IDC: Press Release. http://www.idc.com/getdoc.jsp?containerId= prUS22871611, Juni 2011. abgerufen am: 09.08.2011 [jso11] json.org: Introducing JSON. http://www.json.org/, August 2011. abgerufen am: 24.08.2011 [lan08] lang.org scala: A Tour of Scala. http://www.scala-lang.org/node/ 104, Juli 2008. abgerufen am: 13.08.2011 [OSV08] Odersky, Martin ; Spoon, Lex ; Venners, Bill: Programming in Scala. Artima Press, 2008 [S11a] Srensen, Carsten E.: CreateNewScalaProject. https://code.google. com/p/treeshaker/wiki/CreateNewScalaProject, August 2011. abgerufen am: 15.08.2011 [S11b] Srensen, Carsten E.: SettingUpEclipse. https://code.google.com/ p/treeshaker/wiki/CreateNewScalaProject, August 2011. abgerufen am: 15.08.2011 [Sha09] Sharkey, Google Developer Conference 2009 J.: Coding for Life Battery Life, That Is. http://www.google.com/intl/de-DE/events/io/ 2009/sessions/CodingLifeBatteryLife.html, Mai 2009. abgerufen am: 07.09.2011
Sebastian Stallenberger
81
Literaturverzeichnis
[Sil11] Silva, Nelson: Scala on Android 101 - Proguard, XmlParser and Function2AsyncTask. http://blog.nelsonsilva.eu/2009/10/31/ scala-on-android-101-proguard-xmlparser-and-function2asynctask, August 2011. abgerufen am: 28.08.2011 [Tom11] Tomshardware.de: Googles Android 4 kommt wahrscheinlich im Oktober. http://www.tomshardware.de/ Ice-Cream-Sandwich-Mango-iPhone-5-android-2.4-WP7, news-246059.html, August 2011. abgerufen am: 11.08.2011 [Wik11] Wikipedia: Android (Betriebssystem). http://de.wikipedia.org/ wiki/Android_%28Betriebssystem%29, August 2011. abgerufen am: 09.08.2011
Sebastian Stallenberger
82
A Anhang
A.1 Tutorials fr die Einrichtung von Entwicklungsumgebungen
Hier nden sich Ergnzungen zum Kapitel 3 und ausfhrlichere Schritt-fr-SchrittAnleitungen zur Einrichtung von den Entwicklungsumgebungen mit Scala und Android.
engl. System Properties->System->Advanced System Setting-> Environment Variables falls vorhanden, bitte abgetrennt durch ein Semikolon erweitern
Sebastian Stallenberger
83
A. Anhang
A.1.3 Konsole
Fr die Nutzung des Scala-Android-Shellscripts unter Windows muss man folgende Schritte durchfhren, um das Script in eine ausfhrbare Scala-Datei umzuwandeln: 1. Datei create_project in einem Texteditor nen und die ersten drei Zeilen mit // auskommentieren. Anschlieend speichern und schlieen. 2. In der Konsole mit scala -deprecation create_project SpiritMobile de.fhs.spirit Script ausfhren Dies erzeugt im script-Ordner ein neues Verzeichnis namens SpiritMobile, das ein Android-Scala-Dummy-Projekt beinhaltet. Die Verzeichnisstruktur folgt hierbei Maven Konventionen. Anschlieend wird das Verzeichnis manuell in C:\scalandroid\ CODE kopiert.
A.1.4 Eclipse
In diesem Abschnitt werden fehlerhafte Methoden vorgestellt, Eclipse mit Android und Scala zum Laufen zu bringen. Der erste Versuch sollte durch Umwandlung der SBT-Konguration in ein EclipseProjekt mit SbtEclipsify3 geschehen. Leider schlug dies fehl, aber der Autor stie auf eine andere Variante4 mittels ANT. Voraussetzung dafr ist ein vorkonguriertes Eclipse. Dabei geht man von einem sauberen Eclipse Classic aus. Installiert als Plugins wurden: Scala IDE for Eclipse 2.0.0 beta 3 for Scala 2.9.0.RC3 ADT Plugin for Eclipse5 Bei der Installation des ADT Plugins kann ein Fehler auftreten, der wie folgt lautet: Listing A.2: Mgliche Fehlermeldung bei der Installation des ADT Plugins. Cannot complete the install because one or more required items could not be found . Software being installed : Android Development Tools 10.0.1. v201103111512 -110841 ( com . android . ide . eclipse . adt . feature . group 10.0.1. v201103111512 -110841) Missing requirement : Android Development Tools 10.0.1. v201103111512 -110841 ( com . android . ide . eclipse . adt . feature . group 10.0.1. v201103111512 -110841) requires org . eclipse . gef 0.0.0 but it could not be found Sollte dies der Fall sein, kann das Problem durch eine Installation des Plugins GEF SDK6 gelst werden. Auerdem fehlt bei der blanken Eclipse-Version die Update-Site http://download.eclipse.org/releases/helios. Diese sollte unter dem Namen Helios hinzugefgt werden.
3 4
Sebastian Stallenberger
84
A. Anhang
In den Preferences muss der Pfad zum Android SDK angegeben werden. Anschlieend erstellen wir ein neues Projekt SpiritMobile in C:\scalandroid\CODE\Spirit Mobile_Eclipse2. SpiritMobile dient auch als Application name. Als Package name wurde de.fhs.spiritmobile, als Activity main und als Min SDK-Version 8 gewhlt. Nach einem Klick auf Next wird der Haken fr die Erstellung eines Test-Projekts gesetzt und als Pfad C:\scalandroid\CODE\SpiritMobile_Eclipse2Test angegeben. Danach erstellt man die ANT-Files fr das Projekt in der Konsole. Dazu navigiert man in das Projektverzeichnis SpiritMobile_Eclipse2\src\main (da darin die Datei AndroidManifest.xml liegt) und fhrt auf folgenden Befehl aus: android update project -target 7 -path . 7 Dies erzeugt die Dateien build.properties, default.properties und build.xml im Projekt-Verzeichnis. In der Datei build.xml ersetzt man die Zeile <setup /> mit dem Code aus dem angehngten Kapitel A.2. Unter Windows muss die Zeile -injars ${out.classes. absolute.dir}:tools/ in -injars ${out.classes.absolute.dir};tools/ umgeschrieben werden. Im nchsten Schritt erzeugt man ein Verzeichnis tools im Projektordner. Dort hinein werden kopiert: proguard.rar8 retrace.rar9 scala-compiler.rar10 scala-library.jar
Um das Ant Script starten zu knnen muss noch der Ant-Pfad11 zur Systemvariable Path hinzugefgt werden. Eventuell muss vorher noch die neue Version von Ant heruntergeladen werden, da das Android Ant-System erst ab Ant version 1.8 luft. Gestartet werden sollte der Build-Prozess mit ant install. An dieser Stelle trat der Fehler aaptexec doesnt support the basename attribute mit dem packagingTool aaptexec auf. Bei der Suche nach einer Lsung fr dieses Problem stie der Autor auf das treeshaker-Projekt, welches im Kapitel 3.3 erlutert wird.
A.1.5 IntelliJ
Das hier beschriebene Vorgehen bezieht sich auf die android-plugin-Version vom Mrz 2011 und kann deshalb von aktuellen Anleitungen abweichen. Fr die aktuelle Vorgehensweise informieren Sie sich bitte auf der Website12 von android-plugin. Voraussetzung ist eine aktuelle IntelliJ-Installation und die Plugins Scala, SBT und Android. Das IntelliJ-Projekt basiert auf dem im Kapitel 3.2 erstellten SBT-Projekt mithilfe des android-plugin. Die Quelle fr dieses Kapitel ist [Bre11].
Hierbei steht die 7 fr die Android 2.2 Plattform ohne Google API. Die verfgbaren Plattformen knnen ber android list targets abgerufen werden. 8 http://sourceforge.net/projects/proguard/les/ 9 Ebenfalls im Proguard-Download enthalten 10 Aus dem Ordner C:\scalandroid\scala-2.8.1.nal\lib kopiert 11 <eclipse-Pfad>\plugins\org.apache.ant_<versionsnummer>\bin 12 https://github.com/jberkel/android-plugin
7
Sebastian Stallenberger
85
A. Anhang
1. In IntelliJ neues Java-Projekt aus vorhandenen Dateien erzeugen. Hierbei das Verzeichnis des vorher erzeugten android-plugin-Projekts angeben. IntelliJ ndet in $project_path/src/main/java source les. Im Fall des Autors wurden die Dateien in $project_path/src_managed/main/java gefunden, was auf den Versions-Fehler mit dem android-plugin im Abschnitt Konsole zurckzufhren ist. 2. Nach einem Klick auf Next sieht man die Libraries-Liste. Die library lib wird in scala_2.9.0 umbenannt. 3. Im nchsten Schritt benennt man das Modul Main in den Projektnamen, also in SpiritMobile um. 4. Als Facet whlt man als nchstes Android aus und beendet den Dialog mit Klick auf Finish. Das Projekt wird nun erstellt. Am Projektbaum mssen folgende nderungen vorgenommen werden. 5. Rechtsklick auf den Scala-Ordner und als Source Root markieren. Im Fall des Autors existierte dieser nicht im Baum. Das Kopieren der Ordner main und test aus dem Ordner src in den Ordner src_managed lste dieses Problem. Zum Scala-Ordner wird jetzt noch das Package de.fhs.spirit hinzugefgt. 6. Nachdem die Datei Activity.scala in MainActivity.scala umbenannt wurde, wird diese in das gerade erstellte Package verschoben. Dies war die letzte nderung am Dateibaum. 7. Rechtsklick auf das Projekt und Open Module Settings whlen. Hier whlt man Facets, Android und danach Compiler 8. In der Sektion AAPT Compiler ndert man den Pfad $project_path/src/main/ gen zu $project_path/src/main/java 9. In der Sektion Android Packaging Compiler wird der APK-Pfad in $project_path/target/scala_2.8.1/myproject_2.8.1-0.1.apk gendert. 10. Wenn noch nicht vorhanden, sollte unter Dependencies die im Schritt 3 umbenannte scala_2.9.0 hinzugefgt und deren Scope auf Provided gendert werden. Das waren die nderungen in den Module Settings. 11. In File/Settings im Abschnitt SBT wird der Pfad zur SBT launcher JAR le in C:\scalandroid\sbt-launch-0.7.7.jar gendert. Das waren die nderungen, die ntig sind, um das Projekt in IntelliJ einzubinden. Um es allerdings ber Run starten zu knnen, muss noch eine Run-Konguration erstellt werden, die SBT nutzt. 1. Run/Edit Congurations 2. ber das + in der linken, oberen Ecke erstellt man eine neue Android application und ndert den Namen von Unnamed in DeployRun. 3. Man whlt aus dem Module drop down das Projekt aus und deaktiviert Make. 4. Dafr aktiviert man Run Sbt Action, trgt die Action package-debug ein und besttigt die Dialoge jeweis mit OK. Jetzt kann man das Projekt ber Run ausfhren lassen. Das erstellte Paket wird automatisch auf den Emulator kopiert und gestartet. Das altbekannte hello, world
Sebastian Stallenberger
86
A. Anhang
sollte auf dem Bildschirm erscheinen. Bei dieser Methode kann es vorkommen, dass folgende Exception auftritt: Listing A.3: Mgliche Fehlermeldung bei der Installation des android-plugin. [ info ] Compiling main sources ... [ error ] C :\ scalandroid \ CODE \ SpiritMobile - IntelliJ \ src_managed \ main \ java \ de \ fhs \ spirit \ R . java :10: R is already defined as object R [ error ] public final class R { [ error ] ^ [ error ] one error found Dieses Problem kommt daher, dass die R.java doppelt erzeugt wird. Sowohl im Ordner src/main/java als auch in src_managed\main\java. Um dieses Problem zu umgehen, kann in IntelliJ der Ordner src_managed aus dem Projekt ausgeschlossen werden. Sollte auch dies nicht helfen, kann man einfach die R.java im Ordner src/main/java vor jedem Compilieren gelscht werden.
13
Sebastian Stallenberger
87
A. Anhang
A.2 Code
In diesem Kapitel ndet sich Code, der nicht unmittelbar zu SpiritMobile gehrt. Der folgende Code kommt aus der Quelle [ass11]. Listing A.4: Der Inhalt der buildExtension. < setup import =" false " / > <! -- Custom tasks -- > < taskdef name =" aaptexec " classname =" com . android . ant . AaptExecLoopTask " classpathref =" android . antlibs " / > < taskdef name =" apkbuilder " classname =" com . android . ant . ApkBuilderTask " classpathref =" android . antlibs " / > < taskdef name =" xpath " classname =" com . android . ant . XPathTask " classpathref =" android . antlibs " / > <! -- Properties -- > <! -- Tells adb which device to target . You can change this from the command line by invoking " ant - Dadb . device . arg = -d " for device " ant - Dadb . device . arg = -e " for the emulator . -- > < property name =" adb . device . arg " value =" " / > < property name =" android . tools . dir " location =" ${ sdk . dir }/ tools " / > <! -- Name of the application package extracted from manifest file -- > < xpath input =" AndroidManifest . xml " expression =" / manifest /@ package " output =" manifest . package " / > <! -- Input directories -- > < property name =" source . dir " value =" src " / > < property name =" source . absolute . dir " location =" ${ source . dir } " / > < property name =" gen . dir " value =" gen " / > < property name =" gen . absolute . dir " location =" ${ gen . dir } " /> < property name =" resource . dir " value =" res " / > < property name =" resource . absolute . dir " location =" ${ resource . dir } " / > < property name =" asset . dir " value =" assets " / > < property name =" asset . absolute . dir " location =" ${ asset . dir } " / > <! -- Directory for the third party java libraries -- > < property name =" external . libs . dir " value =" libs " / >
Sebastian Stallenberger
88
A. Anhang
< property name =" external . libs . absolute . dir " location =" ${ external . libs . dir } " / > <! -- Directory for the native libraries -- > < property name =" native . libs . dir " value =" libs " / > < property name =" native . libs . absolute . dir " location =" ${ native . libs . dir } " / > <! -- Output directories -- > < property name =" out . dir " value =" bin " / > < property name =" out . absolute . dir " location =" ${ out . dir } " /> < property name =" out . classes . dir " value =" ${ out . absolute . dir }/ classes " / > < property name =" out . classes . absolute . dir " location =" ${ out . classes . dir } " / > <! -- Intermediate files -- > < property name =" dex . file . name " value =" classes . dex " / > < property name =" intermediate . dex . file " location =" ${ out . absolute . dir }/${ dex . file . name } " / > <! -- The final package file to generate -- > < property name =" out . debug . unaligned . package " location =" ${ out . absolute . dir }/${ ant . project . name } - debug - unaligned . apk " / > < property name =" out . debug . package " location =" ${ out . absolute . dir }/${ ant . project . name } - debug . apk " / > < property name =" out . unsigned . package " location =" ${ out . absolute . dir }/${ ant . project . name } - unsigned . apk " / > < property name =" out . unaligned . package " location =" ${ out . absolute . dir }/${ ant . project . name } - unaligned . apk " / > < property name =" out . release . package " location =" ${ out . absolute . dir }/${ ant . project . name } - release . apk " / > <! -- Verbosity -- > < property name =" verbose " value =" false " / > <! -- This is needed by emma as it uses multilevel verbosity instead of simple true or false The property verbosity is not user configurable and depends exclusively on verbose value . -- > < condition property =" verbosity " value =" verbose " else =" quiet " > < istrue value =" ${ verbose } " / > </ condition > <! -- This is needed to switch verbosity of zipalign and aapt . Depends exclusively on verbose -- > < condition property =" v . option " value =" -v " else =" " >
Sebastian Stallenberger
89
A. Anhang
< istrue value =" ${ verbose } " / > </ condition > <! -- This is needed to switch verbosity of dx . Depends exclusively on verbose -- > < condition property =" verbose . option " value =" -- verbose " else =" " > < istrue value =" ${ verbose } " / > </ condition > <! -- Tools -- > < condition property =" exe " value =" . exe " else =" " > < os family =" windows " / > </ condition > < property name =" adb " location =" ${ android . tools . dir }/ adb ${ exe } " / > < property name =" zipalign " location =" ${ android . tools . dir }/ zipalign ${ exe } " / > <! -- Emma configuration -- > < property name =" emma . dir " value =" ${ sdk . dir }/ tools / lib " /> < path id =" emma . lib " > < pathelement location =" ${ emma . dir }/ emma . jar " / > < pathelement location =" ${ emma . dir }/ emma _ ant . jar " /> </ path > < taskdef resource =" emma _ ant . properties " classpathref =" emma . lib " / > <! -- End of emma configuration -- > <! -- Macros -- > <! -- Configurable macro , which allows to pass as parameters output directory , output dex filename and external libraries to dex ( optional ) -- > < macrodef name =" dex - helper " > < element name =" external - libs " optional =" yes " / > < element name =" extra - parameters " optional =" yes " / > < sequential > < echo > Converting compiled files and external libraries into ${ intermediate . dex . file }... </ echo > < apply executable =" ${ dx } " failonerror =" true " parallel =" true " > < arg value =" -- dex " / > < arg value =" -- output = ${ intermediate . dex . file } " /> < extra - parameters / > < arg line =" ${ verbose . option } " / > < arg path =" ${ out . absolute . dir }/ classes . min . jar " /> < fileset dir =" ${ external . libs . absolute . dir } " includes =" *. min . jar " / > < external - libs / >
Sebastian Stallenberger
90
A. Anhang </ apply > </ sequential > </ macrodef >
<! -- This is macro that enable passing variable list of external jar files to ApkBuilder Example of use : < package - helper > < extra - jars > < jarfolder path =" my _ jars " / > < jarfile path =" foo / bar . jar " / > < jarfolder path =" your _ jars " / > </ extra - jars > </ package - helper > -- > < macrodef name =" package - helper " > < attribute name =" sign . package " / > < element name =" extra - jars " optional =" yes " / > < sequential > < apkbuilder outfolder =" ${ out . absolute . dir } " resourcefile =" ${ ant . project . name }. ap _ " apkfilepath =" ${ out . debug . unaligned . package } " debug =" ${ build . debug } " abifilter =" ${ filter . abi } " verbose =" ${ verbose } " hascode =" true " > < dex path =" ${ intermediate . dex . file } " / > < sourcefolder path =" ${ source . absolute . dir }"/> < sourcefolder refid =" android . libraries . src " / > < jarfolder path =" ${ external . libs . absolute . dir } " / > < jarfolder refid =" android . libraries . libs " /> < nativefolder path =" ${ native . libs . absolute . dir } " / > < nativefolder refid =" android . libraries . libs " / > < extra - jars / > </ apkbuilder > </ sequential > </ macrodef > <! -- This is macro which zipaligns in . package and outputs it to out . package . Used by targets debug , -debug - with - emma and release . -- > < macrodef name =" zipalign - helper " > < attribute name =" in . package " / > < attribute name =" out . package " / > < sequential >
Sebastian Stallenberger
91
A. Anhang
< echo > Running zip align on final apk ... </ echo > < exec executable =" ${ zipalign } " failonerror =" true " > < arg line =" ${ v . option } " / > < arg value =" -f " / > < arg value =" 4 " / > < arg path ="@{ in . package } " / > < arg path ="@{ out . package } " / > </ exec > </ sequential > </ macrodef > <! -- This is macro used only for sharing code among two targets , - install and - install - with - emma which do exactly the same but differ in dependencies -- > < macrodef name =" install - helper " > < sequential > < echo > Installing ${ out . debug . package } onto default emulator or device ... </ echo > < exec executable =" ${ adb } " failonerror =" true " > < arg line =" ${ adb . device . arg } " / > < arg value =" install " / > < arg value =" -r " / > < arg path =" ${ out . debug . package } " / > </ exec > </ sequential > </ macrodef > <! -- Rules -- > <! -- Creates the output directories if they don t exist yet . --> < target name =" - dirs " > < echo > Creating output directories if needed ... </ echo > < mkdir dir = "${ resource . absolute . dir }" / > < mkdir dir = "${ external . libs . absolute . dir }" / > < mkdir dir = "${ gen . absolute . dir }" / > < mkdir dir = "${ out . absolute . dir }" / > < mkdir dir = "${ out . classes . absolute . dir }" / > </ target > <! - - Generates the R . java file for this project s resources . -- > < target name =" - resource - src " depends =" - dirs " > < echo > Generating R . java / Manifest . java from the resources ... </ echo > < exec executable =" ${ aapt } " failonerror =" true " > < arg value =" package " / > < arg line =" ${ v . option } " / > < arg value =" -m " / > < arg value =" -J " / > < arg path =" ${ gen . absolute . dir } " / >
Sebastian Stallenberger
92
A. Anhang < arg < arg < arg < arg < arg < arg </ exec > </ target >
Fachhochschule Schmalkalden SS 2011 value =" -M " / > path =" AndroidManifest . xml " / > value =" -S " / > path =" ${ resource . absolute . dir } " / > value =" -I " / > path =" ${ android . jar } " / >
<! -- Generates java classes from . aidl files . -- > < target name =" - aidl " depends =" - dirs " > < echo > Compiling aidl files into Java classes ... </ echo > < apply executable =" ${ aidl } " failonerror =" true " > < arg value =" -p ${ android . aidl } " / > < arg value =" -I ${ source . absolute . dir } " / > < arg value =" -o ${ gen . absolute . dir } " / > < fileset dir =" ${ source . absolute . dir } " > < include name =" **/*. aidl " / > </ fileset > </ apply > </ target > <! -- Compiles this project s . java files into . class files . --> < target name =" compile " depends =" - resource - src , - aidl " description =" Compiles project s . java files into . class files " > <! - - If android rules are used for a test project , its classpath should include tested project s location --> < condition property =" extensible . classpath " value =" ${ tested . project . absolute . dir }/ bin / classes " else ="." > < isset property =" tested . project . absolute . dir " /> </ condition > < javac encoding =" ascii " target =" 1.5 " debug =" true " extdirs =" " destdir =" ${ out . classes . absolute . dir }" bootclasspathref =" android . target . classpath " verbose =" ${ verbose }" classpath =" ${ extensible . classpath }" > < src path =" ${ source . absolute . dir }" / > < src path =" ${ gen . absolute . dir }" / > < classpath > < fileset dir =" ${ external . libs . absolute . dir } " includes =" *. jar " / > </ classpath > </ javac > < taskdef resource =" scala / tools / ant / antlib . xml " classpath =" tools / scala - compiler . jar : tools / scala -
Sebastian Stallenberger
93
A. Anhang
library . jar " / > < scalac force =" changed " deprecation =" on " destdir =" ${ out . classes . absolute . dir }" bootclasspathref =" android . target . classpath " > < src path =" ${ source . absolute . dir }" / > < src path =" ${ gen . absolute . dir }" / > < classpath > < fileset dir =" ${ external . libs . absolute . dir }" includes =" *. jar " / > < fileset dir =" tools " includes =" *. jar " / > </ classpath > </ scalac > </ target > < target name =" proguard " depends =" compile " > < taskdef resource =" proguard / ant / task . properties " classpath =" tools / proguard . jar " / > < proguard > - injars ${ out . classes . absolute . dir }: tools / scala library . jar (! META - INF / MANIFEST . MF ,! library . properties ) - outjars " ${ out . absolute . dir }/ classes . min . jar " - libraryjars " ${ android . jar }" - dontwarn - dontoptimize - dontobfuscate - keep public class * extends android . app . Activity </ proguard > </ target > <! - - Converts this project s . class files into . dex files --> < target name =" -dex " depends =" proguard " > <dex - helper / > </ target > <! - - Puts the project s resources into the output package file This actually can create multiple resource package in case Some custom apk with specific configuration have been declared in default . properties . --> < target name =" - package - resources " > < echo > Packaging resources </ echo > < aaptexec executable =" ${ aapt }" command =" package " manifest =" AndroidManifest . xml " resources =" ${ resource . absolute . dir }" assets =" ${ asset . absolute . dir }" androidjar =" ${ android . jar }" outfolder =" ${ out . absolute . dir }"
Sebastian Stallenberger
94
A. Anhang
Fachhochschule Schmalkalden SS 2011 basename =" ${ ant . project . name }" / >
</ target > <! - - Packages the application and sign it with a debug key . --> < target name =" - package - debug - sign " depends =" -dex , package - resources " > < package - helper sign . package =" true " / > </ target > <! - - Packages the application without signing it . --> < target name =" - package -no - sign " depends =" -dex , - package - resources " > < package - helper sign . package =" false " / > </ target > < target name =" - compile - tested -if - test " if =" tested . project . dir " unless =" do . not . compile . again " > < subant target =" compile " > < fileset dir =" ${ tested . project . absolute . dir }" includes =" build . xml " / > </ subant > </ target > <! - - Builds debug output package , provided all the necessary files are already dexed --> < target name =" debug " depends =" - compile - tested -if - test , - package - debug - sign " description =" Builds the application and signs it with a debug key ." > < zipalign - helper in . package =" ${ out . debug . unaligned . package }" out . package =" ${ out . debug . package }" / > < echo > Debug Package : ${ out . debug . package } </ echo > </ target > < target name =" - release - check " > < condition property =" release . sign " > <and > < isset property =" key . store " / > < isset property =" key . alias " / > </ and > </ condition > </ target > < target name =" - release - nosign " depends =" - release - check " unless =" release . sign " > < echo > No key . store and key . alias properties found in build . properties . </ echo > < echo > Please sign ${ out . unsigned . package } manually </ echo > < echo > and run zipalign from the Android SDK tools . </ echo >
Sebastian Stallenberger
95
< target name =" release " depends =" - package -no - sign , release - nosign " if =" release . sign " description =" Builds the application . The generated apk file must be signed before it is published ." > <! - - Gets passwords --> < input message =" Please enter keystore password ( store : ${ key . store }) :" addproperty =" key . store . password " / > < input message =" Please enter password for alias ${ key . alias } :" addproperty =" key . alias . password " / > <! - - Signs the APK --> < echo > Signing final apk ... </ echo > < signjar jar =" ${ out . unsigned . package }" signedjar =" ${ out . unaligned . package }" keystore =" ${ key . store }" storepass =" ${ key . store . password }" alias =" ${ key . alias }" keypass =" ${ key . alias . password }" verbose =" ${ verbose }" / > <! - - Zip aligns the APK --> < zipalign - helper in . package =" ${ out . unaligned . package }" out . package =" ${ out . release . package }" / > < echo > Release Package : ${ out . release . package } </ echo > </ target > < target name =" install " depends =" debug " description =" Installs / reinstalls the debug package onto a running emulator or device . If the application was previously installed , the signatures must match ." > < install - helper / > </ target > < target name =" - uninstall - check " > < condition property =" uninstall . run " > < isset property =" manifest . package " / > </ condition > </ target >
Sebastian Stallenberger
96
A. Anhang
< target name =" - uninstall - error " depends =" - uninstall check " unless =" uninstall . run " > < echo > Unable to run ant uninstall , manifest . package property is not defined . </ echo > </ target > <! - - Uninstalls the package from the default emulator / device --> < target name =" uninstall " depends =" - uninstall - error " if = " uninstall . run " description =" Uninstalls the application from a running emulator or device ." > < echo > Uninstalling ${ manifest . package } from the default emulator or device ... </ echo > < exec executable =" ${ adb }" failonerror =" true " > < arg line =" ${ adb . device . arg }" / > < arg value =" uninstall " / > < arg value =" ${ manifest . package }" / > </ exec > </ target > < target name =" clean " description =" Removes output files created by other targets ." > < delete dir =" ${ out . absolute . dir }" verbose =" ${ verbose }" / > < delete dir =" ${ gen . absolute . dir }" verbose =" ${ verbose }" / > </ target > <! - - Targets for code - coverage measurement purposes , invoked from external file --> <! - - Emma - instruments tested project classes ( compiles the tested project if necessary ) and writes instrumented classes to ${ instrumentation . absolute . dir }/ classes --> < target name =" -emma - instrument " depends =" compile " > < echo > Instrumenting classes from ${ out . absolute . dir }/ classes ... </ echo > <! - - It only instruments class files , not any external libs --> < emma enabled =" true " > < instr verbosity =" ${ verbosity }" mode =" overwrite " instrpath =" ${ out . absolute . dir }/ classes " outdir =" ${ out . absolute . dir }/ classes " > </ instr > <! - - TODO : exclusion filters on R *. class and allowing custom exclusion from user defined file --> </ emma > </ target >
Sebastian Stallenberger
97
A. Anhang
< target name =" -dex - instrumented " depends =" -emma instrument " > <dex - helper > < extra - parameters > < arg value =" --no - locals " / > </ extra - parameters > < external - libs > < fileset file =" ${ emma . dir }/ emma _ device . jar " / > </ external - libs > </ dex - helper > </ target > <! - - Invoked from external files for code coverage purposes --> < target name =" - package - with - emma " depends =" -dex instrumented , - package - resources " > < package - helper sign . package =" true " > < extra - jars > <! - - Injected from external file --> < jarfile path =" ${ emma . dir }/ emma _ device . jar " /> </ extra - jars > </ package - helper > </ target > < target name =" - debug - with - emma " depends =" - package - with emma " > < zipalign - helper in . package =" ${ out . debug . unaligned . package } " out . package =" ${ out . debug . package } " / > </ target > < target name =" - install - with - emma " depends =" - debug - with emma " > < install - helper / > </ target > <! - - End of targets for code - coverage measurement purposes --> < target name =" help " > <! - - displays starts at col 13 |13 80| --> < echo > Android Ant Build . Available targets : </ echo > < echo > help : Displays this help . </ echo > < echo > clean : Removes output files created by other targets . </ echo > < echo > compile : Compiles project s . java files into . class files . </ echo > < echo > debug : Builds the application and signs it with a debug key . </ echo >
Sebastian Stallenberger
98
A. Anhang
< echo > release : Builds the application . The generated apk file must be </ echo > < echo > signed before it is published . </ echo > < echo > install : Installs / reinstalls the debug package onto a running </ echo > < echo > emulator or device . </ echo > < echo > If the application was previously installed , the </ echo > < echo > signatures must match . </ echo > < echo > uninstall : Uninstalls the application from a running emulator or </ echo > < echo > device . </ echo > </ target >
A.3 Icons
Die Icons in SpiritMobile stehen alle unter der GNU LESSER GENERAL PUBLIC LICENSE. Die Quellen sind: http :// www . iconfinder . com / icondetails /6634/128/ calendar_date_event_plan_icon http :// www . iconfinder . com / icondetails /18421/128/ la test _new s_ne ws_ pape r_ne wsl ette r_ic on http :// www . iconfinder . com / icondetails /18171/128/ editors_package_icon http :// www . iconfinder . com / icondetails /18242/128/ vcalendar_icon http :// www . iconfinder . com / icondetails /6694/128/ tools_icon http :// www . iconfinder . com / icondetails /6843/128/ developer_folder_icon http :// www . iconfinder . com / icondetails /6712/128/ human_person_user_icon http :// www . iconfinder . com / icondetails /6083/128/ group_jabber_icon http :// www . iconfinder . com / icondetails /6071/128/ help_icon
Sebastian Stallenberger
99
Abbildungsverzeichnis
2.1 2.2 2.3 2.4 2.5 5.1 5.2 5.3 5.4 Die Android Achitektur in Anlehnung an [BP10, Abb. 2-1] und [Goo11j] 6 Der Weg vom Java- zum Dex-Bytecode in Anlehnung an [BP10] Abb. 2-2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 Die verfgbaren SDK-Umgebungen im Versionsvergleich in Anlehnung an [Goo11e] . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 Die Unterteilung einer NinePatch-Grak in Anlehnung an [Dar10] . . 10 Der Hierarchy Viewer . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 . 21 . 22 . 23 . . . . . . . 25 26 27 28 28 29 30
Mglichkeiten der Nutzer-Navigation durch die Module . . . . . . . Vorschlge zur Anordnung der Men-Elemente . . . . . . . . . . . . Aufbau von NewsMulti und der einzelnen News-Elemente . . . . . . Aufbau von NewsSingle, des Comment-Blocks und eines einzelnen Comment-Elements . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.5 Aufbau von NewsCreate . . . . . . . . . . . . . . . . . . . . . . . . 5.6 Aufbau des TimeTable in vertikaler und horizontaler Lage . . . . . 5.7 Aufbau der Detailansicht eines Timeslots . . . . . . . . . . . . . . . 5.8 Aufbau von Settings . . . . . . . . . . . . . . . . . . . . . . . . . . 5.9 Abruf-Algorithmus in NewsMulti . . . . . . . . . . . . . . . . . . . 5.10 Algorithmus des Sendens einer News . . . . . . . . . . . . . . . . . 6.1 6.2 6.3
Lebenszyklus einer Activity in Anlehnung an [Goo11a] . . . . . . . . 35 Screenshot der Activity MainActivity mit genetem Optionsmen . 43 Screenshot der PreferenceActivity Settings . . . . . . . . . . . . . . . 61
Sebastian Stallenberger
100
Tabellenverzeichnis
1.1 Verbreitung aktueller Android-Versionen in Anlehnung an [Goo11g] . 3
Sebastian Stallenberger
101
Listings
3.1 6.1 6.2 6.3 6.4 6.5 6.6 6.7 6.8 6.9 6.10 6.11 6.12 6.13 6.14 6.15 6.16 6.17 6.18 6.19 6.20 6.21 6.22 6.23 6.24 6.25 6.26 6.27 6.28 6.29 6.30 6.31 Die Verzeichnisstruktur des mit android-plugin erzeugten Projekts. . 16 33 33 34 36 37 38 39 39 40 40 41 41 43 43 44 44 45 45 46 46 47 48 49 50 51 51 52 52 52 53 54 Die ersten zwei Zeilen eines AsyncTasks in Java. Quelle: [Goo11d] . . Die AsyncTask Wrapper-Klasse in Anlehnung an [Sil11] . . . . . . . . Code des AsyncTask NewsReloadTask . . . . . . . . . . . . . . . . . Die Methoden onCreate und onResume der MainActivity . . . . . . . Der Code fr den Aufbau des Beta-Screens (Teil 1) . . . . . . . . . . Der Code fr den Aufbau des Beta-Screens (Teil 2) . . . . . . . . . . Der Code fr den Aufbau des Beta-Screens (Teil 3) . . . . . . . . . . Die Abfrage der Rolle und die Denition des Men-Arrays. . . . . . . Die Verbindung zwischen dataList und der GridView. . . . . . . . . . Die Methode menuHandler. . . . . . . . . . . . . . . . . . . . . . . . Der MainMenuAdapter. . . . . . . . . . . . . . . . . . . . . . . . . . Auszge aus der Klasse MainMenuAdapter. . . . . . . . . . . . . . . Der Aufbau eines Optionsmen (Teil 1) . . . . . . . . . . . . . . . . . Der Aufbau eines Optionsmen (Teil 2) . . . . . . . . . . . . . . . . . Der Aufbau eines Optionsmen (Teil 3) . . . . . . . . . . . . . . . . . Inhalt der onCreate-Methode von NewsMulti . . . . . . . . . . . . . . Die Methode checkInternetConnection in SpiritConnect . . . . . . . . Die Methode constructNewsMultiMainLay in ViewBuilder . . . . . . Die Konstruktion einer einzelnen News in der Funktion ViewBuilder.buildNewsView (Teil 1) . . . . . . . . . . . . . . . . . . . . . . . Die Konstruktion einer einzelnen News in der Funktion ViewBuilder.buildNewsView (Teil 2) . . . . . . . . . . . . . . . . . . . . . . . Die Konstruktion einer einzelnen News in der Funktion ViewBuilder.buildNewsView (Teil 3) . . . . . . . . . . . . . . . . . . . . . . . Die Methode onActivityResult in NewsMulti . . . . . . . . . . . . . . Der Inhalt des onClick-Events einer News in NewsMulti . . . . . . . . Die Activity NewsSingle . . . . . . . . . . . . . . . . . . . . . . . . . Die Methode buildCommentsView zur Konstruktion der KommentarListe. (Teil 1) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Methode buildCommentsView zur Konstruktion der KommentarListe. (Teil 2) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Methode buildCommentsView zur Konstruktion der KommentarListe. (Teil 3) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Auszge aus dem Task NewsCommentCreateTask. . . . . . . . . . . . Auszge aus dem Task NewsLoadSingleTask. . . . . . . . . . . . . . . Die Datei newscreate.xml mit dem Layout fr die Activity NewsCreate. Auszug aus der Activity NewsCreate. . . . . . . . . . . . . . . . . . .
Sebastian Stallenberger
102
Listings 6.32 6.33 6.34 6.35 6.36 6.37 6.38 6.39 6.40 6.41 6.42 6.43 6.44 6.45 6.46 6.47 6.48 6.49 6.50 6.51 6.52 6.53 6.54 6.55 6.56 6.57 6.58 6.59 A.1 A.2 A.3 A.4
Fachhochschule Schmalkalden SS 2011 Laden der fr den DegreeClass-Dialog bentigten Daten. . . . Setzen der onClick-Events fr die GUI-Elemente. (Teil 1) . . . Setzen der onClick-Events fr die GUI-Elemente. (Teil 2) . . . Die Methode translateDegreeNameToId. . . . . . . . . . . . . Die Methode onCreateDialog. . . . . . . . . . . . . . . . . . . Die Methode returnSelectedDegrees. . . . . . . . . . . . . . . Der Listener mDateSetListener. . . . . . . . . . . . . . . . . . Die XML-Datei fr die Settings. . . . . . . . . . . . . . . . . . Die PreferenceActivity Settings. (Teil 1) . . . . . . . . . . . . Die PreferenceActivity Settings. (Teil 2) . . . . . . . . . . . . Die PreferenceActivity Settings. (Teil 3) . . . . . . . . . . . . Die doInBackground-Methode des Tasks VerifyLoginDataTask. Der Inhalt der info.xml. . . . . . . . . . . . . . . . . . . . . . Der Inhalt der onCreate-Methode der Activity Info. . . . . . . Die Methode readTextFromResource. . . . . . . . . . . . . . . Die Case Classes im Objekt JSON-Processor. . . . . . . . . . . Die Methode jsonStringToSingleNews. . . . . . . . . . . . . . Die Klasse SpiritHttpClient. . . . . . . . . . . . . . . . . . . . Die Methode getJsonFromUrl. . . . . . . . . . . . . . . . . . . Die Methode putJsonToUrl. . . . . . . . . . . . . . . . . . . . Die Methode checkInternetConnection. . . . . . . . . . . . . . Die Methode getNews. . . . . . . . . . . . . . . . . . . . . . . Die Methode convertStreamToString. . . . . . . . . . . . . . . Die Methoden loadString und saveString. . . . . . . . . . . . . Die Methoden setStringPrefs und getStringPrefs. . . . . . . . Die Methode getBoolPrefs. . . . . . . . . . . . . . . . . . . . . Die Methode setLastNewsDate. . . . . . . . . . . . . . . . . . Die Methode getDegreeMap. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 56 56 57 57 58 59 60 61 62 63 64 64 65 65 66 67 68 69 70 70 71 72 72 73 73 74 74 83 84 87 88
Der Inhalt der bat-Datei. . . . . . . . . . . . . . . . . . . . . . . Mgliche Fehlermeldung bei der Installation des ADT Plugins. . Mgliche Fehlermeldung bei der Installation des android-plugin. Der Inhalt der buildExtension. . . . . . . . . . . . . . . . . . . .
Sebastian Stallenberger
103
Eidesstattliche Erklrung
Ich versichere an Eides Statt durch meine eigenhndige Unterschrift, dass ich die vorliegende Arbeit selbststndig und ohne fremde Hilfe angefertigt habe. Alle Stellen, die wrtlich oder dem Sinn nach auf Publikationen oder Vortrgen anderer Autoren beruhen, sind als solche kenntlich gemacht. Ich versichere auerdem, dass ich keine andere als die angegebene Literatur verwendet habe. Diese Versicherung bezieht sich auch auf alle in der Arbeit enthaltenen Zeichnungen, Skizzen, bildlichen Darstellungen und dergleichen. Die Arbeit wurde bisher keiner anderen Prfungsbehrde vorgelegt und auch noch nicht verentlicht.
Sebastian Stallenberger
104