Fußgängernavigation mit Augmented Reality auf Android-basierten Smartphones

Größe: px
Ab Seite anzeigen:

Download "Fußgängernavigation mit Augmented Reality auf Android-basierten Smartphones"

Transkript

1 Institut für Informatik AG Medieninformatik Diplomarbeit Fußgängernavigation mit Augmented Reality auf Android-basierten Dirk Stürzekarn August 2010 Erstgutachter: Zweitgutachter: Prof. Dr. Oliver Vornberger Jun.-Prof. Dr.-Ing. Elke Pulvermüller

2

3 Zusammenfassung der aktuellen Generation verfügen über immer mehr technische Möglichkeiten. Im Rahmen dieser Arbeit wurde eine Anwendung entwickelt, die eine Fußgängernavigation mit Hilfe eines Mobiltelefons ermöglicht, wobei der Nutzer sowohl durch übliche Kartenwerke als auch durch Augmented Reality geleitet wird. Unter Augmented Reality versteht man die Einbettung zusätzlicher Informationen in das Realbild ohne eine Unterdrückung der natürlichen Sinneseindrücke. Als Software-Plattform dient dabei das von Google entwickelte Betriebssystem Android mit zugehörigem Software Development Kit, hardwareseitig werden eine Internetanbindung, Lagesensoren und ein GPS-Modul benötigt. In Verbindung mit Umgebungsinformationen wie Sehenswürdigkeiten soll eine Route zum einen als Kartenformat vorliegen, zum anderen auf dem Display des Mobilgeräts dargestellt werden. Zurückgegriffen wird hierbei auf Informationen von OpenStreet- Map und Wikipedia. Ziel dieser Arbeit ist, ein Programm zu entwerfen, das die gegebenen Daten nutzerfreundlich und mit hoher Geschwindigkeit aufbereitet. Besonderes Augenmerk wird dabei darauf gelegt, inwieweit sich Android als Entwicklungsplattform zum Erstellen umfangreicherer Software eignet. Außerdem soll evaluiert werden, inwiefern die Hard- und Software aktueller Mobilgeräte den Anforderungen an eine solche Anwendung gerecht werden kann.

4

5 Inhaltsverzeichnis 1 Einleitung 1 2 Android Grundlegendes Aufbau des Betriebssystems Prozessmanagement und Virtual Machine Android SDK Programmaufbau Permissions Activities Services Intents Intent-Filter Persistenz Content Provider Ressourcen Views Menüs Weitere Android-Bestandteile Programmgrundlagen Augmented Reality Kartographische Grundbegriffe Anforderungen an den Funktionsumfang Voraussetzungen an das System Software-Entwurfsmuster Singletons Delegation Callbacks und Observer Verwendete Frameworks OpenStreetMap

6 5.2 Cloudmade Nutiteq Mobile Map API Geonames-Webservice OSM-Namefinder Umsetzung Manifest und Basisklassen Ressourcen Positionsdaten Datenverwaltung Positionsbestimmung Lagebestimmung Kommunikation zwischen Threads Datenaustausch mit PersonalLocation Sehenswürdigkeiten Beispiel-Implementierung Wikipedia-Provider Routenbestimmung Zielbestimmung Umsetzungen der Adresssuche Routenberechnung Realisierung der Wegfindung Benutzeroberfläche Kartenanzeige RoutingActivity MainActivity Anzeige des Kamerabildes Einbettung von Sehenswürdigkeiten in das Kamerabild Richtungsanzeige per Pfeil Datensicherung und Einstellungen Persistierung der Anwendungsdaten Fazit Programmieren für Android Ausblick Abbildungsverzeichnis 100 Listings 101 Literaturverzeichnis 102 Inhalt der CD 106 Erklärung 107

7 Kapitel 1 Einleitung Diese Diplomarbeit beschäftigt sich mit der Aufgabe, für ein Mobiltelefon eine geeignete Navigationssoftware zu entwerfen, die Fußgänger auf intuitive Weise zum gewünschten Ziel führen kann. Hierbei sollen dem Anwender neben den elementaren Routeninformationen außerdem noch weitere Hinweise über die Umgebung bereitgestellt werden. Als Basis für das Programm soll dabei ein Smartphone dienen, auf dem Googles Betriebssystem Android eingesetzt wird. Im Zuge der Arbeit soll dabei sowohl auf die zugrunde liegende Architektur des Android- Betriebssystems näher eingegangen als auch die Konzeption und Implementation der eigentlichen Applikation beleuchtet werden. Ein Hauptziel wird es desweiteren sein, die Möglichkeiten und Grenzen der vorhandenen Hard- und Software in Bezug auf die gegebene Aufgabenstellung zu evaluieren. Zu Beginn der Arbeit soll zunächst eine allgemeine Beschreibung von Android OS gegeben werden. Ebenso sollen die Arbeitsweise beleuchtet und eine breite Einführung in das Software Development Kit und die zugehörige API gegeben werden. Es werden dabei auch allgemeine Konzepte des Betriebssystems vorgestellt und zentrale Bestandteile für eigene Applikationen näher besprochen. Es folgt im Kapitel darauf eine genauere Beschreibung des zu erstellenden Programmes und einige essentielle Begriffsdefinitionen, außerdem wird auf die grundlegenden Anforderungen an das Gerät eingegangen, auf dem die Applikation später laufen soll. Im Anschluss werden einige im Programm eingesetzte Software-Entwurfsmuster vorgestellt, die sich für die Arbeit mit Android als vorteilhaft erwiesen haben und entsprechend im Rahmen der Implementation eine wichtige Rolle spielen. Hierzu gehören insbesondere das Delegation-Pattern und Singletons. Nach diesen allgemeinen Vorbereitungen werden externe Frameworks und Dienste vorgestellt, auf deren Daten die Anwendung zurückgreift. Hierbei stehen insbesondere die Karten- und Routendienste von OpenStreetMap und Cloudmade im Fokus, ebenso die Karten-API für Android von Nutiteq. 1

8 2 KAPITEL 1. EINLEITUNG Die genaue Implementierung der Anwendung wird im darauffolgenden Kapitel beleuchtet, dabei wird sowohl der Zugriff auf vorhandene Geräteressourcen thematisiert als auch die Aufbereitung der zur Verfügung stehenden Daten. Zum Abschluss dieser Arbeit wird eine Bewertung in Hinsicht auf die Leistungsfähigkeit von System und Gerät vorgenommen. Hierbei sollen zum einen die Möglichkeiten und Beschränkungen hinsichtlich der Hardware betrachtet werden und zum anderen die Vor- und Nachteile der von Android bereitgestellten API.

9 Kapitel 2 Android 2.1 Grundlegendes Mit der Markteinführung des iphones von Apple rückte im Smartphone-Markt ein neuer Aspekt in Bezug auf die Nutzerbindung in den Fokus der Öffentlichkeit: Der App-Store, eine von Apple betriebene und moderierte Verkaufsplattform für Drittanbieter-Software, ermöglichte es Besitzern eines Apple-Mobilgeräts, dieses mit einer Vielzahl von zusätzlichen Funktionen zu erweitern, indem sie dort entsprechende Programme herunterladen konnten. Die Eröffnung des App-Stores ging einher mit der Bereitstellung des iphone-sdks, das es Entwicklern ermöglicht, auf Basis eines umfangreichen Frameworks auf die besondere Hardware zugeschnittene Anwendungen zu entwerfen. Die Idee einer explizit auf Mobilgeräte zugeschnittenen API war nicht neu, schon zuvor war auf vielen Handys die Java Micro Edition verfügbar. Der App-Store ermöglichte allerdings eine zentrale Sammelstelle für die verfügbare Software, die zudem noch bequem über das Gerät selbst erreicht werden konnte. Der wachsende kommerzielle Erfolg des iphones, 2009 wurden fast 25 Millionen Geräte verkauft [Gar10], bildet eine zusätzliche Grundlage, weshalb die Entwicklung von Software für Mobilgeräte in den letzten Jahren einen bedeutenden Einfluss auf die Softwareentwicklung genommen hat. Wohl auch als Reaktion auf den Erfolg Apples bei der Einführung des iphones gründete sich im November 2007 die Open Handset Alliance, ein von Google geführtes Konsortium aus Hard- und Softwareherstellern und Mobilfunkbetreibern. Dieses formuliert als eigenes Ziel, offene Standards für Geräte und Programme zu entwickeln und einzusetzen. [Ope07] Im Zuge dessen wurde das federführend von Google entwickelte Betriebssytem Android veröffentlicht, das auf einem Linux-Kernel basiert. Den Quellcode stellte Google unter einer Apache Lizenz als Open Source zur Verfügung. [And10g] Die Entwicklung von Android startete schon einige Jahre vor der Gründung der Open Handset Alliance. Das erste Mobiltelefon mit Android OS war im Oktober 2008 das HTC Dream, das exklusiv von T-Mobile vertrieben wurde. Mit dem Betriebssystem wurde auch das Android Software Development Kit zur Verfügung ge- 3

10 4 KAPITEL 2. ANDROID Abbildung 2.1: HTC Hero mit Android-Betriebssystem stellt, das es Entwicklern ermöglicht, auf Grundlage einer umfangreichen API eigene Programme für das System zu entwickeln. Als Grundlage hierfür dient der Java-Sprachstandard, dessen Standard Edition in einer abgespeckten Variante den Kern des SDK bildet. Die genaue Struktur wird später in dieser Arbeit beleuchtet. Wie Apple stellt auch Google eine Handelsplattform für Anwendungen zur Verfügung, bei der registrierte Entwickler Programme einstellen können. Der Android Market bietet eine Vielzahl kostenloser und kostenpflichtiger Applikationen. 2.2 Aufbau des Betriebssystems Android lässt sich strukturell in vier Ebenen gliedern: Kernel, Bibliotheken, Anwendungsgerüst und Anwendungen. Die unterste Ebene der Architektur ist ein Linux-Kernel der Reihe 2.6, der für Treiber-, Nutzer-

11 2.2. AUFBAU DES BETRIEBSSYSTEMS 5 und Energieverwaltung zuständig ist. Wie bei jedem Betriebssystem stellt er die Verbindung zur Hardware her. Die Ebene darüber stellen die Standardbibliotheken dar. Diese sind in C oder C++ geschrieben und für den Entwickler nicht direkt erreichbar. An dieser Stelle finden sich, wie Abbildung 2.2 zeigt, Grundfunktionalitäten wie die Browserkomponente WebKit, OpenGL, DBMS durch SQLite und Multimedia. Eine etwas genauere Auflistung findet sich auch unter [And10f]. Abbildung 2.2: Android-Systemarchitektur nach [And10f], erstellt von Alvaro Fuentes für Wikipedia [Fue10] Als Verbindung zwischen den hardwarenahen Ressourcen und Standardbibliotheken und den eigentlichen Anwendungen dient das Application Framework. Dieses bildet den Hauptteil des Software Development Kits. Neben einer großen Auswahl vordefinierter Objekte für die Anzeige existieren Klassen zur Verwaltung von Daten, Versendung von Nachrichten, Nutzung eingebauter Funktionen der Hardware und Abwicklung des Anwendungslebens. Im Folgenden werden viele

12 6 KAPITEL 2. ANDROID dieser Komponenten noch einmal eingehend beleuchtet. Die oberste Ebene der Android-Hierarchie bilden die Anwendungen auf dem Gerät. Hierunter fallen Standardprogramme wie Telefon oder Browser, aber auch Software von Drittanbietern. Sie werden auf Basis des Android SDK in Java geschrieben und in Form von Android-Package- Dateien (.apk) auf dem Mobilgerät installiert. 2.3 Prozessmanagement und Virtual Machine Android verfolgt durch ein eingearbeitetes Sicherheitskonzept das Ziel, Applikationen so eigenständig und gekapselt wie möglich auszuführen. Das Betriebssystem verwendet hierfür drei wesentliche Vorgehensweisen 1 : Jede Anwendung wird vom System als eigener Benutzer geführt. Auf diese Weise werden schon auf Kernel-Ebene Berechtigungen so vergeben, dass ausschließlich die eigene Anwendung Zugriff auf die zugehörigen Daten und Dateien haben. Es ist jedoch durch das Setzen spezieller Berechtigungen möglich, dass Anwendungen des selben Entwicklers unter einem einzigen Nutzer geführt werden, so dass diese untereinander auf Daten zugreifen können. Jedes Programm wird zudem in einem eigenen Prozess auf Betriebssystemebene geführt. Hierdurch ist das Betriebssystem in der Lage, einzelne Komponenten nur dann zu starten, wenn es nötig wird, und diese bei Ressourcenknappheit unabhängig voneinander zu beenden. Wie bei Java-Programmen üblich verwendet auch Android eine virtuelle Maschine für seine Programme. Hierbei wird in jedem Prozess eine eigene VM gestartet, so dass auch der in Ausführung befindliche Code von dem anderer Applikationen isoliert bleibt. Wie auch bei der Verteilung von User-IDs gestattet es Android allerdings, dass Packages, die mit der gleichen Signatur versehen wurden, auf speziellen Wunsch hin im selben Thread, und damit in der gleichen Virtual Machine, ausgeführt werden können. Es handelt sich bei der von Android genutzten virtuellen Maschine nicht um die Standard-VM der Java Runtime Environment. Dies hat zum einen Lizenzgründe, zum anderen ist die verbaute Virtual Machine in großem Maße auf die Verwendung auf Mobilgeräten abgestimmt, so dass sie mit den stark begrenzten Ressourcen besser umgehen kann. Die Dalvik Virtual Machine erhält speziell kompilierte Dateien, die einen leicht anderen Befehlssatz besitzen als der normale Java- Bytecode. Diese werden jedoch aus bereits kompilierten Class-Dateien erzeugt. Als wesentliche Unterschiede zur Standard-VM lassen sich insbesondere die unterliegende Java-Implementation, im Falle Dalviks von Apache, und die Grundarchitektur nennen. Während die virtuelle Maschine von Sun als Kellerautomat implementiert ist, arbeitet die DVM als Registermaschine. 1 Application Fundamentals:

13 2.4. ANDROID SDK Android SDK Androids Software Development Kit umfasst weit über 1500 Klassen verschiedener Urheber. Etwa die Hälfte dieser Klassen stammt aus dem Java Development Kit von Oracle/Sun. Hierunter finden sich die meisten der Standardfunktionalitäten. Dazu gehören neben den einfachen Datentypen insbesondere das Threading und Streams, ebenso das Collections-Framework, Brückenklassen für Kryptographie- und Kompressionsalgorithmen und XML. Auch die OpenGL ES- Implementierung findet sich hier. Nicht übernommen wurden dagegen die grafiknahen Bestandteile wie AWT und Swing. Wie das JDK verfügt das Android SDK zudem über die Implementation eines SAX-Parsers, Unterstützung für Document Object Model und JSON. Außerdem stellt das SDK bereits die Funktionen für JUnit-Tests zur Verfügung und greift auf eine Apache-Implementation für das HTTP-Protokoll zurück. Der plattformspezifische Teil im Paket android hat neben den JDK-Klassen den größten Anteil an der API. Hier finden sich die Grundklassen der Komponenten, Wrapper-Implementationen für die Verwendung von Hardwarekomponenten, Klassen für den Zugriff auf die Standardbibliotheken und natürlich die Basisimplementationen für die grafische Benutzeroberfläche und die Nutzerinteraktion. Neben den Klassenbibliotheken beinhaltet das Entwicklungssystem außerdem noch eine Vielzahl an Werkzeugen. Zuvorderst gehören hierzu der Emulator, mit dessen Hilfe man geschriebene Anwendungen bereits auf dem Entwicklungsrechner testen kann, und der Dalvik-Compiler sowie das Packaging-Tool zur Erzeugung von apk-dateien. Desweiteren beinhaltet das Toolkit Debugger, die es ermöglichen, eine zu testende Anwendung während der Laufzeit zu überwachen. 2.5 Programmaufbau Android verfolgt den Ansatz, Anwendungen komponentenbasiert zu erstellen. Das bedeutet, dass Applikationen nicht mehr als abgeschlossene Funktionseinheiten betrachtet werden, sondern die Möglichkeit besteht, Eigenschaften und Fähigkeiten eines Programms in einem anderen zu nutzen. Auf diese Weise soll vermieden werden, dass häufig genutzte Funktionalitäten immer wieder neu implementiert werden müssen. Ein naheliegendes Beispiel hierfür ist die Verwaltung von Kontaktinformationen. Schreibt ein Entwickler beispielsweise ein Programm zum Austausch von Nachrichten, wäre es unpraktisch, eine komplett eigenständige Kontaktdatenbank zu erstellen. Durch den Komponentenansatz bietet sich für den Programmierer nun die Möglichkeit, das System nach einer vorhandenen Kontaktliste zu fragen und auf diese zuzugreifen. Ob und in welchem Umfang es sich anbietet, die Komponenten der eigenen Anwendung für andere Anwendungen zugänglich zu machen, bleibt dem Entwickler überlassen. Android zerlegt also nicht das Programm und stellt Teile zur Nutzung durch fremde Applikationen zur Verfügung, sondern legt dies in die Hände des Programmierers. Der Mechanismus, um eigene Komponenten nach außen zur Verfügung zu stellen, wird etwas später im Abschnitt Intents näher betrachtet.

14 8 KAPITEL 2. ANDROID Zunächst folgt eine Betrachtung der Android-Manifest-Datei. Dabei handelt es sich um ein XML- Dokument, das den Aufbau eines Programms, die vorhandenen Komponenten und anwendungsspezifische Voraussetzungen beschreibt. Die Datei selbst muss AndroidManifest.xml heißen und hat einen Aufbau wie in Listing 2.1 zu sehen. Der Ausschnitt ist Teil der für die erstellte Anwendung verwendeten Manifest-Datei. <? xml version =" 1.0 " encoding ="utf -8"?> < manifest xmlns:android =" http: // schemas. android. com / apk / res / android " android: versioncode =" 1" android: versionname =" 1.0 " package ="de. monarchy. guideme "> < application android: icon drawable / icon " android:label / app_name " android: debuggable =" true " > < activity android: screenorientation =" landscape " android:name =". main. MainActivity " android:label / app_name "> <intent - filter > < action android:name =" android. intent. action. MAIN "> </ action > < category android:name =" android. intent. category. LAUNCHER "> </ category > </ intent - filter > </ activity > <uses - sdk android: minsdkversion =" 3" / > <uses - feature android:name =" android. hardware. camera "> </uses - feature > <uses - configuration android: reqtouchscreen =" finger " > </ uses - configuration > <uses - permission android: name =" android. permission. INTERNET " > </uses - permission > </ application > </ manifest > Listing 2.1: Android-Manifestdatei Alle Daten des Manifests stehen innerhalb des manifest-tags. Das xmlns:android-attribut ist dabei zwingend vorgegeben. Die interne Versionsnummer der Anwendung wird mit Hilfe von android:versioncode angegeben, der öffentliche Versionsname durch android:versionname. Im Attribut package wird der Name des Pakets abgelegt, in dem die Anwendung liegt. Der Entwickler sollte hier den gleichen Konventionen wie bei normalen Java-Applikationen folgen. Im weiteren Teil des Manifests wird diese Angabe jeweils genutzt, um relative Pfadangaben machen zu können. Innerhalb des application-elements werden alle Komponenten definiert, die das Programm beinhaltet. In Frage kommende Komponenten sind hierbei Services, Activities, Content-Provider und Broadcast-Receiver. Deren Verwendungszwecke und Nutzungen werden nachfolgend noch genauer beschrieben. Das application-element dient unter anderem dazu, für alle Inhalte notwendige Berechtigungen festzulegen. Außerdem wird im android:label-attribut der Name der Anwendung festgelegt. Dieser wird zum Beispiel in der Liste der Anwendungen auf dem Gerät

15 2.5. PROGRAMMAUFBAU 9 angezeigt. Ebenso kann in android:icon ein Icon für die Anwendung festgelegt werden. Weitere optionale Einstellungen erlauben die Beeinflussung der Laufzeitumgebung. So lässt sich mit android:debuggable="true" die Anwendung durch das SDK überwachen. Es folgen XML-Elemente, die Einstellungen und Voraussetzungen für die Anwendung definieren. Zunächst wird im Tag uses-sdk angegeben, auf welche Version des Android SDK die Applikation zurückgreift. Auf diese Weise kann das System überprüfen, ob alle für die Anwendung benötigten API-Teile vorhanden sind. Eine weitere Möglichkeit ist es, bestimmte Gerätekonfigurationen einzustellen. Mit den beiden Elementen uses-configuration und uses-feature können bestimmte Anforderungen an vorhandene Hard- und Software gestellt werden. Das Configuration-Element bezieht sich dabei auf mögliche Eingabegeräte wie Touchscreen oder Trackball. Unter Features fallen spezielle Hardware-Erweiterungen wie Kamera-Blitz oder Multitouch-Fähigkeit Permissions Als letzter, wichtiger Teil des Manifests gelten die Permissions. Auf diese Weise kündigt der Entwickler an, dass das Programm auf bestimmte Grundfunktionen des Geräts zugreift. Genutzt wird hierfür das Element uses-permission, dem als Attribut eine Eigenschaft oder Funktionalität mitgegeben wird. Im gegebenen Beispiel ist dies die Fähigkeit, auf das Internet zuzugreifen. Es gibt bereits über 100 verschiedene Permissions, die Android systemseitig zur Verfügung stellt und nahezu jeden Zugriff auf Gerätekomponenten und Standarddaten betreffen. Neben Internet umfassen sie zum Beispiel auch Bluetooth, Kontaktdaten oder Energieeinstellungen. Nutzt ein Programmierer eine geschützte Funktion wie den Zugriff auf die Positionsermittlung des Gerätes, obwohl er die nötige Permission nicht angefordert hat, reagiert das System mit einer entsprechenden SecurityException und unterbindet die Nutzung des entsprechenden Features. Reagiert der Entwickler nicht entsprechend, führt dies zum Abbruch des Programms. Installiert ein Benutzer über den Android Market eine Anwendung auf seinem Gerät, so wird er zuvor informiert, welche Zugriffsberechtigungen das Programm einfordert. Anhand dessen kann er wählen, ob er die Anwendung installieren möchte, ein selektives Verbot bestimmter Permissions existiert allerdings nicht. Auch bereits installierten Applikationen eine Berechtigung wieder zu entziehen, ist nicht vorgesehen. Da Android komponentenbasiert ist, gibt es für Entwickler die Möglichkeit, Teile der eigenen Anwendung ebenfalls durch eigene Permissions zu schützen. Sie werden auch im Manifest in permission-elementen deklariert und bekommen einen eindeutigen Namen durch das Attribut android:name. Die Eindeutigkeit sollte hier wieder über die üblichen Namenskonventionen sichergestellt werden, da die Permissions applikationsübergreifend bekannt gegeben werden und es daher sonst zu Konflikten kommen könnte. Zudem kann der Entwickler über das Attribut android:protectionlevel beschreiben, inwiefern der Zugriff auf diese Komponente systemrelevant ist. Auf diese Weise lässt sich unter anderem der Zugriff auf Komponenten beschränken, die mit der gleichen Signatur versehen wurden. Permissions lassen sich über entsprechende XML-

16 10 KAPITEL 2. ANDROID Elemente gruppieren und in eine Hierarchie bringen. Der Schutz einer Komponente durch die entworfene Permission wird über ein dortiges Attribut im Manifest-File geregelt. 2.6 Activities Alle Komponenten, die in Verbindung mit dem Nutzer treten, also eine grafische Oberfläche aufweisen, sind vom Typ Activity. Jede Anwendung definiert eine bestimmte Activity als Einsprungspunkt, diese wird dann beim Programmstart auf dem Bildschirm angezeigt. Jede eigene Activity, die ein Programm nutzt, muss im Manifest angekündigt werden. Hierfür dient der activity-tag, der innerhalb des application-elements aufgeführt wird. Eine nicht im Manifest aufgeführte Activity kann nicht aufgerufen werden, die virtuelle Maschine reagiert in diesem Fall mit einer Exception. Wichtigster Teil des activity-tags ist das Attribut android:name, das den Klassennamen der Activity beinhaltet. Hierbei muss entweder der voll qualifizierte Name angegeben werden oder beginnend mit einem Punkt der Name im Bezug auf das im Manifest festgelegte Package. Weitere Attribute sind optional. Es lässt sich dabei unter anderem einstellen, in welcher Ausrichtung die Activity auf dem Bildschirm angezeigt werden soll, welche Permission nötig ist, um die Activity von außen zu starten, oder welchen Titel das Fenster haben soll, das die Activity darstellt. Activities können auf mehrere Weisen gestartet werden. Zum einen wird eine bestimmte Activity beim Start der Applikation aufgerufen, zum anderen besteht die Möglichkeit, aus einer bestehenden Activity eine andere zu starten. Hierbei muss wiederum unterschieden werden, ob die neue Activity eigenständig sein soll oder ob der Aufrufer erwartet, dass sie ihm ein Ergebnis zurückliefert. Je nachdem lässt sich die neue Aktivität mit startactivity(intent) oder startactivityforresult(intent, int) aufrufen. Beide Methoden liefern keinen Rückgabewert, der Aufrufer hält also zu keiner Zeit die Instanz der gestarteten Activity in der Hand. Der genaue Mechanismus des Aufrufs wird später im Abschnitt über Intents noch näher behandelt. Soll ein Ergebnis zurückgeliefert werden, muss dieses gespeichert werden. Dafür gibt es die Methode setresult(int), der man einen Ergebniscode mitgeben kann. Dafür stehen Standardkonstanten zur Verfügung, es ist jedoch genauso möglich, eigene Codes festzulegen. Sollen weitere Informationen zurückgegeben werden, wird zusätzlich zum Ergebniswert ein Intent mit Daten mitgegeben, dies geschieht über setresult(int, Intent). Der genaue Zweck von Intents folgt später, ebenso die Verwendung zur Datenübertragung. Wird eine Activity gestartet, speichert Android die zu ihr gehörenden Daten auf einem Stack. Auch alle zuvor aufgerufenen Activities, die seit dem letzten Aufruf des Startbildschirms angezigt wurden, befinden sich auf diesem Stack, wobei die zuerst gestartete Aktivity zuunterst liegt. Um von der aktuellen Oberfläche zur zuvor genutzten zurückzukehren, kann der Benutzer den Zurück-Button des Gerätes verwenden. Dieses Verhalten ist besonders wichtig, wenn eine Applikation aus mehreren Nutzerschnittstellen besteht, die sich gegenseitig aufrufen und untereinander

17 2.6. ACTIVITIES 11 Daten übertragen sollen. auf diese Weise ist sichergestellt, dass der Anwender immer wieder von einem Unterfenster zum Hauptprogramm zurückkehren kann, ohne dass dort getätigte Eingaben zwingend verloren sind. Jede aufgerufene Activity beginnt automatisch einen festen Lebenszyklus. Dieser durchläuft mehrere Phasen, die sich nach dem Zustand der Aktivität richten. Generell wird unterschieden, ob eine Activity im Vordergrund sichtbar, teilweise oder vollständig verdeckt ist. Nur die Activities, die nicht von anderen verdeckt werden, sind wirklich aktiv. Alle anderen befinden sich in einem inaktiven Zustand, in dem es möglich ist, dass sie vom System beendet werden, um die Systemressourcen zu schonen. Abbildung 2.3: Der Lebenszyklus einer Activity [And10a] Jeder Lebenszyklus einer Activity beginnt mit der Methode oncreate(bundle savedstate). Diese wird immer dann aufgerufen, wenn die Activity neu angelegt werden muss. Hier werden

18 12 KAPITEL 2. ANDROID die grundlegenden Operationen erledigt, die vor der ersten Anzeige notwendig sind. Dazu gehört die Erstellung des User Interfaces ebenso wie das Laden notwendiger Daten. Wie bei allen Standardmethoden des Lebenszyklus ist es zwingend erforderlich, innerhalb der oncreate-methode einer eigenen, von Activity erbenden Klasse die oncreate-methode der Elternklasse aufzurufen. Geschieht dies nicht, reagiert das System mit einer Exception und bricht die Programmausführung ab. Das der oncreate-methode übergebene Objekt vom Typ Bundle ist eine Sammlung von Daten, die einen Status repräsentieren, den die Activity bei einem früheren Durchlauf des Lebenszyklus hatte. Auf diese Weise kann eine Anwendung, die beendet wurde, bei einem Neustart in den Zustand zurückversetzt werden, in dem sie zuvor war. Um diesen Zustand zu persistieren, dient die Methode onsaveinstancestate(bundle outstate). Diese wird dann aufgerufen, wenn eine neue Anwendung die eigene Anwendung überdeckt, allerdings nur in Fällen, in denen das System erwarten kann, dass explizit in diese Instanz der Activity zurückgesprungen werden kann und sie zusätzlich während der Inaktivität vom System beendet wird. Wird daher eine Activity mit dem Zurück-Button des Geräts verlassen, verschwindet sie vom Anwendungsstapel und muss definitiv neu gestartet werden, ein Speichern des Zustandes ist in einem solchen Fall also nicht nötig. Nach jedem Aufruf der oncreate-methode wird im nächsten Schritt die Methode onstart() aufgerufen, anschließend onresume(). Daten und Ressourcen, die bereits bei kurzfristiger Inaktivität freigegeben werden müssen, sollten in letzterer allokiert werden. Sie ist die einzige, von der garantiert ist, dass sie nach einer Zeit der Inaktivität auf jeden Fall aufgerufen wird. Andererseits ist es hier besonders wichtig, dass eine hohe Performanz gewährleistet ist, da ein Pause-Status häufig auftreten kann. Wie bei der Erzeugung der Activity läuft auch bei der Inaktivierung eine Kette von Methoden ab. Im Gegensatz zu den obigen Methoden ist jedoch nicht garantiert, dass wirklich alle der folgenden Methoden durchlaufen werden. Bei Ressourcenknappheit besteht an jedem Punkt der Kette die Möglichkeit, dass von Systemseite der zur Activity gehörige Prozess beendet wird. In diesem Fall werden die eventuell noch ausstehenden Methoden nicht mehr aufgerufen. Als Gegenstück zu onresume fungiert die Methode onpause(). Sie wird aufgerufen, sobald die Activity nicht mehr im Vordergrund liegt. Da sie, wie ihr Pendant, die einzige Methode ist, die im Verlauf der Inaktivität auf jeden Fall ausgeführt wird, sollte an dieser Stelle im Allgemeinen eine Datensicherung vorgenommen werden. Hier bietet es sich außerdem an, leistungsfordernde Bestandteile zu beenden, um Ressourcen zu sparen. Wichtig ist, dass die Activity, die sich vor die betroffene geschoben hat, erst dann starten kann, wenn die onpause-methode fertig durchlaufen wurde. Entsprechend sollten an dieser Stelle keine langwierigen Rechnungen durchgeführt werden. Pausiert die Activity, bestehen drei Möglichkeiten der Fortsetzung. Zunächst kann ihr Prozess beendet werden, womit auch die Activity stirbt. Außerdem kann sie in den Vordergrund zurückkehren, dann wird die onresume-methode aufgerufen. Wird die Activity komplett verdeckt, ruft das System die Methode onstop() auf. Wie lange dieser Zwischenstatus andauert, ist nicht vorgegeben. Es besteht wiederum die Möglichkeit, dass an dieser Stelle der Prozess beendet wird. Gelangt die Activity erneut in den Vordergrund, ruft das System zunächst die Methode onrestart()

19 2.7. SERVICES 13 auf, um die Möglichkeit zu geben, Ressourcen zu belegen, die bereits während der Laufzeit zuvor erzeugt wurden. Anschließend folgt der Aufruf von onstart. Alternativ kann die Activity auch zerstört werden. Dieses geschieht in der ondestroy()-methode. Hier sollten alle Ressourcen freigegeben werden, die von der Activity während des Lebenszyklus gebraucht werden, da sie bei einem erneuten Start in oncreate wieder erzeugt werden. 2.7 Services Im Gegensatz zu Activities weisen Services keine Benutzeroberfläche auf. Sie laufen lediglich im Hintergrund eines Prozesses und dienen zum Beispiel dazu, Daten zu laden oder dauerhafte Aufgaben zu erledigen. Sie werden im Allgemeinen im selben Thread ausgeführt wie die Acitivity der gleichen Anwendung, so dass die Gefahr besteht, dass die Anwendung blockiert wird, wenn ein Service rechenaufwändige Aufgaben erledigen muss. Auch der Service ist eine Komponente, auf die von außen zugegriffen werden kann. Wie schon die Activity muss auch er im Manifest verankert sein, um verwendet werden zu können. Das service- Element ist entsprechend ähnlich aufgebaut. Ihm muss der Name der Klasse übergeben werden, außerdem kann auch ein Service durch eine Permission Zugriffsrechte Dritter beeinflussen. Wie schon angedeutet, ist vorgesehen, dass ein Service im Prozess der Anwendung läuft, die ihn deklariert hat. Das Attribut android:process im Manifest kann dieses jedoch beeinflussen. Wird hier ein Name angegeben, erhält der Service einen eigenen Prozess, auf den von verschiedenen Applikationen zugegriffen werden kann, soweit die gesetzten Berechtigungen es zulassen. Beginnt der Prozessname jedoch mit einem Doppelpunkt, so bedeutet das, dass nur die startende Applikation Zugriff erhält. Auch das Leben eines Services ist deutlich einfacher gehalten als das der Activities. Er verfügt lediglich über zwei wichtige steuernde Methoden, onstartcommand() und onstop(), die bei Start und Beendigung aufgerufen werden. Die fehlende Nutzeroberfläche macht es überflüssig, Zwischenzustände je nach Sichtbarkeit zu erschaffen. Vor Version 2 des Android-SDK wurde zum Start eines Services noch onstart() aufgerufen. Gestartet wird ein Service innerhalb einer Activity oder eines Services durch Aufruf der Methode startservice(intent), wobei im Intent der Name des Services übergeben wird. Das hat wie bei Activities zur Folge, dass die startende Instanz keine Referenz auf die Instanz des neu gestarteten Services erhält. Um mit einem Service in Kontakt zu treten, muss man sich explizit mit diesem verbinden. Die Verbindung mit einem Service und somit der Datenaustausch wird über Binder-Objekte geregelt. Diese stellen eine Schnittstelle zum gewünschten Service dar. Um einen solchen Binder zu erhalten, muss eine Komponente die Methode bindservice(intent, ServiceConnection, int) aufrufen. Der Intent dient wiederum zur Identifizierung des Services, als letzter Parameter wird angegeben, unter welchen Voraussetzungen der Service ausgeführt werden soll. Hier kann

20 14 KAPITEL 2. ANDROID man festgelegen, ob der Service explizit neu gestartet werden oder zum Beispiel nur während der Lebenszeit des Aufrufers existieren soll. Beim Interface ServiceConnection handelt es sich um ein Callback-Interface. Hierüber erhält die den Service aufrufende Instanz einen Hinweis, wenn die Verbindung zum Service hergestellt oder beendet wurde. Wenn mit dem Service verbunden wurde, wird an dieser Stelle außerdem das Binder-Objekt für die Kommunikation übergeben. Über Binder besteht die Möglichkeit, Daten vom Service abzufragen oder an ihn weiterzureichen. Möchte ein Entwickler den Datenaustausch mit seinem Service ermöglichen, muss er dafür eine eigene Binder-Implementation bereitstellen und in der Methode onbind(intent) zurückgeben. Diese wird immer bei einem Aufruf von bindservice angestoßen. Das Kommunikationsprotokoll sollte per Konvention in der Android Interface Definition Language (AIDL) beschrieben werden, allerdings ist dies keine zwingende Voraussetzung. Da die Spezifikation von AIDL im weiteren Verlauf dieser Arbeit keine Rolle spielen wird, soll an dieser Stelle auf eine Einführung verzichtet werden. Wie bei allen Bestandteilen des SDK findet sich auch hier eine schrittweise Erklärung unter [And10d]. Um die Verbindung zu einem Service beenden zu können, ruft man mit der zu beendenen Verbindung als Übergabeparameter die Methode unbindservice(serviceconnection) auf. Existieren keine weiteren Bindungen zum Service mehr, wird an diesem die Methode onunbind aufgerufen, um die Möglichkeit zu haben, darauf zu reagieren. 2.8 Intents Wie bereits beschrieben, werden Intents immer dann benötigt, wenn eine Komponente eine andere starten möchte. Die Grundidee hierbei ist, dass alle Komponenten wiederverwendbar sein sollen. Anstatt also jeweils eine Activity explizit selbst aufzurufen, wird in einem Intent lediglich mitgeteilt, welche Art von Aufgabe erledigt werden soll. Das System lauscht dann auf solche Intents und sucht in allen Applikationen nach einer auf die Anforderung passenden Komponente, die dann gestartet wird. Auf diese Weise soll verhindert werden, dass häufig verwendete Standardfunktionalitäten immer und immer wieder neu implementiert werden müssen. Außerdem wäre es für einen Entwickler unzumutbar, immer alle Komponenten explizit benennen zu können. Mag das bei Systemfunktionen noch denkbar sein, ist der Zugriff auf nachträglich installiere Zusatzsoftware ohnehin nicht möglich, da sich der Programmierer nie sicher sein kann, dass diese auf allen Geräten vorhanden ist. Intents werden immer an einen Context gereicht. Diese Schnittstelle stellt alle Informationen der Anwendungsumgebung zur Verfügung. Alle Komponenten sind Subklassen einer Context- Implementation. Elementen des User-Interfaces muss ebenfalls der Kontext bekannt sein, in dem sie angezeigt werden. Über den Kontext wird jegliche Kommunikation mit dem System, seinen internen Services und globalen Einstellungen abgewickelt. Grundsätzlich werden zwei Arten von Intents unterschieden: So genannte explizite Intents, denen

21 2.8. INTENTS 15 ausdrücklich der Name einer zu startenden Komponente mitgeteilt wird, und implizite Intents, denen lediglich eine Aufgabenbeschreibung übergeben wird, anhand derer eine geeignete Komponente vom System ausgewählt wird. Um eine spezielle Komponente zu nutzen, muss ein Entwickler einen expliziten Intent verwenden. Diesem wird mitgeteilt, welche Komponente genau er aufrufen soll. Gewählt wird die Komponente über ihren Klassennamen, den man entweder im Konstruktor Intent(Context, Class) übergeben kann oder über die Methode setcomponent(componentname) setzt. ComponentName besteht hierbei aus einem Paket und einer Klasse. Der Kontext im Konstruktor dient ebenfalls dazu, das Paket der Komponente zu ermitteln. Sollen der Komponente zusätzliche Informationen übergeben werden, kann man den Intent um ein Datenbündel erweitern. Dieses Bundle kann einfache Schlüssel-Wert-Paare verwalten, wobei die Werte zum einen primitive Datentypen sein können, aber auch serialisierbare Objekte. Diese Einschränkung auf Serialisierbarkeit ist nötig, da Bundles auch eingesetzt werden können, um Daten über Ströme auszutauschen. Explizite Intents werden hauptsächlich eingesetzt, wenn anwendungsintern eine neue Komponente gestartet werden soll, zum Beispiel ein weiteres Fenster, in dem der Nutzer Eingaben tätigen kann, die dann wieder an die aufrufende Activity übermittelt werden sollen. Möchte der Programmierer bestimmte Daten verwalten (Kontaktdaten, Internetseiten oder Telefonanrufe), eignen sich die expliziten Intents nicht mehr, weil der Entwickler die verantwortlichen Klassen nicht kennt. Bei impliziten Intents entscheidet das System, welche installierte Komponente den Anforderungen des Entwicklers am ehesten gerecht wird. Die dafür nötigen Daten werden in zwei Teilen beschrieben: Zum einen erhält der Intent einen String, der die auszuführende Aktion beschreibt, desweiteren wird über einen Uniform Resource Identifier (URI) beschrieben, welche Daten von dieser Aktion betroffen sind. Zusätzlich kann man die Aktion durch eine Kategorie weiter spezifizieren, außerdem können in einem Bundle weitere Informationen übergeben werden. Eine Reihe von Standard-Aktionen ist in der API bereits vordefiniert. Diese Handlungen umfassen die meisten alltäglichen Tätigkeiten, zum Beispiel die Anzeige von Daten, deren Änderung und Versendung. Definiert ein Entwickler für seine Komponenten eine neue Aktion, so muss er dabei wieder darauf achten, dass deren Name eindeutig gewählt wird. Der Aufbau der URI hängt immer von den zu übertragenden Daten ab. Übliche URIs sind hierbei Telefonnummern (tel:), Internet-Adressen (http: und ftp:), -Adressen (mailto:) oder die Kontaktdaten des Geräts (content://contacts/people/). Abseits des für URIs geltenden Standards bleibt es hierbei den jeweiligen Entwicklern überlassen, für ihre Art der Daten einen entsprechenden Vertrag über die Konfiguration der URI bereitzustellen. Durch die URI wird auch der MIME-Typ der zu übertragenden Daten festgelegt. Alternativ kann auch im Intent explizit vermerkt werden, von welchem Typ die Daten sind. Über die Kategorie lässt sich die Art des Intents noch näher beschreiben. Auf diese Weise lässt sich unter anderem angeben, ob die aufzurufende Komponente für Testzwecke gedacht ist oder in der Lage ist, auch zu arbeiten, wenn das Gerät an eine Docking-Station angeschlossen ist. Wie

22 16 KAPITEL 2. ANDROID auch bei den Actions existieren bereits vorgefertigte Kategorien. Es ist möglich, einem Intent mehrere Kategorien zuzuweisen. Das Auflösen impliziter Intents zur Ermittlung passender Komponenten übernimmt das Betriebssystem. Hierfür wertet der PackageManager Aktion, Typ und Kategorien des Intents aus. Diese vergleicht er mit den Daten, die bei der Komponente im Manifest notiert wurden Intent-Filter Auf welche Intents eine Komponente reagieren soll, muss bereits im Manifest vermerkt werden. Grundsätzlich kann sie immer über explizite Intents angesprochen werden, bei impliziten muss sie einen passenden Filter definieren. Innerhalb des activity-elements kann eine beliebige Menge von intent-filter-tags definiert werden. Darin werden die Elemente action, category und data angegeben, welche zu den entsprechenden Angaben im Intent korrespondieren. Jeder Intent-Filter wird dabei unabhängig von den anderen überprüft. Jeder Filter muss auf mindestens eine Action reagieren, es können jedoch auch mehrere angegeben werden. Nur wenn ein action-tag im Filter mit der Aktion im Intent übereinstimmt, kommt die Komponente für eine Ausführung in Frage. Nach der Action werden die Kategorien von Intent und Filter verglichen. Beide können eine beliebige Menge von Kategorien beinhalten, die des Intents muss jedoch eine Teilmenge der im Filter definierten darstellen. Beinhaltet der Intent eine Kategorie, die im Intent nicht aufgeführt ist, so gilt der Filter als unpassend. Stimmen Aktion und Kategorien überein, vergleicht das System noch die Daten. Das data- Element ermöglicht die Angabe aller Teile einer URI in entsprechenden Attributen und die Definition des MIME-Typen. Hierbei muss die URI nicht komplett angegeben werden. Der Intent passt zum entsprechenden Filter, wenn sich die URIs entsprechen und auch die MIME-Typen übereinstimmen. Fehlt eine der beiden Angaben im Intent, so darf sie auch im Filter nicht vorhanden sein. Hat eine Komponente einen zum Intent passenden Filter deklariert, der alle vorgenannten Bedingungen erfüllt, so wird sie ausgeführt. Kommen mehrere Komponenten in Frage, erhält der Anwender eine Übersicht über die passenden Komponenten und kann jene auswählen, die er wünscht. Erfüllt kein Filter die Vorgaben des Intents, reagiert das aufrufende Programm mit einer Exception. Einen der wichtigsten Intent-Filter sieht man in Listing 2.1. Die Aktion main mit der Kategorie Launcher steht für eine Activity, die beim Programmstart ausgeführt werden kann.

23 2.9. PERSISTENZ Persistenz Die Speicherung von Daten kann unter Android auf mehrere Weisen erfolgen. Wie bei normalen Java-Applikationen kann man auch hier auf das Filesystem zugreifen. Desweiteren beinhaltet das Software Development Kit mit SQLite ein integriertes Single-User-Datenbank-Managementsystem. Speziell für Android existiert außerdem noch das System der SharedPreferences, mit dem man Daten einfach über ein Framework persistieren kann. All diese Möglichkeiten bieten in verschiedenen Situationen unterschiedliche Vorzüge. Der Zugriff auf das Dateisystem bietet sich für die Speicherung kleiner, aber dennoch komplexer Datenmengen an. Von einer Applikation erzeugte Dateien werden grundsätzlich im eigenen Verzeichnis abgelegt und sind standardmäßig auch nur für die eigene Anwendung sichtbar. Um auf eine Datei Zugriff zu erhalten, bedient man sich der Methoden openfileoutput(string) und openfileinput(string), die jeweils einen Dateinamen erwarten. Das manuelle Erstellen eigener Streams für den Zugriff auf den internen Gerätespeicher ist in den Sicherheitseinstellung des Systems nicht vorgesehen und wird bei Zugriff mit entsprechenden Exceptions geahndet. Über ein Flag, welches der Output-Methode übergeben wird, kann eingestellt werden, ob die erzeugte Datei nur für die eigene Anwendung oder auch für externe Programme sichtbar sein soll. Um auf ein externes Speichermedium wie eine SD-Karte zu schreiben, gibt es Methoden, die den Status des Laufwerks abfragen und Zugriff auf das dortige Dateisystem ermöglichen. An dieser Stelle ist der Entwickler selbst verantwortlich, entsprechende Dateien und Streams zu erzeugen. Da externe Datenspeicher im Allgemeinen austauschbar sind, stellt Android hier keinerlei Sichtbarkeits- und Zugriffsverwaltung zur Verfügung. Dateien auf einer SD-Karte sind prinzipiell von allen Applikationen lesbar, ob aber auf das Medium überhaupt zugegriffen werden kann, muss vom Programmierer vor jedem Zugriff explizit überprüft werden. Der externe Speicher ist auch der Ort, an dem gemeinsam verwendete Daten abgelegt werden sollen. Neuere API-Versionen bringen hier bereits eine vorgesehene Verzeichnisstruktur mit, so dass verschiedene Dateitypen in bestimmte, vorgegebene Verzeichnisse abgelegt werden können, um systemweit auch in den betriebssystem-eigenen Applikationen verwendet zu werden. Es ist ebenso möglich, temporäre Dateien abzuspeichern. Hierfür gibt es zum einen spezielle Cached Files sowohl auf dem internen als auch auf dem externen Speicher. Das System überwacht hierbei den internen Speicher und löscht solche temporären Dateien, wenn nötig, selbständig. Auf dem externen Speicher sollte dies der Programmierer verwalten, hier werden die Daten erst bei Programmdeinstallation automatisch entfernt. Außerdem ist seit kurzem auch ein Verzeichnis auf externem Speicher explizit ausgezeichnet, um dort anwendungsbezogene Daten zu speichern. Dieses wird ebenfalls bei einem Entfernen des Programms gelöscht. Größere Datenmengen, speziell wenn sie gut strukturiert sind, sollten in einer Datenbank abgelegt werden. Android stellt mit SQLite standardmäßig ein Datenbank-Managementsystem zur Verfügung, das den SQL92-Standard weitgehend unterstützt. Im Gegensatz zu anderen Datenbanksystemen ist die SQLite-Version jedoch ausschließlich auf einen Einzelnutzer-Betrieb ausge-

24 18 KAPITEL 2. ANDROID richtet, so dass keinerlei Rechteverwaltung über das DBMS erfolgen kann. Eine solche ist jedoch auch nicht nötig, da jede Datenbank ausschließlich für eine einzige Applikation angelegt wird. Ein direkter Datenaustausch zwischen mehreren Anwendungen über eine Datenbank ist also nicht vorgesehen. Weil im weiteren Verlauf der Arbeit keine Datenbank verwendet wird, soll hier auf eine genauere Einführung verzichtet werden. Eine Beschreibung der zugehörigen Objekte und Query-Strukturen findet sich in der API-Referenz. Für die Speicherung kleinerer Datenmengen stellt Android die so genannten Shared Preferences zur Verfügung. Diese werden vom System verwaltet und können einfach über entsprechende Objekte manipuliert werden, ohne dass sich der Entwickler um die eigentliche Speicherung kümmern muss. Wie der Name schon sagt, dienen die Preferences in erster Linie zum Speichern von Programmeinstellungen. Dennoch können auch einfache Daten hier gespeichert werden. Wichtig ist dabei, dass die Shared Preferences lediglich einfache Datentypen unterstützen, also Primitive und Strings. Zum Speichern von Objekten oder Tupeln eignen sie sich nicht. Abgelegt wird hierbei in einer Map immer ein Schlüssel-Wert-Paar. Applikationsweit gespeicherte Daten lassen sich über den Kontext der Anwendung mit Hilfe der Methode getsharedpreferences(string, int) abfragen. Das erhaltene Objekt liefert über datentypbezogene get-methoden jeweils den Wert zu einem bestimmten Schlüssel. Um die Daten der SharedPreferences zu ändern, muss man sich einen zugehörigen Editor geben lassen. Dieser hat wiederum für jeden Datentypen eine passende put-methode und kann ebenso Schlüssel löschen. Änderungen müssen dabei jeweils mit einem commit-aufruf festgeschrieben werden. Der Zugriff auf Preferences kann auch über Komponenten erfolgen. Diese stellen dem Entwickler Möglichkeiten zur Verfügung, Standardeinstellungen einzulesen und diese auf problemlose Weise über eine vorgegebene Benutzeroberfläche zu ändern. Bei Betrachtung der eigenen Implementation wird dies später noch genauer vorgestellt Content Provider Eine besondere Komponente zum Austausch von Daten ist in Android der Content Provider. Dieser ermöglicht es auf eine standardisierte Weise, Informationen zwischen verschiedenen Applikationen auszutauschen. Dabei abstrahiert er von der unterliegenden Datenhaltung und stellt ein einheitliches Interface für die Abfrage der Daten zur Verfügung. Soll eine Applikation einen Content Provider zur Verfügung stellen, wird dieser, wie andere Komponenten auch, im Manifest angekündigt. Die eigentliche Kommunikation mit dem Provider erfolgt nicht direkt. Stattdessen wird ein Content Resolver benutzt, der bereits die nötigen Fertigkeiten mitbringt, die für eine Kommunikation zwischen verschiedenen Prozessen nötig ist. Seine Daten stellt der Content Provider in Form einer

25 2.10. RESSOURCEN 19 URI zur Verfügung, die er genau spezifizieren muss, wobei das Schema immer vom Typ content sein muss. Ähnlich zur SQL-Syntax existieren immer CRUD-Methoden zum Einfügen, Abfragen, Ändern und Löschen der vorgehaltenen Daten. Da auf einen Einsatz von Content Providern im Rahmen dieser Arbeit verzichtet wurde, soll an dieser Stelle auf die genauere Betrachtung der Syntax für die Operationen verzichtet werden. Generell ähnelt diese SQL, hat allerdings zusätzliche Syntax-Bedingungen. Genauere Informationen finden sich im Entwicklerhandbuch unter [And10c] Ressourcen Um die Verwaltung und Wartung von Anwendungen zu verbessern, bietet Android die Möglichkeit, statische Inhalte aus dem eigentlichen Quellcode auszulagern und in eigenen Dateien strukturiert zu speichern. Diese Daten werden innerhalb der Anwendung in einem Ordner res abgelegt. Grundsätzlich unterscheidet Android dabei zwischen verschiedenen Ressourcentypen. Diese werden nach Konvention in entsprechenden Unterverzeichnissen abgelegt. Es können sowohl Bilder (in res/drawable/) als auch sonstige Binärdateien (res/raw/) abgelegt werden. Ebenso können feste GUI-Layouts (res/layout/) oder Menüstrukturen (res/menu/) ausgelagert werden. Auch für einfache Daten gibt es ein Verzeichnis (res/values/). Hier können Strings, Arrays, Farben und weiteres vorgehalten werden. Außer den zuerst genannten Binärdaten werden alle weiteren in eigenen XML-Dateien abgelegt. Listing 2.2 beinhaltet einige Standardeinträge für Ressourcendateien. Diese sind normalerweise nach Typ in verschiedenen Dateien geordnet. Mit dem color-tag lassen sich Farben definieren. Diese erhalten wie alle Ressourcen als Attribut einen Namen. Der jeweilige Wert, hier ein ARGB- Hexadezimal-Wert, steht innerhalb des Elements. Damit vergleichbar ist die string-ressource, während Arrays noch geschachtelt die einzelnen Einträge beinhalten. In diesem Fall handelt es sich, wie der Elementname andeutet, um ein Arrays von Strings. Grundsätzlich ist der array-tag zuständig, ein Array zu kreieren, das wiederum Ressourcen beinhaltet. <? xml version =" 1.0 " encoding ="utf -8"?> < resources > <color name =" red "># FFFF0000 </ color > < string name =" app_name ">Guide Me </ string > <string - array name =" languages "> <item > Englisch </ item > <item > Deutsch </ item > </ string - array > </ resources > Listing 2.2: Ressourcen-Typen Android bietet die Möglichkeit, Ressourcen für bestimmte Gerätekonfigurationen zu überschreiben. Auf diese Weise bietet sich dem Entwickler eine einfache, effektive Möglichkeit, zum Bei-

26 20 KAPITEL 2. ANDROID spiel auf geänderte Spracheinstellungen auf einem Gerät oder eine andere Bildschirmauflösung zu reagieren. Solche alternativen Ressourcen werden in eigenen Verzeichnissen abgelegt. Soll ein Programm beispielsweise in deutscher und englischer Sprache verfügbar sein, so lagert man als Programmierer alle sichtbaren Strings in die strings.xml im values-verzeichnis aus. Die alternative Konfiguration, hier englisch, wird dann in einem Ordner values-en abgelegt. Das System sucht bei der Auswertung solcher Ressourcen immer nach der passendsten Konfiguration und greift anschließend auf die allgemeinen Daten zu. Ein englischsprachiges Gerät würde also zunächst den Ordner values-en nutzen, findet es dort einen Eintrag nicht, sucht es im Standardordner values. Ein Gerät mit französischer Spracheinstellung würde nach dem Ordner values-fr suchen und dann auf values zugreifen. Solche Ressourcen-Spezialisierungen sind nicht allein auf die Sprache beschränkt (die Länder- Codes richten sich dabei nach dem entsprechenden ISO-Standard), sondern umfassen zum Beispiel auch die Bildschirmgröße oder -auflösung. Auf die Verfügbarkeit von Hardwarekomponenten wie Touchscreen oder ausklappbare Tastatur kann mit entsprechenden Werten ebenfalls zurückgegriffen werden. Geschachtelte Konfigurationsbeschreibungen sind ebenso möglich. Android setzt hier eine bestimmte Hierarchie voraus, die auch bei der Auswahl der richtigen Einstellung genutzt wird. Eine genauere Beschreibung des Vorgehens findet sich im entsprechenden Kapitel des Entwicklerhandbuchs unter [And10e]. Für jede Ressource, die der Entwickler schreibt, legt der Compiler in einer generierten Datei eine eindeutige ID ab. Die Datei gen/r.java enthält dabei für jede Art von Ressource eine innere Klasse und für jedes Element wiederum eine ID. package de. monarchy. guideme ; public final class R { public static final class array { public static final int language =0 x7f ; } public static final class color { public static final int red =0 x7f ; } public static final class string { public static final int app_ name =0 x7f ; } } Listing 2.3: Generierte Ressourcen-Datei R.java Die Verwendung der ausgelagerten Ressourcen innerhalb des Quelltextes erfolgt wiederum über den Context der Applikation. Über die Methode getresources() erhält man ein Objekt, mit dem auf die zur Applikation gehörenden Ressourcen zugegriffen werden kann. Dieses besitzt für jeden Ressourcentyp eine entsprechende get-methode, der man dann die ID der gewünschten Ressource übergeben kann. Auch Methoden, die ohne den Umweg über das Resources-Objekt auskommen, gibt es an mehreren Stellen der API.

27 2.11. VIEWS 21 Es ist außerdem möglich, bereits innerhalb der Ressourcen auf andere Ressourcen zuzugreifen. Eine solche Referenz beginnt immer mit gefolgt von einem Paketnamen plus Doppelpunkt, falls auf eine Ressource außerhalb der eigenen Applikation zugegriffen werden soll. Danach folgt der Typ der Ressource und ihr Name, zum 2.11 Views Die Darstellung des User Interfaces erfolgt in Android über von der Klasse View abgeleitete Objekte. Diese lassen sich dabei in zwei Gruppen gliedern: Zum einen die View-Komponenten, die eine wirkliche Anzeige-Fläche aufweisen, zum anderen die ViewGroups, die für das Layout verantwortlich sind und wiederum weitere Views aufnehmen können. Insgesamt folgt das Konzept bei Android trotz einer eigenen Objekthierarchie dem bei Java. Die Android-API weist, wie das JDK, mehrere verschiedene Typen von Layouts auf. Darunter fallen das FrameLayout, das in einem Bereich des Bildschirms mehrere Komponenten in einem Stapel verwaltet, das LinearLayout, das Komponenten strikt horizontal oder vertikal nebeneinander anordnet und das RelativeLayout, bei dem die enthaltenen Views in Abhängigkeit voneinander platziert werden. Die Positionierung der Kinder in einem Layout erfolgt über Layout-Parameter. Jede Klasse definiert hierbei ihre eigene innere Klasse LayoutParams, die je nach Layout verschiedene Parameter benötigt. <? xml version =" 1.0 " encoding ="utf -8"?> < RelativeLayout xmlns:android =" http: // schemas. android. com / apk / res / android " android: layout_ width =" fill_ parent " android: layout_ height =" wrap_ content " > < ImageButton android: layout_ width =" wrap_ content " android: layout_ height =" wrap_ content " android:src / ic_menu_search " android: layout_ alignparenttop =" true " android: layout_ alignparentright =" true " android:id search_button "> </ ImageButton > < EditText android: hint / routing_ search " android: layout_ width =" fill_ parent " android: lines =" 1" android: inputtype =" text " android:id search_text " android:layout_toleftof search_button " android: layout_ height =" wrap_ content " android: layout_ centervertical =" true " android: layout_ centerinparent =" true " > </ EditText > </ RelativeLayout > Listing 2.4: Layout in XML Einfache Komponenten verhalten sich insgesamt vergleichbar zu üblichen AWT- oder Swing- Komponenten. Sie werden definiert durch eine Größe, eine Position und nach Möglichkeit durch

28 22 KAPITEL 2. ANDROID Abbildung 2.4: Sucheingabefeld wie in Listing 2.4 definiert Innen- und Außenabstände. Neben bekannten Views wie Buttons oder Textfeldern gibt es eine Reihe vorgefertigter Views für Tabellen, Listen oder Datumsauswahlen. Erzeugt werden alle Views immer an einem bestimmten Context. Welche View eine Activity darstellen möchte, wird durch die Methode setcontentview(view) festgelegt. Grundsätzlich kann dies sowohl eine View als auch eine ViewGroup sein. Es besteht zudem mit addcontentview(view) die Möglichkeit, einer Activity zusätzliche ContentViews zuzuweisen. Views müssen nicht zwingend im Quelltext zusammengebaut werden. Für starre Benutzeroberflächen besteht die Möglichkeit, diese bereits als XML-Ressource vorzudefinieren. Dabei können sowohl einzelne Komponenten als auch große Hierarchien erzeugt werden, die wiederum im Quelltext noch weiter ergänzt werden können. Listing 2.4 beschreibt ein einfaches Layout mit einem Texteingabefeld und einem Suchbutton, wie es auch in Abbildung 2.4 dargestellt wird. XML-Elemente werden immer ebenso benannt wie die Klassen, die sie repräsentieren. Jedes Element hat dabei eine Reihe von Attributen, die das Layout beeinflussen. Die Attribute android:layout_width bzw....height definieren dabei beispielsweise die Breite und Höhe. Auch die weiteren Attributnamen sind entsprechend eindeutig benannt. Im Listing erkennt man zudem den Zugriff auf eine andere Ressource Besonders hervorzuheben ist das Attribut android:id, über das man einzelnen Komponenten einer größeren View einen eindeutigen Namen zuweisen kann. Auf diese Weise lassen sie sich wiederum über XML oder im Quelltext referenzieren. Dass eine neue ID für dieses Element angelegt werden soll, wird dabei durch ein + gekennzeichnet Menüs Android unterscheidet in der Anzeige zwischen zwei Arten von Menüs. Das Options-Menü ist das Hauptmenü einer Activity. Es wird auch über selbige registriert und ist über eine entsprechende Taste am Mobilgerät aufrufbar. Die Anzeige gliedert sich dabei in das grafische Icon-Menü und das erweiterte Menü. Im Icon-Menü können insgesamt sechs Einträge aufgenommen werden, die jeweils mit einem Beschreibungstext und einem Icon versehen werden können. Soll das Menü weitere Einträge beinhalten, werden diese lediglich in einer textuellen Übersicht im erweiterten Menü aufgeführt. Um ein solches Menü zu erzeugen, muss der Entwickler in der entsprechenden Activity die Callback-Methode oncreateoptionsmenu(menu) überschreiben, in der einem gegebenen Menü entsprechende Items hinzugefügt werden. Die Reaktion auf eine Item-Auswahl erfolgt ebenfalls über einen Callback, die Methode onoptionsitemselected(menuitem). Hier lässt sich die ID des

29 2.11. VIEWS 23 Items ermitteln und darüber eine entsprechende Aktion ausführen. Alternativ besteht die Möglichkeit, einen Intent an ein Menü-Item zu binden, der bei Wahl des entsprechenden Eintrages ausgelöst werden soll. Hierfür dient die Methode setintent(intent) am MenuItem. Abbildung 2.5: Applikation mit angezeigtem Options-Menü Eine weitere Menüart ist das Kontextmenü. Dieses wird durch einen langen Druck auf eine View-Komponente angezeigt, ist allerdings nicht exklusiv an diese gebunden. Die Registrierung des Menüs wird über die Activity geregelt, die für diese View zuständig ist. Hierfür gibt es, vergleichbar zum Options-Menü, die Callback-Methode oncreatecontextmenu(contextmenu, View, ContextMenuInfo). Dieser wird neben dem Kontextmenü, das zu füllen ist, auch die betroffene View übergeben. Müssen zur View noch weitere Daten übertragen werden (zum Beispiel der ausgewählte Eintrag einer ListView), geschieht dies über die ContextMenuInfo. Damit überhaupt ein Kontextmenü angezeigt wird, muss diese View zuvor bei der Activity mit der Methode registerforcontextmenu(view) eingetragen werden, um auf einen langen Druck zu reagieren. Die Information, dass das Kontextmenü aufgerufen und ein Eintrag gewählt wurde, geschieht dann über das Callback oncontextitemselected(menuitem). <? xml version =" 1.0 " encoding ="utf -8"?> <menu xmlns:android =" http: // schemas. android. com / apk / res / android "> <item android:id start_routing " android: title / main_ menu_ routing " android: icon android: drawable / ic_ menu_ directions " android: titlecondensed / main_ menu_ routing " > </ item > <item android:id restart_routing " android: title / main_ menu_ rerouting " android:icon / ic_menu_share " android: titlecondensed / main_ menu_ rerouting " > </ item > </ menu > Listing 2.5: Menüs in XML

30 24 KAPITEL 2. ANDROID Neben diesem generellen Kontextmenü können Views auch über eigene Erweiterungen verfügen. Diese können in der View-eigenen Methode oncreatecontextmenu(contextmenu) angegeben werden. Wie Layouts können auch Menüs bereits statisch in XML formuliert werden. Listing 2.5 gibt hierfür ein Beispiel mit zwei Menüeinträgen. Man kann hier sowohl eine textuelle Kurzbeschreibung als auch ein Icon für die Anzeige angeben. Wie jeder View-Komponente sollte auch einem Menü-Item immer eine ID zugewiesen werden, um im Quelltext darauf verweisen zu können. Es sollte außerdem über einen anzuzeigenden Titel und eine Kurzfassung dieses Titels verfügen. Für Einträge im Optionsmenü kann zudem ein Icon angegeben werden. Menüeinträge können ebenfalls mit einem Auswahlfeld versehen werden. Desweiteren gibt es die Möglichkeit, mit group-elementen Einträge zu gruppieren. Ein Item kann außerdem wieder ein Submenü beinhalten, indem man ein weiteres menu-element darin verschachtelt Weitere Android-Bestandteile Neben Activities, Services und Content Providern gibt es noch eine weitere Komponente in Android. Weil sie im Rahmen dieser Arbeit allerdings nicht verwendet wurde, folgt hier der Vollständigkeit halber nur eine kurze Einführung, da sie durchaus von Interesse sein kann. Mit Broadcast-Receivern ist eine Applikation in der Lage, auf Nachrichten des Betriebssystems oder anderer Anwendungen zu reagieren. Sie besitzen weder eine Benutzeroberfläche, noch führen sie längerfristig Code aus. Ihre einzige Aufgabe ist es, Broadcasts in Form von Intents zu empfangen und zu verarbeiten. Grundsätzlich gibt es zwei Möglichkeiten, einen Broadcast-Receiver zu aktivieren. Innerhalb einer Applikation kann über den Context die Methode registerreceiver(broadcastreceiver, IntentFilter) angesprochen werden, außerdem kann ein Receiver auch immer bereits im Manifest vermerkt werden. Für dieses Vorgehen steht das receiver-element zur Verfügung, das ähnlich wie die schon bekannten Komponenten aufgebaut ist. Mit Hilfe von Intent-Filtern kann auch hier eingestellt werden, auf welche Art von Events der Broadcast-Receiver reagieren soll. Beim Senden von Broadcasts wird zwischen normalen und ordered Broadcasts unterschieden. Während bei einem Standard-Broadcast alle Receiver asynchron angesprochen werden, hält das System beim geordneten eine Reihenfolge ein, so dass wichtigere Receiver in der Lage sind, einen solchen Broadcast exklusiv zu beanspruchen und die Weitergabe an weniger wichtige Receiver zu verhindern oder ihnen ein Ergebnis zu übergeben. Die Relevanz eines Receivers kann über sein entsprechendes Attribut priority gesteuert werden. Broadcast-Receiver besitzen nur eine sehr begrenzte Lebensdauer. Empfangen sie einen Intent, wird ihre Methode onreceive(context, Intent) aufgerufen. Ist diese Methode durchlaufen, ist das Objekt nicht mehr ansprechbar. Das bedeutet insbesondere, dass man auf jede Art asynchronen Nachrichtenaustauschs verzichten soll, da Antworten ins Leere laufen werden.

31 2.12. WEITERE ANDROID-BESTANDTEILE 25 Durch solche Einschränkungen sind Broadcast-Receiver in ihrer Fähigkeit, mit dem Nutzer zu interagieren, deutlich eingeschränkt. Eine Möglichkeit, die durch den Broadcast gewonnenen Informationen an den Benutzer weiterzureichen, stellt der Notification-Manager dar. Diese Schnittstelle ermöglicht es der Applikation, eine Information in der systemweiten Statusleiste abzulegen. Auf diese Weise kann man den Nutzer auf Basisinformationen aufmerksam machen und eine Komponente starten, wenn dieser die Notification auswählt. Neben der bloßen textuellen Anzeige können auch weitere Reize gesetzt werden.

32 Kapitel 3 Programmgrundlagen Durch die Aufgabenstellung, eine Navigationssoftware zu implementieren, ist der Funktionsumfang der zu fertigenden Applikation eng umrissen. Nachdem der Benutzer sich für einen gewünschten Zielort entschieden hat, muss die Anwendung einen Weg dorthin ermitteln und den Anwender während der Reise durch Instruktionen zum Ziel führen. Eine spezielle Zusatzanforderung an das Programm ist dadurch gegeben, dass neben der eigentlichen Wegbeschreibung auch die Möglichkeit bestehen soll, in der aktuellen Umgebung des Nutzers Sehenswürdigkeiten auszumachen, die von potentiellem Interesse für diesen sein könnten. Aufgabe der Applikation ist es, die Wegführung so umzusetzen, dass der Anwender durch sie in der Lage ist, ohne eigene weitere Orientierungen sicher zum Ziel zu finden. Hierfür soll das Programm nicht nur dynamisch auf Informationen über die Position des Navigationsgerätes zugreifen, sondern ebenso die Blickrichtung des Benutzers für die Bereitstellung der Hilfen heranziehen. Zur Orientierung kann es sich hierbei als sehr hilfreich erweisen, die unterstützenden Hinweise in die natürliche Umgebung einzubetten, anstatt sie davon losgelöst bereitzustellen. Die zu entwerfende Anwendung soll dieses Konzept umsetzen, indem sie auf Mittel der erweiterten Realität oder Augmented Reality zurückgreift. 3.1 Augmented Reality Der Begriff erweiterter Realität wird seit Anfang der 90er Jahre mit wachsender Beliebtheit eingesetzt, um Arten von Vermischung realer und virtueller Sinneseindrücke zu beschreiben. Eine allgemein gültige Definition der Augmented Reality gibt es hingegen bis heute nicht. Einige Abhandlungen zum Thema vermischter Realität stammen von Paul Milgram, der im Jahre 1994 in einem Artikel eine Abgrenzung verschiedener Arten der Vermischung von Realität und Virtualität vorgenommen hat [MTUK94]: 26

33 3.1. AUGMENTED REALITY Monitor-based (non-immersive) AR displays, upon which computer graphic (CG) images are overlaid. 2. Same as 1, but using immersive HMD [head mounted displays]-based displays, rather than WoW [window of the world] monitors. 3. HMD-based AR systems, incorporating optical see-through (ST). 4. HMD-based AR systems, incorporating video ST. 5. Monitor-based AV systems, with CG world substratum, employing superimposed video reality. 6. Immersive or partially immersive (e.g. large screen display) AV systems, with CG substratum, employing superimposed video or texture mapped reality. 7. Partially immersive AV systems, which allow additional real-object interactions, such as reaching in and grabbing with one s own (real) hand. Im weiteren verdeutlicht Milgram Unterschiede dieser Klassen anhand von verschiedenen Sichtweisen. So betrachtet er die Frage, ob die zugrunde liegenden Daten real oder virtuell sind, ob ein direkter Blick auf die Realität geworfen oder nur ein Abbild betrachtet wird, der Blickwinkel ego- oder exozentrisch ist und die Einpassung der Zusatzinformationen blickwinkelgetreu erfolgt oder nicht. Abbildung 3.1: Das Reality-Virtuality-Kontinuum nach Milgram [MTUK94] Milgram fasst Realität und Virtualität als zwei gegenüberliegende Enden eines Kontinuums auf, deren Übergangsbereich er als gemischte Realität bezeichnet. Wie in Abbildung 3.1 zu sehen, ordnet er die erweiterte Realität dabei in der Nähe der normalen Realität ein. Diese Sichtweise greift unter anderem Ronald Azuma in einem Artikel im Jahre 1997 auf. Er definiert auf dieser Basis den Begriff Augmented Reality wie folgt [Azu97]: To avoid limiting AR to specific technologies, this survey defines AR as systems that have the following three characteristics: 1. Combines real and virtual 2. Interactive in real time 3. Registered in 3-D Als Augmented Reality werden nur Systeme angesehen, die alle drei Axiome erfüllen. Ein Nachteil dieser Definition ist dabei, dass sie sich hauptsächlich an der Erweiterung der visuellen Eindrücke orientiert. Allerdings ist es durchaus auch denkbar, Sinne wie das Hören und Fühlen zu erweitern.

34 28 KAPITEL 3. PROGRAMMGRUNDLAGEN In der Literatur findet sich eine Reihe von Beispielen für erweiterte Realität, so zum Beispiel auch in Azumas Artikel. Eines der verbreitetsten Beispiele und wohl auch eine der ersten Anwendungen der erweiterten Realität ist das so genannte Head-Up-Display. Hierbei werden Piloten Informationen zum Beispiel zur Geschwindigkeit oder Ausrichtung der Flugzeugnase direkt auf die Scheibe ihres Cockpits projiziert. Erste Umsetzungen waren militärischer Natur, inzwischen kann der Begriff Pilot bis zum Autofahrer ausgedehnt werden, so verbaut zum Beispiel BMW in seinen Fahrzeugen solche Displays [BMW10]. Azuma führt ebenso Beispiele für die Kombination von Realität und virtuellen Daten an, die nach dieser Definition nicht als Augmented Reality gelten. So legt er dar, dass ein Film beispielsweise nicht mit Interaktivität ausgestattet sei. Augmented Reality ist nicht beschränkt auf das Hinzufügen von Informationen. Es ist ebenso denkbar, dass die Virtualisierung darin besteht, vorhandene Objekte in der Realität auszublenden. Hier kann wiederum der Autofahrer als Beispiel dienen: Blickt dieser in den Rückspiegel, könnte mit Hilfe von Kameras ein Sichtfeld ohne toten Winkel erscheinen, auf dem potentielle Gefahrenquellen hervorgehoben werden. 3.2 Kartographische Grundbegriffe Im Rahmen dieser Arbeit werden einige Begriffe der Geographie und Geoinformatik benötigt. Um eine eindeutige Terminologie zu schaffen und Unklarheiten auszuräumen, sollen an dieser Stelle einige Ausdrücke erklärt werden. Wichtigster Begriff sind die geographischen Koordinaten oder kurz Geokoordinaten. Diese dienen dazu, eine Position auf der Erdoberfläche einfach und eindeutig beschreiben zu können. Jede Koordinate ist dabei ein 2-Tupel, bestehend aus der geographischen Breite und der geographischen Länge. In dieser Arbeit wird zur Beschreibung ein Gradnetz verwendet, wie es bei der allgemeinen Angabe üblich ist. Das bedeutet, die Angaben der geographischen Breite und Länge erfolgen in Grad. die geographische Breite läuft zwischen den Polen, wobei 0 Grad dem Äquator der Erde entspricht und zu den Polen nach Nord und Süd jeweils 90 Grad beschrieben werden. Die Erde umlaufende Kreise ganzzahliger, konstanter geographischer Breite werden auch Breitengrade genannt. Eine wichtige Eigenschaft der Breitengrade ist, dass diese äquidistant sind. Zwei Orte identischer geographischer Länge, die sich in der Breite um ein Grad unterscheiden, haben im verwendeten Bezugssystem eine Entfernung von etwa 111 Kilometern. Im Gegensatz zu Breitengraden sind Längengrade nicht äquidistant. Längengrade beschreiben durch die Pole der Erde laufende Großkreise. 0 Grad entsprechen hierbei dem Großkreis, der durch die Sternwarte im Londoner Stadtteil Greenwich läuft. Der Winkel zwischen diesem Bezugspunkt und der zu beschreibenden geographischen Länge wird jeweils nach Westen oder Osten angegeben, so dass der Längengrad maximal 180 Grad betragen kann. Streng genommen beschreibt die geographische Länge also nur eine Hälfte des Großkreises. Die Angabe der Einträge einer Geokoordinate erfolgt im Allgemeinen auf zwei verschiedene Weisen. Die eine sieht vor, nach der Unterteilung in Grad eine feinere Aufteilung über Minuten- und

35 3.2. KARTOGRAPHISCHE GRUNDBEGRIFFE 29 Sekunden-Angaben zu erreichen. Hierbei wird der Bereich zwischen zwei Längen- oder Breitengraden gleichmäßig in 60 Unterabschnitte, die Minuten, und diese wieder in 60 weitere Teile, die Sekunden, unterteilt. Eine Darstellung der Geokoordinate Osnabrücks läse sich also ( Nord, Ost) und die von Buenos Aires ( S, W). Alternativ wird die Gradzahl einfach als reelle Zahl angegeben. Auch eine Zwischenschreibweise, die weitere Unterteilung der Minute als reelle Zahl zu schreiben, ist besonders in der Luftfahrt verbreitet. Eine Umsetzung in der Informatik ist am einfachsten über eine dezimale Schreibweise zu erreichen. In Bezug auf die Speicherung macht auch die zuvor beschriebene Unterteilung in Nord- und Südhalbkugel beziehungsweise West- und Osthalbkugeln ein Problem. Daher wird häufig vereinbart, Koordinaten südlich des Äquators oder westlich des Nullmeridians mit negativen Zahlen zu kennzeichnen. In dieser, wesentlich maschinenfreundlicheren Schreibweise ist die geographische Koordinate Osnabrücks ( , ) und die von Buenos Aires ( , ). Bei der Positionsbestimmung spielt es eine wichtige Rolle, dass die Erde keine exakte Kugel ist. Nimmt man dieses an, können sich Ungenauigkeiten in der Positionsbeschreibung ergeben, die im Kilometerbereich liegen. Deshalb ist es in der Kartographie von Bedeutung, ein Planetenmodell zu wählen, welches der wirklichen Form der Erde möglichst nahe kommt. Um die Oberfläche der Erde möglichst exakt zu beschreiben, wird daher ein Geoid verwendet. Es kennzeichnet eine Fläche gleicher Schwerkraft über die gesamte Erdoberfläche. Eine erste Approximation erfolgt über ein an den Polen abgeflachtes Ellipsoid. Es gibt eine Vielzahl solcher Ellipsoide, speziell aufgrund der Tatsache, dass im Laufe der Jahre immer genauere Approximationen der Erdform möglich wurden. Die beiden wichtigsten sind dabei das so genannte Bessel-Ellipsoid, das unter anderem in Deutschland für offizielle Karten genutzt wird, und das WGS84-Referenzellipsoid, das für GPS-Messungen zugrunde gelegt wird. Beide unterscheiden sich in ihren Daten kaum, sie veranschlagen jeweils einen Äquatorialdurchmesser von 6378,137 Kilometern. Bei der Abplattung unterscheiden sie sich in der sechsten Nachkommastelle, diese beträgt je etwa 298, Unter Abplattung wird das Verhältnis der Differenz der beiden beschreibenden Achsen zur längeren Ellipsoid-Achse verstanden. Ein weiterer wichtiger Begriff in Bezug auf die Geographie ist die Georeferenzierung. Sie beschreibt das Vorgehen, anhand einer gegebenen Ortsbeschreibung die zugehörige geographische Koordinate zu ermitteln. Häufig wird hierfür auch der englische Begriff, das Geocoding, verwendet. Möglich sind verschiedene Arten von Georeferenzierung. Zum einen kann die Referenzierung über Adressen erfolgen, man weist also einem gegebenen Datensatz eine Adresse zu. Eine solche Adresse muss nicht unbedingt einer Postanschrift entsprechen, auch eine Anschlussstellenbezeichnung einer Autobahn kann in diesem Zusammenhang als Adresse aufgefasst werden. Liegt zudem eine entsprechende Datenbank vor, kann es dann über eine Adresse möglich sein, die genaue Geokoordinate zu bestimmen. Eine andere Art der Georeferenzierung stellt die Möglichkeit dar, einem beliebigen Datensatz direkt eine geographische Koordinate anzuhängen. Auf diese Weise kann ein Objekt einem Ort zugeordnet werden. Das auch Geotagging genannte Verfahren wird zum Beispiel für Fotos oder Internetartikel genutzt. Praktische Verwendung findet auch die Umkehrung der Georeferenzierung, die im Englischen Reverse Geocoding genannt wird. Bei diesem Verfahren ist eine Position durch ihre geographische

36 30 KAPITEL 3. PROGRAMMGRUNDLAGEN Koordinate beschrieben und es soll in einer Datenbank ein Datensatz gefunden werden, der diese Position beschreibt. Hier ist es wie beim Geocoding wiederum möglich, einerseits eine Adresse zur Position zu bestimmen, andererseits aber auch andere Daten wie Fotos. Dieses Konzept wird innerhalb des Programms genutzt werden, um etwa die Wikipedia-Artikel der Umgebung zu ermitteln. Im weiteren Verlauf wird auch der Begriff des Kurswinkels genutzt. Unter diesem versteht man eine Angabe, in welche Richtung ein Objekt in Bezug zu einem anderen liegt. Er wird normalerweise als Gradzahl angegeben und beschreibt die Nordabweichung im Uhrzeigersinn, die eine Strecke zwischen zwei Punkten im Startpunkt hat. Den Kurswinkel kann man generell auf verschiedene Arten angeben. Es ist üblich, eine von zwei speziellen Betrachtungsweisen zu wählen. Die Winkelgleiche verbindet die gegebenen Punkte durch eine Strecke auf der Oberfläche, bei welcher der Kurswinkel auf allen Punkten dieser Strecke bezogen zum Ziel gleich bleibt. Sie wird auch Kursgleiche genannt, da die Kursangabe auf ihr konstant ist. Ihr wissenschaftlicher Fachbegriff ist Loxodrome. Eine Loxodrome, deren Kurswinkel kein Vielfaches von 90 Grad ist, beschreibt auf einer Kugel eine Kurve, die sich beliebig den Polen annähert, diese allerdings nie erreicht. Wie der zur Loxodrome gehörende Kurswinkel berechnet wird, folgt im Abschnitt zur Implementierung. Neben der Loxodrome spielt auch die Orthodrome eine wichtige Rolle in der Navigation. Die Orthodrome stellt die kürzeste Verbindung zweier Punkte auf einer Kugeloberfläche dar. Auch wenn ihre Länge geringer ist als die der Loxodrome, hat sie den Nachteil, dass sich der Kurswinkel entlang der Strecke stetig ändert. Da der zur Luftlinie gehörende Kurswinkel keine Rolle spielen wird, soll an dieser Stelle auf die recht einfache Formel verzichtet werden. 3.3 Anforderungen an den Funktionsumfang Welche grundlegenden Funktionen eine Navigationssoftware enthalten sollte, erscheint auf den ersten Blick klar und übersichtlich. Ein Benutzer sollte in der Lage sein, den gewünschten Zielort zu wählen und anschließend eine Reihe von Instruktionen erhalten, mit deren Hilfe er dieses Ziel erreichen kann. Schnell lassen sich jedoch Ideen finden, mit denen diese elementaren Funktionalitäten erweitert werden können. Zunächst stellt sich die Frage, auf welche Weise der Anwender in der Lage sein soll, ein Streckenziel festzulegen. Intuitiv lässt sich sagen, dass das wahrscheinlichste Vorgehen beinhaltet, dass der Nutzer die Adresse des Zielorts eingibt und diese dann vom Programm als Ziel festgelegt wird. Eine Alternative, die auch in der Anwendung umgesetzt werden soll, wäre, dass der Nutzer zwar die Lage des Zielortes kennt, nicht aber dessen genaue Adresse. Daher soll er die Möglichkeit besitzen, den Zielpunkt des Weges auf einer Karte auszuwählen und danach von der Anwendung dorthin geleitet zu werden. Anschließend muss die Art geklärt werden, auf welche die Applikation ermittelte Daten aufberei-

37 3.3. ANFORDERUNGEN AN DEN FUNKTIONSUMFANG 31 tet. Der einfache Fall wäre hierbei, dass die Routendaten rein statisch vorgehalten werden und nur in Form einer Liste von Instruktionen vorliegen, wie es zum Beispiel bei Routenplanern im Internet der Fall ist. Eine Abstufung hiervon wäre, die gesamte Route nach ihrer Berechnung grafisch in einer Karte einzubetten. Bei der zu entwickelnden Software soll es sich allerdings nicht um einen reinen Planer handeln, das Programm soll ebenso in der Lage sein, den Benutzer dynamisch während des Weges anzuleiten. Es soll also auf den Fortschritt der Reise reagieren können und immer aktuelle Informationen zur kurzfristigen Orientierung bereitstellen. Im Rahmen der Arbeit sollen diese Hilfestellungen auf visuelle Art erfolgen. Eine Anleitung über textuelle oder akustische Signale wäre aber ebenso denkbar. Außerdem soll die Anwendung neben diesen Basisfunktionen auch noch weitere Bestandteile haben. Hierbei soll insbesondere die Möglichkeit erweiterter Realität genutzt werden. Zunächst soll die Führung des Anwenders entlang der Route in das Realbild eingebettet werden. Als Wirklichkeit wird dabei die Darstellung der Realität auf dem Bildschirm des Mobilgerätes gesehen, die ein Kamera-Livebild darstellt. Abbildung 3.2: Use-case-Diagramm der Anwendung Als weiteren Zusatz soll die Applikation Informationen anzeigen können, die nicht direkt mit der Route zusammenhängen. So soll es möglich sein, sich über Sehenswürdigkeiten in einem Umkreis von einigen Kilometern zu informieren. Auch die dafür nötige Visualisierung soll mit Hilfe der

38 32 KAPITEL 3. PROGRAMMGRUNDLAGEN erweiterten Realität umgesetzt werden. Um eine leichte Erweiterbarkeit um weitere Datenquellen zu gewährleisten, soll hierbei eine geeignete Schnittstelle genutzt werden. Aus dieser Programmerweiterung erwächst eine dritte Möglichkeit, über die der Anwender Ziele festlegen kann. Möchte er eine dieser Sehenswürdigkeiten besichtigen, soll es ihm ohne Umwege möglich sein, sie als Bestimmungsort für die Reise festzulegen, ohne einen der bereits beschriebenen Fälle zur Zielpunktbestimmung nutzen zu müssen. Mit Blick auf die Nutzerfreundlichkeit soll es innerhalb der Anwendung einfach möglich sein, bestimmte Darstellungsarten kurzfristig zu deaktivieren, um eventuell die Orientierung zu erleichtern. Der Benutzer hat also die Wahl, ob er die erweiterte Realität sieht, eine Darstellung der Umgebung auf einer Karte oder eine Kombination davon. 3.4 Voraussetzungen an das System Zur Erstellung der Navigationssoftware muss das eingesetzte Mobilgerät einige Grundbedingungen erfüllen, damit die gewünschte Funktionsvielfalt auch umgesetzt werden kann. Diese umfasst auf der einen Seite Mindestanforderungen an das Gerät, was die verbaute Hardware angeht, zum anderen muss aber auch die verwendete API einige Eigenschaften erfüllen. Wichtigste Komponente für die Applikation ist eine im Gerät verbaute Kamera. Diese wird zwingend benötigt, um Augmented Reality verwenden zu können. Spezielle Voraussetzungen muss die Kamera dabei jedoch nicht erfüllen. Weder ist es notwendig, dass ein Blitz vorhanden ist, noch muss ein Autofokus im Einsatz sein, lediglich die Vorschaufunktion muss über die API nutzbar sein. Zusätzlich soll das Programm so entwickelt werden, dass es vom Anwender bequem über wenige Klicks steuerbar ist. Voraussetzung soll hierfür ein berührungsempfindliches Display sein. Durch die Verwendung eines Touchscreens lässt sich zudem die Interaktivität erhöhen, weil der Nutzer so die Möglichkeit hat, direkt in der Szene der erweiterten Realität zu agieren. Weiterhin muss das Gerät über eine dauerhafte Datenverbindung verfügen. Weil es nur schwer möglich ist, ein komplettes Kartenwerk auf einem Mobiltelefon zu hinterlegen, muss sichergestellt sein, dass die Anwendung alle benötigten Grafiken online abrufen kann. Ob und inwiefern die Leistungsfähigkeit der Applikation darunter leidet, wird im Fazit untersucht. Für die Ermittlung von Zusatzinformationen ist es hingegen nahezu unerlässlich, mit dem Internet verbunden zu sein. Da diese, wie schon beschrieben, modular erweiterbar sein sollen, ist es nicht möglich, diese Daten statisch vorzuhalten. Es wird ebenso deutlich, dass hier auch keine Möglichkeit bestünde, wenn man plante, zum Beispiel die gesamte Wikipedia in mehreren Sprachen offline zu speichern. Natürlich benötigt die Anwendung außerdem eine Möglichkeit, die Position von Gerät und Nutzer eindeutig bestimmen zu können. Ohne das ist eine dynamische Wegbeschreibung nicht möglich. Die Art der Positionsbestimmung sollte dabei zwei wichtige Voraussetzungen erfüllen: Sie muss eine hohe Genauigkeit aufweisen, die es der Applikation ermöglicht, den Standort auf wenige

39 3.4. VORAUSSETZUNGEN AN DAS SYSTEM 33 Meter genau zu bestimmen, und sie muss zeit- und ortsunabhängig verfügbar sein. Die Art der Positionsermittlung sollte nach Möglichkeit keine Rolle spielen, wichtig ist lediglich, dass sie ausreichend genau und aktuell ist, um eine straßenbezogene Navigation zu ermöglichen. Die letzte Anforderung an die Hardware ist ein eingebauter Lagesensor. Dieser muss die Fähigkeit besitzen, die Ausrichtung des Gerätes in drei Dimensionen zu beschreiben. Hierbei ist insbesondere ein eingebauter Kompass wichtig, über den das Programm die Nordabweichung, und somit die Blickrichtung des Benutzers, bestimmen kann. Über diese Information soll es dann in der Lage sein, Informationen passend zu platzieren und korrekte Richtungsvorgaben zu machen. Auch den Kippwinkel des Gerätes soll die Applikation berücksichtigen. Auf diese Weise soll der Benutzer bei hybrider Anzeige von Karte und Realität einen Einfluss auf die Größe der beiden Komponenten nehmen. Anforderungen an Software und API lassen sich ebenfalls stellen, diese sind allerdings eher grundsätzlicher Natur. Zunächst ist es von Bedeutung, dass eine Anwendung eigenverantwortlich auf die Ressourcen der Kamera zugreifen kann. Um die Einbettung zusätzlicher Inhalte vornehmen zu können, muss es möglich sein, speziell die Aufnahmevorschau im eigenen Kontext einbetten zu können. Ein bloßer Zugriff auf eine Anzeigekomponente würde hier nicht ausreichen. Wichtig ist außerdem, dass die API eine Möglichkeit zur Verfügung stellt, über die sich einzelne Komponenten der grafischen Oberfläche exakt positionieren lassen. Sollen die Bestandteile der erweiterten Realität möglichst passend in den Raum gelegt werden, so reicht es nicht aus, die Lage der Objekte durch Beziehungen zueinander oder zum Gesamtkontext zu beschreiben, wie dies in den bekannten Layouts unter Java üblich ist. Hier führt kaum etwas an einer pixelgenauen Ausrichtung vorbei. Funktionell sollte die API außerdem in der Lage sein, Mechanismen bereitzustellen, mit deren Hilfe man die Daten und den Status der Applikation bei einer zwischenzeitlichen Unterbrechung oder Beendigung persistieren kann. Dass solche Möglichkeiten bestehen, wurde bereits im vorangegangenen Kapitel beschrieben. Um Daten aus dem Internet nachladen zu können, ist es desweiteren notwendig, dass Schnittstellen zur Kommunikation im HTTP-Protokoll vorhanden sind. Android stellt hierfür Klassen von Apache zur Verfügung. Zusätzlich sollte auch eine Implementierung für XML-Parser vorhanden sein. Ein weitergehender Anspruch an einen solchen Parser wäre dabei, dass dieser ressourcenschonend arbeitet. Ein großes Problem kann die Tatsache darstellen, dass die Ermittlung von Inhalten über das Internet mit großen Zeitverzögerungen einhergehen kann. In der Zwischenzeit muss die Anwendung allerdings auch weiterhin auf Nutzereingaben reagieren können. Daher muss das Betriebssystem unbedingt Multithreading unterstützen, so dass solche Hintergrundprozesse unabhängig von der eigentlichen Programmlogik ablaufen können. Um überhaupt die Grundfunktionalitäten der Navigationssoftware bereitstellen zu können, ist es notwendig, dass eine API für den Routenservice bereitsteht. Diese sollte sowohl auf Remote- Seite die nötigen Informationen vorhalten, also zum einen die Karten, zum anderen aber auch die

40 34 KAPITEL 3. PROGRAMMGRUNDLAGEN Wegfindungsalgorithmen. Das lokale System benötigt grundlegende Klassen, um diese ermittelten Daten grafisch aufbereiten, verwalten und darstellen zu können. Auch elementare Operationen zur Interaktion des Anwenders mit der Kartendarstellung müssen Teil dieser Schnittstelle sein. Genau wie Karte und Wegdaten soll auch eine Datenbank vorliegen, die in der Lage ist, Adressen in Geokoordinaten aufzulösen. Auch für diese Funktion muss ein entsprechendes Interface zur Abfrage vorhanden sein. Zuletzt sollte das System noch die Möglichkeit bereithalten, auf effektive Weise räumliche Szenen darzustellen. Da die Überlagerung von Realität und Virtualität nach der gegebenen Definition im Dreidimensionalen erfolgen soll, bietet sich an dieser Stelle ein Computergrafik-Framework wie OpenGL an.

41 Kapitel 4 Software-Entwurfsmuster Bei der Programmierung unter Android ist es an einigen Stellen wichtig, einen geeigneten Kompromiss zwischen sauberer, objektorientierter Programmierung bei der Implementierung und einer notwendigen Effektivität während der Laufzeit zu finden. Im Folgenden sollen einige Software- Entwurfsmuster und ihr Bezug zur Anwendungsentwicklung unter Android vorgestellt werden, die im Rahmen der Applikations-Implementierung eingesetzt wurden. Hierbei handelt es sich im Speziellen um das Delegation-Pattern und Singletons. Die Auswahl dieser Patterns erfolgte dabei über mehrere Kriterien, die sich auf Effizienz beziehen. Ein wichtiger Aspekt ist dabei unter anderem der beschränkte Speicherplatz auf dem Mobilgerät. Da Applikationen unter Android vom System nur eine begrenzte Menge Laufzeitspeicher zugewiesen bekommen, die nicht vergrößert werden kann, ist es wichtig, die Anzahl von Objekten nicht über Gebühr zu steigern. Spezielles Gewicht bekommt dies, wenn es um Objekte besonderer Größe geht. Neben dem Speicherplatz haben Objekte den weiteren Nachteil, dass ihre Erzeugung verhältnismäßig zeitaufwendig ist. Aus diesem Grund sollte darauf verzichtet werden, häufig neue Objekte anzulegen. Ein zusätzlicher Grund für die Auswahl dieser Designmuster ist die, auf Mobilgeräten fast zwangsläufig anzutreffende, starke Asynchronität der Datenverarbeitung. Wann immer Daten aus externen Quellen nachgeladen werden müssen, entsteht eine nicht zu vernachlässigende Verzögerung, die je nach Güte der vorhandenen Datenverbindung verschieden stark ausgeprägt sein kann. Ebenso darf die im Hintergrund arbeitende Programmlogik nicht zu stark mit der Nutzerinteraktion konkurrieren. Entgegen der üblichen Vorgehensweise wird das Model-View-Controller-Prinzip in der Applikation nicht vollständig umgesetzt. Die Gründe hierfür liegen in der Beschaffenheit von Android und werden im Fazit näher beleuchtet. 35

42 36 KAPITEL 4. SOFTWARE-ENTWURFSMUSTER 4.1 Singletons Bei Singletons handelt es sich um Objekte, deren Instanzzahl einen bestimmten Wert nicht übersteigen darf. Dieses Erzeugungsmuster wird im Deutschen häufig auch als Einzelstück bezeichnet. Die exakte Definition ist hierbei, dass zu jedem Zeitpunkt der Laufzeit nur maximal eine Instanz der Singleton-Klasse existieren darf. Als Verallgemeinerung lässt sich auch ein variabler Maximalwert vorstellen, der eine Klasse auf zum Beispiel drei Instanzen beschränkt. Der Zugriff auf die Instanz eines echten Singletons lässt sich in Java über eine statische Methode regeln. Um eine unkontrollierte Erzeugung von außerhalb zu unterbinden, sperrt der Entwickler den Zugriff auf den Konstruktor, indem er seine Sichtbarkeit auf die eigene Klasse beschränkt. Auf diese Weise ist nur noch die Erzeugermethode in der Lage, ihn aufzurufen. Wurde innerhalb des Erzeugers eine Instanz der Klasse angelegt, so wird diese in einer statischen Variable gespeichert, deren Referenz über die statische Methode zu erhalten ist. Abbildung 4.1: UML-Klassendiagramm einer üblichen Singleton-Umsetzung Für die eigentliche Instanziierung des Objekts stehen zwei Möglichkeiten zur Auswahl. Es kann zum einen beim Laden der Klasse angelegt werden, zum anderen erst beim Aufruf der statischen Methode. Welche Variante sich besser eignet, ist davon abhängig, ob die Instanzerzeugung eventuell durch äußere Umstände beeinflussbar sein soll. Zudem lässt sich durch die spätere Instanziierung eventuell Zeit und Speicher einsparen, wenn das Singleton nicht in jedem Fall benötigt wird. Bei der Arbeit mit Singletons ist besondere Vorsicht geboten. Erhalten mehrere Threads Zugriff auf das Objekt, sollte dieses bereits intern synchronisiert werden, um Transaktionsfehler zu vermeiden. Auch wenn das Singleton Ressourcen bereitstellt, die einen exklusiven Zugriff erfordern, muss immer darauf geachtet werden, dass diese schnellstmöglich wieder freigegeben werden, um unnötige Wartezeiten oder gar Deadlocks zu vermeiden. Hierbei ist besonders problematisch, dass die Klassenspezifikation nicht unbedingt anzeigen muss, dass das verwendete Objekt dem Singleton-Pattern folgt. Eine gute Dokumentation und das Vereinbaren von verbindlichen Verträgen ist daher unerlässlich. Im Hinblick auf einen streng objektorientierten Entwurf und eine entsprechende Programmierung weist das Singleton einige Probleme auf, weil es nur über einen statischen Kontext angesprochen werden kann. Ebenso sind die Daten eines Singletons statisch oder selbst wieder Singletons. Auch das Zerstören eines Singletons ist nicht unproblematisch, da die Klasse auch über diese Operation die volle Kontrolle besitzen muss. Hierfür würde sich eine Art Retain-Counter eignen, über

43 4.2. DELEGATION 37 den gezählt werden kann, wie viele Objekte noch auf das Singleton zugreifen. Für Programmiersprachen wie Java stellt allerdings auch das keine Option dar, weil der Programmierer hier keine Kontrolle über die Speicherverwaltung und somit über den Zeitpunkt der Löschung des Objekts besitzt. Ist der Speicher einer Anwendung wie bei Android begrenzt, können sich Singletons dennoch als hilfreiches Konstrukt erweisen. Der Entwickler kann so Inhalte, die an mehreren Stellen benötigt werden, ressourcenschonend verwalten, indem er nur eine einzige Instanz für die verschiedenen Kontexte anlegt. Als Beispiel für ein solches Vorgehen eignen sich die in einem früheren Kapitel besprochenen Ressource-Dateien. Werden dort abgelegte Bilder etwa in mehreren Views angezeigt, so reicht es wahrscheinlich aus, sie ein einziges Mal an zentraler Stelle zu laden. Gerade bei größeren Dateien lässt sich auf diese Weise einfach, aber effektiv Speicherplatz einsparen. 4.2 Delegation Im Programmablauf gibt es Situationen, in denen ein mit einer Aufgabe betrauter Codeabschnitt nicht eigenständig evaluieren kann, wie es weitergehen soll. Wenn so etwas passiert, besteht für dieses Objekt die Möglichkeit, diese Entscheidung oder die gesamte Aufgabe an ein anderes Objekt zu übertragen. Ein solches Vorgehen wird auch Delegation genannt. Auf diese Weise ist es also möglich, einem allgemeinen Objekt eine spezielle Funktionalität zuzuweisen. Abbildung 4.2: Schema einer Delegation-Umsetzung Delegation wird häufig bei View-Containern eingesetzt. In einer strikten Variante des Model- View-Controller-Musters soll es möglich sein, einzelne Bausteine der Nutzeroberfläche so unspezifisch zu erstellen, dass diese komplett unabhängig von den in ihnen dargestellten Inhalten sind. Dieses Konzept wird in den User-Interface-Klassen des iphones intensiv genutzt, aber auch Java und Android verwenden dieses Konzept beispielsweise in ihren Listen. Diese stellen ausschließlich eine Tabelle zur Verfügung, in der eine Menge von Elementen verwaltet werden kann. Die Anzeige der konkret abgelegten Daten wird dann über ein Delegate abgewickelt, das selbst ein Layout für

44 38 KAPITEL 4. SOFTWARE-ENTWURFSMUSTER jedes Element des Inhalts definiert. Entsprechend wird auch die Interaktion zwischen Programm und Nutzer vom Listen-Container an das Delegate weitergereicht. Der Einsatz von Delegation-Objekten lohnt sich also immer besonders dann, wenn man als Entwickler möglichst generische Objekte erhalten möchte, die sich in verschiedenen Kontexten ohne Änderungen einsetzen lassen. Die Grundfunktionen des Delegates sollten dabei nach Möglichkeit in einem Interface beschrieben werden. Auf diese Weise lässt sich über Delegation auch polymorphes Verhalten simulieren. Das übergebene Delegate dient dabei dazu, das Verhalten des Objekts abhängig vom Kontext zu steuern. 4.3 Callbacks und Observer Unter einem Callback wird eine Methode verstanden, die einer anderen als Parameter übergeben wird. Diese kann das Callback dann im Laufe seiner eigenen Ausführung nutzen, um eigene Berechnungen abhängig vom Zusammenhang durchführen zu können. Ein Einsatzgebiet ist hierbei die Nutzung von Systemfunktionen niederer Schichten. Diese stellen eine bestimmte Basisfunktionalität zur Verfügung, beispielsweise die Berechnung eines Funktionswertes, während der Aufrufer die exakte Funktion als Parameter definiert. Unter Java ist es bekanntlich nicht möglich, Methoden als Parameter für andere Methoden zu benutzen. Daher kann man sie hier nicht direkt übergeben, sondern muss ein Interface definieren, das dann die aufzurufende Methode beinhaltet. Abbildung 4.3 zeigt schematisch eine mögliche Umsetzung von Callbacks über Interfaces. Abbildung 4.3: Schema eines möglichen Callback-Aufbaus unter Java Als Observer wird ein Objekt bezeichnet, welches sich bei einem gegebenen anderen Objekt registriert und im Falle eines bestimmten Ereignisses von diesem eine Nachricht erhält. Das übliche Beispiel für dieses Entwurfsmuster ist unter Java das Eventlistener-Konzept, bei welchem eine Komponente, der Listener, sich bei einer anderen Komponente registrieren muss, um im

45 4.3. CALLBACKS UND OBSERVER 39 Falle eines bestimmten Ereignisses benachrichtigt zu werden. Tritt dieses Ereignis ein, ruft das betroffene Objekt eine vorher definierte Methode des Listeners auf, um diesen zu benachrichtigen. Callbacks und Observer sind sehr ähnliche Konzepte. Möchte man einen Unterschied herausarbeiten, so ließe sich sagen, dass bei einem Callback die Übergabe der Rückruffunktion zum Zeitpunkt des Methodenaufrufs durchgeführt und der Rückruf synchron während der Ausführung oder direkt nach Ermittlung eines Ergebnisses erfolgt. Das Observer-Pattern hingegen ist eine asynchrone Struktur, bei der sich der Beobachter zunächst nur bei einem anderen Objekt anmeldet und anschließend zu einem nicht exakt bekannten Zeitpunkt über die Rückrufmethode benachrichtigt wird. Insbesondere das Observer-Pattern wird unter Android sehr häufig eingesetzt. Dies ist nicht nur bei der Ereignisverarbeitung nach Nutzerinteraktionen der Fall. Auch das System setzt auf Callback-Funktionen, um über Zustandsänderungen zu informieren, ohne dass der Nutzer sich zuvor dafür registrieren musste. Bestes Beispiel ist dabei der Activity-Lebenszyklus, bei dem das System eine Reihe von Callbacks der Activity-Klasse aufruft. Wie in der Einleitung dieses Kapitels beschrieben, laufen viele Prozesse auf Mobilgeräten und speziell in der Navigationssoftware asynchron ab. Das bedeutet, die Ermittlung des Resultats einer Anfrage dauert eine längere Zeit, in der das Programm dennoch weiter funktionsfähig sein muss. Um das zu gewährleisten, erweisen sich Rückruffunktionen als wirksames Mittel. Auf diese Weise muss der Aufrufer nicht warten, bis eine Methode einen Rückgabewert liefert, sondern kann stattdessen parallel weiterarbeiten und erst dann reagieren, wenn die aufgerufene Routine über den Rückruf den Abschluss ihrer Bemühungen mitteilt. Im weiteren Verlauf dieser Arbeit wird der Begriff des Callbacks noch mehrfach genutzt werden. Dabei wird er weniger das Entwurfsmuster beschreiben als vielmehr synonym für die Rückruffunktion stehen. Das liegt darin begründet, dass der Begriff auch in der Android-Dokumentation häufig auf diese Weise verwendet wird und so eine Konsistenz zum Entwicklerhandbuch erreicht werden soll.

46 Kapitel 5 Verwendete Frameworks In diesem Abschnitt werden einige externe Datenbanken und Dienste vorgestellt, die innerhalb der Applikation genutzt werden. Ihre Aufgabe ist es, die notwendigen Informationen bereit zu stellen, mit deren Hilfe der Anwender angeleitet werden kann. Insbesondere gehören hierzu die Webservices, über die das Kartenmaterial bereit gestellt wird, und diejenigen, die Informationen über Sehenswürdigkeiten zur Verfügung stellen. 5.1 OpenStreetMap OpenStreetMap ist ein Projekt mit dem Ziel, eine freie Weltkarte zu erschaffen. [Ope10a] Hinter OpenStreetMap (OSM) verbirgt sich ein freies, offenes und freiwilliges Projekt, das eine möglichst umfassende Datenbank mit geographischen Informationen aufstellen möchte. Die Daten werden von Freiwilligen gesammelt und unter einer Creative-Commons-Lizenz veröffentlicht, die es dem Benutzer ermöglicht, das Material kostenlos und sogar mit eigenen Anpassungen darin zu verwenden. Die OpenStreetMap zugrunde liegenden Daten werden in einer PostgreSQL-Datenbank abgelegt. Für den Zugriff auf diese Inhalte stellt das Projekt eine eigene API zur Verfügung, die derzeit in Version 0.6 vorliegt. Außerdem stellt OpenStreetMap zwei Renderer zur Verfügung, die Kartenkacheln liefern. Dies sind zum einen Mapnik für Kartenkacheln im Pixelformat und zum anderen Osmarenderer für Ausgaben in SVG. Als Grundstruktur der Informationen in der Datenbank werden Elemente vom Typ Knoten verwendet. Diese können offene und geschlossene Wege beschreiben, die wiederum als Elemente gewertet werden, und zusätzlich in Relationen gesetzt werden. Außerdem gibt es die Möglichkeit, die Elemente mit Attributen genauer zu spezifizieren. Über diese Tags werden auch die Verwendungszwecke der Knoten definiert. Die Anzahl und Art dieser Attribute ist dabei theoretisch nicht beschränkt, jedoch gibt es seitens des Projekts Richtlinien, nach denen Elemente 40

47 5.1. OPENSTREETMAP 41 kategorisiert werden sollten. Eine umfangreiche Liste stellt das OpenStreetMap-Wiki zur Verfügung. [Ope10b] In der Datenbank von OpenStreetMap wird eine Vielzahl von Informationen gespeichert. Es werden nicht nur Straßen aufgeführt, diese werden auch kategorisiert (Autobahnen, Bundesstraßen, etc.). Ebenso finden sich Informationen zu Fuß- und Radwegen bei OpenStreetMap, ein Vorteil, der das Projekt zum Beispiel von Google Maps abhebt. Dort finden sich nur Einträge von Wegen, die auch mit dem Auto befahren werden können. Abbildung 5.1: Vergleich von Google Maps (oben) und OpenStreetMap auf dem Westerberg Wie Abbildung 5.1 zeigt, kann der Unterschied zwischen den beiden Datenanbietern lokal sehr groß sein. Es ist ebenso zu erkennen, dass OpenStreetMap nicht nur Straßendaten sammelt, sondern auch Informationen zu Gebäuden und Umgebung bereitstellt. Der Detailgrad kann auch hier variieren, wobei er unter anderem von der Art der dargebotenen Inhalte abhängig ist. Das gegebene Beispiel sollte allerdings nicht zu der Annahme verleiten, dass die Daten bei Open- StreetMap durchgängig eine so hohe Qualität haben wie in diesem Fall. Weil die Daten von Freiwilligen gesammelt werden, kann die Güte von Ort zu Ort deutlich unterschiedlich sein. Osnabrück kommt hierbei zu Gute, dass es ein örtliches Projekt gibt, das sich mit der Erfassung

48 42 KAPITEL 5. VERWENDETE FRAMEWORKS der Geodaten innerhalb der Stadt beschäftigt. [Int07] In der Oberlausitz beispielsweise können kommerzielle Kartensysteme eine wesentlich bessere Karte bereitstellen als OSM zu leisten in der Lage ist. Die Daten des Projekts bilden die Grundlage der Applikation. Sie werden verwendet, um die angezeigten Karten zu erzeugen, und dienen desweiteren dazu, die Auflösung von Adressen in Geokoordinaten vorzunehmen. OpenStreetMap bildet auch die Basis der später vorgestellten Cloudmade-API. Sowohl das Projekt an sich als auch die Nutzung und Erweiterung der Datensätze wird ausführlich in einem mehrsprachigen Wiki beschrieben. Hier finden sich auch weitergehende Ausführungen über den Aufbau des Systems. [Ope10b] 5.2 Cloudmade Im Jahr 2007 gründete der OpenStreetMap-Gründer Steve Coast zusammen mit Nick Black die Firma Cloudmade. Auf der Basis von OpenStreetMap-Daten bietet die Firma eine Vielzahl von Entwicklerschnittstellen an, die nach eigener Aussage einen einfacheren Umgang mit den angebotenen Geodaten ermöglichen sollen. [Clo10a] Die von Cloudmade bereitgestellten Produkte sind dabei vielfältig. Sie beinhalten unter anderem ein Software Development Kit für das iphone, eine Reihe von Programmen zur Erstellung von Navigationsprogrammen und eine Verkaufsplattform für Lizenzen von Geodaten-Diensten. Neben dem iphone werden noch weitere Mobilplattformen unterstützt. Hier werden Entwicklungswerkzeuge für alle bekannten System angeboten, so für die Java Micro Edition, Symbian und Android. Diese Development Kits werden allerdings nicht von Cloudmade direkt angeboten, sondern von anderen Firmen vertrieben. Um Cloudmade nutzen zu können, muss sich ein Entwickler kostenlos registrieren. Er erhält nach der Anmeldung einen eindeutigen Schlüssel bereitgestellt, den API-Key. Mit diesem autorisiert er die Datenabfragen seiner Applikationen. Je nach Anwendung und Zugriffsart kann der Entwickler mehrere API-Keys generieren lassen. Der Zugriff auf die Inhalte unterliegt wie bei OpenStreetMap einer Creative-Commons-Lizenz. Diese erlaubt es dem Programmierer, die Daten frei zu verbreiten und den eigenen Bedürfnissen anzupassen, unter der Voraussetzung, dass er den Urheber nennt und sein Projekt ebenfalls unter einer passenden Lizenz veröffentlicht. Cloudmade stellt desweiteren APIs bereit, mit denen man direkt über das HTTP-Protokoll eine Reihe von Daten abfragen kann. Hierzu gehört auch die Möglichkeit, Ausschnitte der Weltkarte als Pixelgrafiken, sogenannte Kacheln, herunterzuladen. Eine Abfrage hat dabei immer die folgende Form: a.tile.cloudmade.com ist der Server, wobei für simultane Abfragen der erste Buchstabe variiert werden kann, um so gleichzeitig mehrere Verbindungen zu ermöglichen. Es folgt der API-Key für die Anwendung, anschließend wird das Aussehen der Karte bestimmt. Hier stehen zwei unterschiedliche Layouts zur Verfügung, die sich im äußeren Erscheinungsbild unterscheiden. Sie

49 5.2. CLOUDMADE 43 werden durch die numerischen Werte 1 und 2 beschrieben. Die Größe der Kartenkachel kann entweder 64 oder 256 Quadratpixel betragen. Die letzten drei Zahlen definieren den Ausschnitt der Weltkarte, der angezeigt werden soll. Die dortigen Angaben lassen sich über die folgenden Formeln berechnen, wobei man zunächst einen Zoomfaktor Z wählen muss: Y = n = 2 Z X = lon n (1 (log (tan (lat)+sec (lat))/π)) 2 n lon gibt die geographische Länge des nordwestlichen Punktes des Kartenausschnitts in Grad an, während lat die korrespondierende geographische Breite angibt. Diese Berechnung der Kacheln wird auch von OpenStreetMap verwendet. Cloudmade stellt ebenfalls eine API bereit, um Vektor- statt Pixelgrafiken zu erhalten. Diese produziert allerdings nur SVG-Dateien, die unter Android nicht von vorhandenen Systemkomponenten selbst angezeigt werden können. Daher stellt sie keine Alternative für die Navigationssoftware dar. Wichtig ist wiederum die Funktion, Routen berechnen lassen zu können. Auch hierfür bietet Cloudmade einen Webservice an, der per HTTP abgefragt werden kann. Eine Anfrage hat dabei die folgende Form: Bei START, TRANSIT und END werden Startpunkt, Zwischenziele und Endkoordinate der Route angegeben, jeweils in der Form Breitengrad,Längengrad. Die Zwischenpunkte sollten dabei in eckigen Klammern aufgeführt werden. TYPE gibt an, für welche Fortbewegungsart die Route berechnet werden soll, Cloudmade unterstützt hierbei Fußgänger-, Fahrrad- und Auto-Navigation. Diese Eigenschaft ist für ein Navigationssystem speziell für Fußgänger natürlich von großem Vorteil. Das Ergebnis kann entweder im JSON-Format oder als GPX erfolgen, je nachdem, was als FORMAT angegeben wurde. Neben den Zwischenpunkten können noch weitere optionale Parameter angegeben werden, darunter die Sprache der Routenbeschreibung, die Maßeinheit der Entfernungen und für JavaScript-Abfragen auch eine eventuelle Callback-Funktion, der das Ergebnis übergeben werden soll. Eine weitere von Cloudmade bereitgestellte Operation ist das Geocoding. Geocoding beschreibt den Vorgang, die zu einer Ortsbeschreibung gehörende Koordinate in Längen- und Breitengrad zu ermitteln. Eine solche Beschreibung kann sowohl eine Adresse wie Albrechtstraße, Osnabrück sein, aber auch eine Beschreibung wie Kölner Dom. Eine Abfrage wird in Cloudmade wie folgt durchgeführt: Als Ausgabeformat sieht die API JSON oder HTML vor. QUERY ist die Adressbeschreibung, wobei mehrere Worte jeweils durch ein + verbunden werden. Als Parameter kann unter anderem

50 44 KAPITEL 5. VERWENDETE FRAMEWORKS der Suchbereich angegeben werden. Außerdem kann hier über object_type eine andere Suchart genutzt werden: Statt über den Such-Query die Position eines Objektes zu ermitteln, können auch Objekte eines bestimmten Typs in einer Umgebung gesucht werden, zum Beispiel Hotels oder Schulen. Alle genannten Werkzeuge werden auf den Entwicklerseiten von Cloudmade vorgestellt und durch kleine Beispiele eingeführt. Diese speziellen Informationen finden sich unter [Clo10b]. Die hier vorgestellten HTTP-Anfragen werden in der Applikation später nicht direkt abgesetzt. Dies ist jedoch theoretisch ohne Probleme möglich, da die Android-API alle nötigen Werkzeuge mitbringt. Neben den obligatorischen HTTP-Klassen steht auch eine eingebaute JSON- Repräsentation zur Verfügung. Dennoch ist es von Vorteil, die von Nutiteq speziell für Android entwickelte API zu nutzen, die noch weitere hilfreiche Funktionalitäten liefert. 5.3 Nutiteq Mobile Map API Auf die von Cloudmade bereitgestellten Daten zugreifend, hat die estnische Firma Nutiteq [Nut10a] eine API für Android entwickelt. Diese bietet bequemen Zugriff auf die vorhandenen Dienste. Es werden Klassen zur Verfügung gestellt, die sowohl das Laden von Kartenausschnitten verwalten als auch passende Viewobjekte für die Anzeige zur Verfügung stellen und eine Interaktion mit diesen ermöglichen. Außerdem ist eine Schnittstelle vorhanden, um Routenberechnungen durchführen zu lassen und den gefunden Weg darzustellen. Ebenso ist es möglich, über einen eingebauten Service Geocoding-Anfragen zu stellen. Bei der Darstellung der Karte gibt es eingebaute Möglichkeiten, Zusatzinformationen einzublenden. Hierbei können sowohl einzelne Orte eingefügt werden als auch Wege und Polygone. Auf diese Weise lassen sich entsprechend auch Routen und Sehenswürdigkeiten in der Karte visualisieren. Nutiteq vertreibt seine API sowohl unter GNU General Public License (GPL) als auch unter kommerziellen Lizenzen. Veröffentlicht ein Programmierer den Quelltext einer von ihm entwickelten Applikation unter der GPL, so erhält er ebenfalls eine GPL für die Maps-API und kann auch den Quelltext einsehen. Auf Basis der API ist nicht nur die Arbeit mit Cloudmade möglich. Die Struktur sieht generische Kartentypen vor, die durch den Entwickler nach Wunsch ausgetauscht werden können. Es werden bereits Klassen für mehrere andere Kartendienste angeboten, unter anderem ist auch ein Direktzugriff auf den Mapnik-Renderer von OpenStreetMap dabei. Weil die Nutiteq Maps-API in der Applikation an mehreren Stellen eingesetzt wird, soll an dieser Stelle zunächst auf eine weitergehende Einführung verzichtet werden. Die entsprechend notwendigen Teile der API werden bei der Vorstellung der Implementierung dann genauer behandelt und erklärt. Die einzelnen Klassen sind in der zugehörigen Javadoc-Dokumentation [Nut10b] beschrieben. Leider ist diese sehr lückenhaft, was die Arbeit mit der API an manchen Stellen deutlich erschwert.

51 5.4. GEONAMES-WEBSERVICE Geonames-Webservice Für die Einblendung von Zusatzinformationen über Sehenswürdigkeiten dürfte die größte Quelle eindeutig Wikipedia sein. Ziel ist es also, über einen geeigneten Webservice ortsbezogene Daten der Online-Enzyklopädie abzufragen, die dann in die Navigations-Applikation eingeblendet werden können. Eine passende Datenbank für diesen Zweck stellt Geonames zur Verfügung. Nach eigenen Angaben verfügt Geonames über etwa sieben Millionen verschiedene Einträge und weist über elf Millionen Abfragen pro Tag auf [Geo10]. Unter anderem verwendet auch Spiegel Online die Geonames-Daten in seinem Reiseportal km.42. Wie auch schon bei OpenStreetMap stehen die von Geonames angebotenen Daten unter einer Creative-Commons-Lizenz, die es dem Verwender erlaubt, die Informationen unter Nennung der Quelle zu verbreiten und an eigene Bedürfnisse anzupassen. Im Gegensatz zu OpenStreetMap und Cloudmade verzichtet Geonames jedoch auf die Einschränkung, dass der nutzende Dienst wiederum unter einer vergleichbaren Lizenz stehen muss. Das Geonames-Projekt bietet neben Webservices auch die Möglichkeit, mit APIs für verschiedene Programmiersprachen auf die Datenbank zuzugreifen. Hierbei sind Umsetzungen unter anderem für Java, PHP, Ruby und Python vorhanden. Auch ein Such-Plugin für Firefox wurde programmiert. In der Navigationssoftware wird der Zugriff auf Geonames allerdings über einen der verfügbaren Webservices erfolgen. Zur Verfügung stehen über 30 verschiedenen Services, die jeweils andere Daten liefern, darunter etwa Erdbebendaten oder die Ermittlung von Orten zu bestimmten Postleitzahlen. Wichtig für die Applikation ist der Webservice findnearbywikipedia, eine Bestimmung von Wikipedia-Artikeln im Umkreis einer Position. Die Suche kann sowohl anhand einer Geo-Koordinate erfolgen als auch anhand eines Ortsnamens oder einer Postleitzahl. Geonames stellt für den Webservice grundsätzlich drei Ausgabeformate zur Verfügung: XML, JSON und RSS. Im Folgenden soll der Zugriff auf den Server mit XML als Rückgabe betrachtet werden. Die Anfrage für die beiden anderen Formate unterscheidet sich dabei nur in der aufgerufenen URL. Um die gewünschten Daten im XML-Format zu erhalten, wendet man sich mit einer HTTP-GET-Anfrage an die folgende Adresse: ws.geonames.org/findnearbywikipedia?params Der Zugriff auf JSON würde hier mit findnearbywikipediajson, der auf eine RSS-Ausgabe mit Hilfe von findnearbywikipediarss erfolgen. Wichtigstes Argument ist die Angabe der Wikipedia-Sprachversion, die durchsucht werden soll. Hier gibt man beim Parameter lang den Ländercode der Sprache an, also beispielsweise de oder en. Über den Parameter country lassen sich die Ergebnisse auf ein bestimmtes Land einschränken. Die Ergebnismenge wird begrenzt durch maxrows, bei Nichtangabe werden fünf Ergebnisse zurückgeliefert. Wichtig ist auch der Suchradius, der sich mit radius angeben lässt, wobei die Angabe in Kilometern erfolgt. Die Suche nach Ort und Koordinate läuft jeweils unterschiedlich ab. Sucht man nach einer Postleitzahl, muss der Parameter postalcode aufgeführt sein, der durch country näher definiert wird. Soll rund um eine Geokoordinate gesucht werden, wird für diese der Breitengrad mit lat und

52 46 KAPITEL 5. VERWENDETE FRAMEWORKS der Längengrad mit lng angegeben. Eine Suche nach Wikipedia-Artikeln, die in einem Fünf- Kilometer-Radius um den Osnabrücker Dom liegen, sähe also wie folgt aus: ws.geonames.org/findnearbywikipedia?lat= &lng= &radius=5&lang=de Der Aufbau der Antwort lässt sich in Listing 5.1 ansehen. Ein Nachteil von Geonames ist dabei, dass keine Beschreibung des XML-Formats als DTD oder ähnliches zur Verfügung steht. Welche Elemente also in einem Eintrag genau enthalten sein können, ist daher nicht exakt definiert. Dennoch ist der Aufbau der Ergebnisse immer der gleiche. < geonames > <entry > <lang >de </ lang > <title >Dom St. Peter ( Osnabrück )</ title > < summary > ist der Sitz des Bischofs von Osnabrück. Der Dom ist ein spätromanisches Bauwerk und prägt seit seiner Entstehung die Silhouette der Stadt. (...) </ summary > < feature > landmark </ feature > < countrycode >DE </ countrycode > < population >0 </ population > < elevation >0</ elevation > <lat > </ lat > <lng > </ lng > < wikipediaurl > http: // de. wikipedia. org / wiki / Dom_St. _Peter_ %28 Osnabr %C3% BCck %29 </ wikipediaurl > < thumbnailimg /> < distance > </ distance > </ entry > </ geonames > Listing 5.1: Antwort einer Geonames-Abfrage Als oberstes Element ist der geonames-tag vorhanden, in dem die Ergebnisse geschachtelt sind. Jedes Ergebnis wiederum ist in einem entry-element aufgeführt. Die Ergebnisse liegen dabei auf einer Ebene. Jeder entry wird durch eine Reihe von inneren Elementen näher beschrieben, deren Namen jeweils recht eindeutig gewählt sind. So finden sich in lat und lng die Angaben zum Breiten- und Längengrad des im Eintrag beschriebenen Objekts und in title der Name des Artikels. Außerdem werden eine Kurzzusammenfassung summary und der Link zur Seite in der Wikipedia über wikipediaurl mitgegeben. Weitere Elemente beschreiben die Höhe, Einwohnerzahl und Kategorie (Objekt, Ortschaft, Gewässer etc.) des Eintrags. Auf eine Auswertung dieser Teile wird in der Applikation verzichtet, da diese häufig Ungenauigkeiten aufweisen. Mit Blick auf die Nutzung von Geonames muss darauf verwiesen werden, dass die Ergebnisse des Webservices immer nur so gut sein können wie die Daten, welche Wikipedia bereitstellt. Das Wiki-Projekt Georeferenzierung hat es sich zum Ziel gesetzt, möglichst viele Artikel mit leicht auszuwertenden Templates zu versehen [Wik10]. Dennoch ist es noch bei sehr vielen Artikeln so, dass eine Georeferenzierung komplett fehlt oder in einem inkompatiblen Format eingetragen

53 5.5. OSM-NAMEFINDER 47 ist. Daher liefert der Webservice je nach Region zwar durchaus eine große Zahl von Treffern, allerdings müssen diese nicht alle vorhandenen Artikel umfassen. 5.5 OSM-Namefinder Um in der Lage zu sein, Adressdaten in Geokoordinaten auflösen zu können, wird eine Datenbank benötigt, welche die Namen möglichst vieler Orte kennt. Hierfür bietet OpenStreetMap unter anderem das Werkzeug Namefinder an. Diese Suchmaschine, die ebenfalls auf den Seiten von OpenStreetMap genutzt wird, stellt dabei eine Reihe verschiedener Arten der Suche zur Verfügung. Der Suchmaschinenindex gruppiert Objekte jeweils in 111 Quadratkilometer große Flächen. Er ist in der Lage, verschiedene Arten von Abfragen zu interpretieren. So ist es möglich, explizit nach einem bestimmten Objekt zu suchen, ebenso kann aber auch nach Orten in der Nähe einer Position gesucht werden, hierfür dient das Schlüsselwort near. Auch versteht der Namefinder Anfragen nach bestimmten Objektkategorien wie Kirchen oder Flughäfen. Eine Abfrage könnte also dom near Osnabrück lauten. Äquivalent dazu wären die Aufrufe dom, Osnabrück oder auch dom, ,8.05. Namefinder durchsucht sowohl Beschreibungen als auch Kategorien, die in OpenStreetMap eingetragen wurden. Dabei gilt es allerdings zu beachten, dass diese Kategorien englische Bezeichner aufweisen, so dass eine Suche nach dom im oberen Beispiel nur Orte findet, die auch einen dom im Namen haben. Wie die Treffer generiert werden, ist im OpenStreetMap-Wiki unter [Ope09] erklärt. Grundsätzlich zerfällt die Suche dabei in drei Schritte. Ist durch ein near oder ein Komma ein Ort angegeben, in dessen Nähe gesucht werden soll, wird zunächst dessen Geo-Koordinate ermittelt. Ist bereits eine Angabe als Breiten- und Längengrad eingegeben worden, entfällt dieser Schritt. Anschließend wird die Index-Fläche bestimmt, in der sich die Position befindet. Sollte sie nahe am Rand der Fläche sein, werden auch die benachbarten Felder ergänzt. Innerhalb der gegebenen Flächen werden dann die auf die Anfrage passenden Orte ermittelt und zurückgegeben. Soll nur eine generelle Suche ohne Spezifizierung einer Umgebung erfolgen, entfällt auch der eingrenzende Schritt und es werden alle Flächen durchsucht. Zu jedem Treffer werden noch einige weitere Informationen, wie die nächstgelegene Ortschaft, ermittelt, was für die spätere Navigationsanwendung allerdings keine Rolle spielt. Um eine Anfrage an die Suchmaschine zu stellen, existiert eine URL, die den gewünschten Suchstring erwartet: Als GET-Parameter werden der URL dabei noch weitere Attribute mitgegeben. Wichtigster Teil ist dabei der Schlüssel find, der als Wert den String der Suchanfrage beinhaltet. Dieser kann die zuvor beschriebenen Elemente enthalten. Da es sich um einen Teil einer URL handelt, muss der String entsprechend kodiert werden. Neben find gibt es noch zwei weitere Attribute. Zum

54 48 KAPITEL 5. VERWENDETE FRAMEWORKS einen kann man mit max die Anzahl der Suchergebnisse beschränken, um die Geschwindigkeit zu erhöhen, zum anderen ist es möglich, mittels any=1 anzugeben, dass die Suche außerhalb des Radius um das durch near angegebene Objekt fortgesetzt werden soll, falls in der Nähe keine Ergebnisse ermittelt werden konnten. < searchresults find ="" sourcedate =" " date =" :57:44 " distancesearch =" no" findname =" dom " findplace ="" foundnearplace =" yes " > <named type =" way " id=" " lat =" " lon =" " name =" Domhof " category =" highway " rank ="0" region =" " info =" pedestrian " zoom ="16"> < description > pedestrian < strong > Domhof </ strong > found about 1 km north of middle of county < strong > Landkreis Osnabrück </ strong > in Niedersachsen, Bundesrepublik Deutschland, Europe and about 1 km north of middle of city < strong > Osnabrück </ strong >, ditto ( which is less than 1 km south - east of middle of county < strong > Landkreis Osnabrück </ strong >, ditto ) </ description > <place > <named type =" node " id=" " lat =" " lon =" " name =" Osnabrück " category =" place " rank ="60" region =" " is_in =" in Niedersachsen, Bundesrepublik Deutschland, Europe " info =" city " distance =" " approxdistance =" 1" direction =" 290 " zoom ="10"> < nearestplaces > <named type =" node " id=" " lat =" " lon =" " name =" Landkreis Osnabrück " category =" place " rank =" 0" region =" " is_in =" in Niedersachsen, Bundesrepublik Deutschland, Europe " info =" county " distance =" " approxdistance =" 0" direction =" 158 " zoom ="16"> </ named > </ nearestplaces > </ named > </ place > < nearestplaces > <named type =" node " id=" " lat =" " lon =" " name =" Landkreis Osnabrück " category =" place " rank =" 0" region =" " is_in =" in Niedersachsen, Bundesrepublik Deutschland, Europe " info =" county " distance =" " approxdistance =" 1" direction =" 288 " zoom ="16"> </ named > </ nearestplaces > </ named > </ searchresults > Listing 5.2: Antwort einer Namefinder-Anfrage Die in Listing 5.2 zu sehende XML-Datei ist das Ergebnis einer Suche nach dom,osnabrück mit Beschränkung auf einen Eintrag. Da die Suchergebnisse in aufsteigender Reihenfolge nach ihrem Abstand zum Referenzpunkt sortiert sind, ist der erste Treffer der Domhof. Es ist schnell zu sehen, dass die Menge der übertragenen Daten deutlich höher ist als bei der Abfrage von Wikipedia- Artikeln mittels Geonames. Auch ist ein Teil der Daten redundant, was das Übertragungsvolumen noch weiter erhöht.

55 5.5. OSM-NAMEFINDER 49 Wurzelelement der Rückgabe ist searchresults, das als Attribute Informationen zur Suche beinhaltet. Diese umfassen unter anderem das Datum der Anfrage und den übergebenen Suchstring. Unterhalb der Wurzel werden alle gefundenen Elemente aufgelistet, jedes bekommt einen eigenen Knoten named. Dieser beinhaltet eine Reihe von Attributen, die beschreiben, welche Art von Objekt vorliegt (Punkt, Weg, Fläche), auf welchem Breiten- und Längengrad es liegt, welchen Namen es hat und so weiter. Eine Erklärung aller Attribute führt [Ope09] auf. Namefinder erzeugt außerdem eine textuelle Beschreibung der Lage des Elements im Knoten description. Hierfür werden nahegelegene Orte herangezogen, die dann in Beziehung zum beschriebenen Ort gesetzt werden. Ein weiterer Knoten ist place, der den Ort angibt, in dessen Umkreis gesucht wurde. Außerdem wird im Knoten nearestplaces eine Menge von Orten angegeben, die dem gefundenen Ort am nächsten liegen. Diese Angaben können sich im schlimmsten Fall auch rekursiv fortsetzen, was die Rückgabe weiter aufbläht.

56 Kapitel 6 Umsetzung Dieses Kapitel soll die eigentliche Umsetzung der Navigationssoftware behandeln. In den folgenden Abschnitten wird schrittweise erklärt, welche Klassen für die Datenhaltung zuständig sind, auf welche Weise Routen berechnet und verarbeitet werden und wie die Darstellung auf dem Gerät erfolgt. Zunächst soll geklärt werden, auf welche Art die Daten innerhalb der Anwendung vorgehalten werden. Außerdem werden die statischen Informationen des Manifests behandelt. Dazu soll beschrieben werden, wie die Applikation die aktuelle Position und Lage des Geräts bestimmen kann. Anschließend folgt ein allgemeiner Blick auf die Ermittlung von Informationen über Sehenswürdigkeiten und deren Speicherung. Im Speziellen wird dabei der Zugriff auf georeferenzierte Artikel der Wikipedia behandelt, der im Rahmen der Applikation als Beispiel umgesetzt wurde. Desweiteren wird dargelegt, wie mit der von Nutiteq zur Verfügung gestellten API die Anzeige einer Karte abgewickelt wird. Auch die hierbei vorgenomme abstrakte Kapselung wird im Verlauf des Kapitels beschrieben. Das Vorgehen, um eine Route zu ermitteln, soll ebenfalls in diesem Abschnitt behandelt werden, dazu gehörend außerdem die Einbindung des Geocodings zur Ermittlung von Zielen. Im weiteren Verlauf wird dann auf die eigentliche Programmoberfläche eingegangen. Hier soll die Navigationsoberfläche, über die der Anwender geleitet wird, mit ihren Teilen beschrieben werden. Besonderes Augenmerk gilt dabei der erweiterten Realität, also der Verknüpfung von Kamera und Zusatzinformationen. Abschließend wird außerdem erklärt, wie die Persistierung von Daten in der Applikation vonstatten geht. Hierbei werden sowohl die Speicherung von Inhalten wie Routen als auch die Sicherung vorgegebener Programmeinstellungen behandelt. Dieser Teil soll den Abschluss des Kapitels bilden. 50

57 6.1. MANIFEST UND BASISKLASSEN Manifest und Basisklassen Um jederzeit einen Rückbezug herstellen zu können, soll zuallererst das Manifest der Applikation abgehandelt werden. Da sie nur wenige unterschiedliche Oberflächen bietet, die nur intern genutzt werden sollen, ist es sehr kurz gehalten. <? xml version =" 1.0 " encoding ="utf -8"?> < manifest xmlns:android =" http: // schemas. android. com / apk / res / android " package =" de. monarchy. guideme " android: versioncode =" 1" android: versionname =" 1.0 " > < application android: icon drawable / icon " android:label / app_name " android:debuggable =" true "> < activity android: screenorientation =" landscape " android:name =". main. MainActivity " android:label / app_name "> <intent - filter > < action android:name =" android. intent. action. MAIN "></ action > < category android:name =" android. intent. category. LAUNCHER "></ category > </ intent - filter > </ activity > < activity android: screenorientation =" landscape " android:name =". main. RoutingActivity " android:label / app_name "> </ activity > < activity android: screenorientation =" landscape " android: name =". main. PreferenceActivity " android:label / app_name "> </ activity > </ application > <uses - sdk android: minsdkversion =" 3" / > <uses - feature android:name =" android. hardware. camera "></uses - feature > <uses - configuration android: reqtouchscreen =" finger " ></ uses - configuration > <uses - permission android: name =" android. permission. ACCESS_ FINE_ LOCATION " > </uses - permission > <uses - permission android: name =" android. permission. INTERNET " > </uses - permission > <uses - permission android: name =" android. permission. CAMERA " > </uses - permission > <uses - permission android:name =" android. permission. WAKE_LOCK "> </uses - permission > </ manifest > Listing 6.1: Manifest-Datei der Applikation Wie Listing 6.1 zeigt, verfügt die Applikation Guide Me über drei Activities, von denen die MainActivity mit dem passenden Filter versehen wurde, um als Einsprungspunkt der Anwendung zu dienen. Wie später noch ausführlicher erklärt wird, wurde auf den Einsatz von anderen Android-Komponenten verzichtet. Übersichtlich sind auch die gesetzten Permissions. Die Permission ACCESS_FINE_LOCATION erlaubt den Zugriff auf die Positionsdienste des Geräts, insbesondere auch die exakte Ortsbestimmung über GPS oder einen vergleichbaren Anbieter. Schnell ersichtlich ist die Notwendigkeit des

58 52 KAPITEL 6. UMSETZUNG Internetzugriffs, da die Karten- und Routeninformationen von dort nachgeladen werden sollen, genau wie die ergänzenden Daten über Sehenswürdigkeiten. Dass auch ein Kamerazugriff erlaubt sein muss, ist ebenso klar, da ansonsten die erweiterte Realität nicht funktionieren kann. Die Art der Kamera wird im Element uses-feature als allgemeine Kamera angegeben. Wäre dieses nicht vorhanden, nähme Android an, dass es sich zwingend um eine Kamera mit Autofokus handeln muss, wie es in frühen API-Spezifikationen vorgesehen war. Die Applikation kann so auch mit Kameras arbeiten, die einen starren Fokus nutzen. Die letzte gesetzte Permission ist WAKE_LOCK. Diese dient dazu, dass die Anwendung in der Lage ist zu verhindern, dass das Gerät in einen Stromsparmodus wechselt, in dem der Bildschirm abgeschaltet wird. Da das zur Folge hätte, dass die Anwendung pausiert, wäre eine geordnete Programmsteuerung nicht mehr möglich. Außerdem müsste der Benutzer ansonsten in regelmäßigen Abständen eine Aktion durchführen, damit der Bildschirm nicht aufgrund der Inaktivität schwarz wird. Letztes nennenswertes Element des Manifests ist uses-configuration, in dem über das Attribut android:reqtouchscreen="finger" angekündigt wird, dass die Anwendung über ein Touch- Display zu steuern ist. Auf diese Weise kann sicher gestellt werden, dass der Benutzer auch in der Lage sein wird, das Programm zu bedienen Ressourcen Viele der innerhalb der Anwendung verwendeten Daten werden statisch in den Ressourcen vorgehalten. Auf diese Weise soll es möglich sein, einfach Erweiterungen und Änderungen vorzunehmen, indem jeweils nur die XML-Einträge angepasst werden müssen. Es wurde versucht, exemplarisch jede Art von Ressource einzusetzen. Grafiken werden in res/drawable/ vorgehalten. Hierbei handelt es sich zum einen um kleine, farbliche Punkte für die Darstellung auf der Karte, zum anderen um größere Grafiken für Buttons. Sie liegen hier nur in einer Version vor, können aber bei Bedarf schnell um Versionen für höhere Auflösungen ergänzt werden, worauf schon im Abschnitt 2.10 eingegangen wurde. Auch grafische Layouts werden als XML-Dokumente vorgehalten. Sie umfassen sowohl ganze View-Komponenten in res/layout/ als auch Menüs in res/menu/. Intern sind sie ebenfalls wieder so aufgebaut, dass ihre Inhalte aus anderen Ressourcen bestehen, was die Austauschbarkeit erhöhen soll. Auf die einzelnen Dateien wird in den folgenden Abschnitten näher eingegangen. Außerdem wurden alle Texte, die dem Nutzer grafisch präsentiert werden, in eine XML-Datei ausgelagert. Auf diese Weise ist es möglich, die Anwendung multilingual zu halten. Bei ihnen wurde auch beispielhaft eine Lokalisierung der Ressourcen umgesetzt, indem eine englische Version der Strings in res/values/ existiert und eine deutsche Übersetzung der englischen Begriffe in res/values-de/.

59 6.1. MANIFEST UND BASISKLASSEN Positionsdaten Die in der Applikation am häufigsten instantiierte Klasse ist die Grundklasse GeoCoordinate. Sie ist das Objekt, in dem geographische Positionen gespeichert werden, und wird an allen Stellen verwendet, an denen solche Informationen abgelegt werden müssen. GeoCoordinate dient als Oberklasse für spezielle geographische Objekte, dies sind die Klassen PointOfInterest, die wiederum eine Basisimplementierung für Sehenswürdigkeiten darstellt, und RoutePoint, deren Objekte die einzelnen Stationen eines berechneten Weges darstellen. GeoCoordinate besteht aus zwei double-variablen, die jeweils die geographische Breite und Länge repäsentieren. Über entsprechende Methoden wird der Zugriff beschränkt, so dass sicher gestellt ist, dass die Werte für die Breite im Bereich [ 90, 90] und für die Länge in [ 180, 180] liegen. Grundsätzlich besteht die Möglichkeit, die Werte eines Objekts nachträglich zu ändern, was im Hinblick auf die Performanz in Android von Vorteil ist. Abbildung 6.1: GeoCoordinate und Vererbungshierarchie Die zwei wichtigen Methoden der GeoCoordinate, auf die im Programm am häufigsten Bezug genommen wird, sind distanceto(geocoordinate) und trackangle(geocoordinate). Sie dienen dazu, Entfernung und Richtung zwischen zwei Geokoordinaten zu bestimmen. Um die Zahl der Berechnungen zu reduzieren, werden in den Methoden nicht Ellipsoide als Grundlage genommen, sondern eine Einheitskugel. Die Entfernung zweier Punkte in distanceto wird mit Hilfe der Orthodrome berechnet. Deren Länge d zwischen zwei gegebenen Punkten r = (B r, L r ) und s = (B s, L s ) wird auf der Einheitsku-

60 54 KAPITEL 6. UMSETZUNG gel berechnet. Für qualitative Vergleiche reicht diese Berechnung aus, soll jedoch eine Entfernung in Metern angegeben werden, muss das Ergebnis noch mit dem Radius der Erde multipliziert werden, um für eine entsprechende Skalierung zu sorgen. Die Formel zur Berechnung lautet wie folgt: d = arccos (sin (B r ) sin (B s ) + cos (B r ) cos (B s ) cos (L s L r )) Die Berechnung des Kurswinkels φ zwischen zwei Orten in trackangle erfolgt über die Loxodrome und wird in mehrere Schritte zerlegt. Sie ist ungleich aufwendiger, da zunächst einige Transformationen der Koordinaten durchgeführt werden müssen. Zuerst wird eine Mercator-Projektion auf die Punkte angewandt. Das bedeutet, die Koordinaten der Kugel werden so auf eine Ebene abgebildet, dass alle geographischen Längen wie die Breiten parallel und äquidistant sind. Im Folgenden wird vorausgesetzt, dass die angegebenen Koordinaten im Bogenmaß vorliegen. Eine solche Umrechnung stellt in der Methode den ersten Schritt dar. (X, Y ) = M(r) = (M X (L r ), M Y (B r )) M X (L r ) = L r M Y (B r ) = ln tan ( B r 2 + π ) 4 Die Projektion in die Ebene vereinfacht die folgenden Rechnungen entscheidend. Eine wichtige Eigenschaft der Mercator-Projektion, die sich an dieser Stelle ausnutzen lässt, ist, dass die Loxodrome in ihr als Gerade beschrieben wird. Um nun den korrekten Winkel im Startpunkt r zum Ziel s zu ermitteln, bedient sich die Methode des zwei-argumentigen Arcustangens atan2(y, x). α = atan2(m Y (B s ) M Y (B r ), L s L r ) 0 für x 0, y = 0 π für x < 0, y = +0 π für x < 0, y = 0 atan2(y, x) := +π/2 für x = 0, y > 0 π/2 für x = 0, y < 0 arctan y x sonst Die Definition des atan2 in der Java-API-Dokumentation sieht noch weitere Spezialfälle vor, wenn x = oder y = gilt. Da die Positionen im Programm aber immer endlich sind, wird der Einfachheit halber auf sie verzichtet. Um den Winkel α, der die Koordinate (X, Y ) in Polarkoordinaten definiert, in einen Richtungswinkel umzuwandeln, der von Norden aus im Uhrzeigersinn läuft, muss er noch gedreht werden, so dass sich α = π 2 α ergibt. Wie schnell ersichtlich ist, kann α nur dann der gesuchte Winkel sein, wenn L s L r π gilt. Ansonsten wurde der Winkel über die längere Entfernung zwischen den beiden Punkten berechnet. Ist das der Fall, muss das Ergebnis noch um 180 Grad gedreht werden. { α für L s L r π φ = (π + α ) mod (2 π) für L s L r > π

61 6.2. DATENVERWALTUNG 55 Ein Problem bei der hier durchgeführten Berechnung des Kurswinkels liegt bereits in der Umrechnung in die kartesischen Koordinaten bei der Mercator-Projektion. In ihr kann keine Repräsentation der Pole erfolgen, wodurch die Berechnung des Kurswinkels in diesem Fall fehlschlägt. Außerdem ist zu beachten, dass die Loxodrome, wie schon an früherer Stelle beschrieben, nicht den kürzesten Weg angibt, wodurch die Methoden distanceto und trackangle streng genommen inkonsistente Angaben machen. Die zu erwartenden Abweichungen liegen bei Fußwegen allerdings im Bereich von Zentimetern, so dass sie praktisch keine Rolle spielen. 6.2 Datenverwaltung Ebenso wichtig wie die Klasse GeoCoordinate ist auch die PersonalLocation. An dieser Stelle werden praktisch alle Daten der Applikation zentral verwaltet, die positionsbezogen sind. Die Klasse sammelt Informationen über die Position und Lage des Geräts, ist für die Routenfindung verantwortlich und ermittelt die Sehenswürdigkeiten der Umgebung. Hier laufen also letztlich alle Informationsflüsse zusammen. Entsprechend fällt PersonalLocation eine Schlüsselrolle in der Anwendung zu. Weil sie die Daten vorhält, werden in PersonalLocation auch die Operationen für Sicherung und Rückgewinnung der zu persistierenden Daten implementiert. Diese sollen allerdings erst zu einem späteren Zeitpunkt ausführlich behandelt werden, um diesen Abschnitt thematisch besser einschränken zu können. Gleiches gilt für die Verwaltung der Sehenswürdigkeiten und der Routen, die zwar ebenfalls über diese Klasse läuft, allerdings in andere Klassen ausgelagert ist. Die Klasse und ihre Funktionalitäten sollen in mehreren Schritten erklärt werden. Als Einstieg wird die Grundstruktur der Klasse beschrieben, insbesondere die Art, wie eine Initialisierung vonstatten geht. PersonalLocation muss, wie alle Klassen, in den Lebenszyklus der Anwendung eingebunden werden. Das bedeutet, dass sie beim Start der Anwendung aktiviert und bei einer Unterbrechung wieder deaktiviert werden muss. Jedoch sind Teile der Klasse ohnehin nur auf Anfrage aktiv, so dass diese in einem solchen Fall unberührt bleiben können. Sie umfassen die Funktionen zur Ermittlung von Sehenswürdigkeiten und die Routenberechnung. Beide werden in Objekten vorgehalten, die explizit auf einen Aufruf warten, um zu agieren. Daher werden ihre Instanzen bereits im Konstruktor von PersonalLocation angelegt und über die Lebensdauer der Instanz nicht mehr verändert. Anders verhält es sich mit der Ermittlung von Position und Lage des Geräts. Beides wird über Systemdienste abgewickelt, die nur aktiv sein müssen, wenn wirklich eine Applikation entsprechende Daten abfragt. Zu anderen Zeiten können sie für die Energieeffizienz abgeschaltet werden. Daher ist es wichtig, diese Ressourcen bei Pausierung der Anwendung ebenfalls freizugeben. PersonalLocation verwendet dafür zwei Methoden, zum einen onstart(), zum anderen onstop(). Die Methodennamen sind analog zur Android-Namenskonvention gewählt. In onstart werden die benötigten Systemdienste ermittelt und verbunden, außerdem werden persistierte Daten geladen. Während onstop werden die belegten Dienste wieder freigegeben, außerdem werden, sofern der

62 56 KAPITEL 6. UMSETZUNG Anwender das wünscht, die aktuellen Daten persistent gesichert. Nach dieser kurzen Einführung soll nun eine genauere Betrachtung des Funktionsumfangs von PersonalLocation erfolgen. Zuerst soll erläutert werden, auf welche Weise die Ermittlung der Positionsdaten vonstatten geht. Es wird also gezeigt, wie Android die gewünschten Daten ermittelt und wie sie dann einer Anwendung zur Verfügung gestellt werden. In der Applikation wird die Hauptarbeit hierbei von einer Hilfsklasse erledigt, die ausführlich betrachtet wird. Es handelt sich dabei um PositionListener Positionsbestimmung Um eine Positionsermittlung durchzuführen, verwendet man in Android den LocationManager. Über diese Klasse kann ein Provider ermittelt werden, der die gewünschten Daten zur Verfügung stellt. Der LocationManager ist ein Systemdienst und wird über den Kontext der Applikation mit der Methode getsystemservice(string) abgefragt, der als Argument der String der Konstante Context.LOCATION_SERVICE übergeben wird. Er stellt sowohl einen expliziten als auch einen impliziten Zugriff auf gewünschte Dienste zur Verfügung. In Listing 6.2 sieht man in den Zeilen 6 bis 9 die direkte Verbindung mit dem GPS-Provider, so weit dieser aktiviert ist. Anschließend folgt im Code die implizite Ermittlung des am besten geeigneten Providers über ein Criteria-Objekt. Über diese Kriterien lässt sich ein Provider finden, der für die eigenen Anforderungen am besten geeignet ist. Hierfür stehen die Eigenschaften Stromverbrauch, Genauigkeit, Richtungsanzeige, Geschwindigkeitsmessung, Höhenangabe und Kosten zur Verfügung. Sollte kein Provider auf die gewünschte Beschreibung passen, so werden die geforderten Eigenschaften in der eben angegebenen Reihenfolge gelockert, mit Ausnahme der Angabe, ob Kosten anfallen dürfen. Die Methode getbestprovider(criteria, boolean) des LocationManagers, die zur Bestimmung aufgerufen wird, erhält neben dem Kriterien-Objekt auch noch ein Flag, ob nur aktive Provider ausgewählt werden sollen. Rückgabewert der Methode ist der Name des Providers. Die Anmeldung der Applikation an den Provider erfolgt über requestlocationupdates(string, long, float, LocationListener). Ihr wird der Name des zu beobachtenden Providers übergeben, eine Angabe in Millisekunden, die mindestens zwischen zwei Positionsermittlungen liegen soll und eine Entfernung in Metern zwischen der letzten bekanntgegebenen Position und der folgenden. Während die Entfernung nur dazu dient, eine Kontrolle über die Häufigkeit der Mitteilungen über Positionsänderung zu beeinflussen, kann der Parameter für die Zeit auch beeinflussen, dass der Provider nur in bestimmten Zeitabständen wirklich aktiv ist, was eine Reduzierung des Stromverbrauchs ermöglichen kann. Als letzter Parameter erfolgt eine Referenz auf ein Objekt vom Typ LocationListener. Dieses Objekt stellt Callback-Methoden zur Verfügung, über die der Provider eine neue Position bekannt gibt. Die in der Applikation verwendete Klasse, die LocationListener implementiert, ist der bereits genannte PositionListener. Er muss insgesamt vier Methoden implementieren, die ihn über neue Positionen und Änderungen am Provider informieren. Da der Anwender zum Beispiel das GPS-Gerät manuell deaktivieren kann, erhält der Listener bei Änderung der Provideraktivität Meldungen über die Methode onproviderdisabled(string) bei Deaktivierung und mit onproviderenabled(string) bei Aktivierung des als Parameter übergebenen Providers, bei

63 6.2. DATENVERWALTUNG 57 dem er sich zuvor registriert hat. Eine ähnliche Funktion hat die zu implementierende Methode onstatuschanged(string, int, Bundle), die den Listener darüber informieren kann, dass der Provider kurz- oder langfristig nicht in der Lage ist, eine Position zu bestimmen, oder dass er diese Fähigkeit wieder erhalten hat. 1 public void registerpositionlistener () { 2 // find location manager 3 LocationManager lm = ( LocationManager ) context 4. getsystemservice ( Context. LOCATION_ SERVICE ); 5 String prov = null ; 6 if ( lm. isproviderenabled ( LocationManager. GPS_ PROVIDER )) { 7 // try to obtain gps data 8 prov = LocationManager. GPS_ PROVIDER ; 9 lm. requestlocationupdates ( prov, MIN_TIME, MIN_DISTANCE, listener ); 10 } else { 11 // if gps is unavailable, get best location criteria possible 12 Criteria crit = new Criteria (); 13 crit. setaccuracy ( Criteria. ACCURACY_ FINE ); 14 crit. setcostallowed ( false ); 15 crit. setpowerrequirement ( Criteria. POWER_ LOW ); 16 // find provider 17 prov = lm. getbestprovider ( crit, true ); 18 // register listener 19 lm. requestlocationupdates ( prov, MIN_TIME, MIN_DISTANCE, listener ); 20 } 21 } Listing 6.2: Ermittlung eines Providers für die Positionsbestimmung Schließlich ist onlocationchanged(location) die Methode, mit der eine neue Position vom Provider an den Listener weitergegeben wird. Die Klasse Location kann dabei intern Informationen über Breiten- und Längengrad, Höhe, Geschwindigkeit, Richtung, Zeitpunkt, Genauigkeit und den erzeugenden Provider beinhalten. Mit Ausnahme der eigentlichen Positionsbeschreibung, des Zeitpunktes der Messung und des Providers müssen diese Daten aber nicht enthalten sein. Das Vorgehen des PositionListeners ist in diesem Fall nicht sonderlich umfangreich: Er erzeugt lediglich eine neue Instanz von GeoCoordinate, die mit den Breiten- und Längengrad- Informationen des Location-Objekts gefüllt wird. Dieses übergibt er dann an sein übergeordnetes PersonalLocation-Objekt zur weiteren Verwendung Lagebestimmung Die Applikation benötigt nicht nur die Position des Geräts, sondern ebenso Informationen über die Lage im Raum. Auch mit der Ermittlung dieser Daten betraut PersonalLocation die Klasse PositionListener. Zunächst folgt aber wieder die allgemeine Vorgehensweise zur Bestimmung von Lageinformationen. Wie auch bei der Position benötigt man einen Dienst des Systems, der einem die zu verwendenden Schnittstellen des Geräts zur Verfügung stellt. In diesem Fall ist die gesuchte Klasse

64 58 KAPITEL 6. UMSETZUNG der SensorManager, der ebenfalls über getsystemservice abgefragt werden kann. Als Parameter wird hier allerdings konsequenterweise Context.SENSOR_SERVICE übergeben. Der SensorManager verwaltet alle Sensoren des Geräts, auf die zugegriffen werden kann. Solche Sensoren umfassen neben dem Kompass auch die Erfassung des Magnetfeldes, Thermometer, Druckmesser, Beschleunigungs- oder Helligkeitssensor. Keiner dieser Sensoren muss im Gerät verbaut sein, der SensorManager stellt die Methode getsensorlist(int) zur Verfügung, um alle vorhandenen Messgeräte zu ermitteln. Um die Daten eines Sensors zu erhalten, muss man lediglich einen SensorEventListener an diesem registrieren. Auch diese Aufgabe wird über den SensorManager erledigt. Die Methode registerlistener(...) ist für verschiedene Arten der Anmeldung überladen. Sie erwartet immer eine Referenz auf den anzumeldenden Listener, den Sensor, an dem die Anmeldung erfolgen soll, und außerdem eine Abfragefrequenz. Über sie lässt sich steuern, wie oft der Listener mit neuen Daten versorgt wird, entsprechende Konstanten stellt der SensorManager zur Verfügung. Eine exakte Aussage ist die Angabe aber nicht, sie dient wie bei den Positions-Updates nur als Richtlinie für das System. 1 SensorManager sm = ( SensorManager ) context 2. getsystemservice ( Context. SENSOR_ SERVICE ); 3 sm. registerlistener ( listener, 4 sm. getdefaultsensor ( Sensor. TYPE_ ORIENTATION ), 5 SensorManager. SENSOR_ DELAY_ UI ); Listing 6.3: Anmeldung eines Listeners an einen Sensor Listing 6.3 stellt dar, wie ein Listener an einem Sensor registriert wird. In diesem Fall wird ein geeignetes Messinstrument gesucht, um die Lage des Geräts zu bestimmen. Hier wird kein spezieller Sensor angesprochen, sondern das vom System festgelegte Standardgerät genutzt. Die Aktualisierungsrate entspricht einem Wert, der mit der Aktualisierungsgeschwindigkeit der grafischen Komponenten einhergeht. Der soeben angemeldete Listener ist, wie schon beschrieben, vom Typ SensorEventListener und reagiert dementsprechend auf Ereignisse vom Typ SensorEvent. Das Interface schreibt vor, dass der Listener zwei Methoden implementieren muss, nämlich onaccuracychanged(sensor, int) und onsensorchanged(sensorevent). Die erste Methode ermöglicht es dem Programm, auf eine Änderung der Genauigkeit der Messungen zu reagieren. Über onsensorchanged erfolgt eine Benachrichtigung über eine aktualisierte Messung, deren Daten im übergebenen Event enthalten sind. Wie bei vielen vorhandenen Klassen der API sind auch im SensorEvent alle Daten öffentlich zugreifbar, um Methodenaufrufe zu sparen. Diese Daten umfassen den beteiligten Sensor, die Genauigkeit der Messung, den Zeitpunkt dieser Messung und die Messwerte. Letztere können je nach Sensor unterschiedlich sein, gespeichert werden sie allerdings immer in einem float-array, dessen Einträge entsprechend unterschiedliche Bedeutungen haben können. Wichtig für die Navigationsanwendung sind insbesondere die Daten, welche vom Lagesensor gespeichert werden. Daher werden sie im Gegensatz zu anderen hier noch einmal im Detail erklärt.

65 6.2. DATENVERWALTUNG 59 Das Wert-Array einer Lagebestimmung besteht aus drei Einträgen und beschreibt die Lage des Geräts in einem eigenen Koordinatensystem. Diese spiegeln wider, wie das Gerät in Bezug auf die x-, y- und z-achse orientiert ist. Die Achsen sind hierbei rechtshändig orientiert, x zeigt bei einem flach auf der Erde liegenden Gerät nach rechts, y nach vorne und z in den Himmel. Erster Eintrag des Arrays ist die Rotation um die z-achse in Grad. Hierbei wird der Winkel zwischen der Richtung des magnetischen Nordpols und der y-achse angegeben, dieser wird bei 0 beginnend im Uhrzeigersinn gemessen, so dass 90 Grad für Osten stehen, 180 für Süden und 270 für Westen. Dieser Eintrag ist also der Kompass. An zweiter Stelle des Arrays steht die Rotation um die x-achse, also der Anstellwinkel des Geräts. Er wird ebenfalls in Grad angegeben, mit Werten von -180 bis Grad beschreibt hierbei das flach auf dem Boden liegende Gerät, positive Werte zeigen an, dass die z-achse auf den Betrachter zeigt. Der letzte Wert des Arrays gibt die Rotation des Geräts um die y-achse an. Diese Werte bewegen sich im Bereich von -90 bis 90 Grad, wobei negative Werte bedeuten, dass die rechte Kante des Geräts niedriger ist und positive Werte, dass sich die linke Kante unterhalb der rechten befindet. Es ist dabei nicht erkennbar, ob das Gerät mit Vorder- oder Rückseite nach oben liegt. Wie auch bei den Positionsupdates reicht der PositionListener die erhaltenen Daten lediglich an die PersonalLocation weiter, so dass diese sie speichern und an interessierte andere Objekte weiterleiten kann Kommunikation zwischen Threads Eine Eigenschaft der Klasse PersonalLocation ist, dass sie ihre Verwaltung von ein- und ausgehenden Nachrichten in einem eigenen Thread abwickelt. Dies ist wichtig, da sie auch Operationen durchführt, die Datenabfragen aus dem Internet beinhalten. Liefen diese mit im Hauptthread der Anwendung, würden sie die Applikation blockieren und so keine vernünftige Interaktion mit dem Nutzer zulassen. Nun stellt sich die Frage, auf welche Art verschiedene Threads miteinander kommunizieren können. Im vorangegangen Abschnitt wurde beschrieben, wie ein PositionListener Daten vom LocationManager empfängt. Dieser läuft in einem Systemthread, also finden die dortigen Methodenaufrufe außerhalb des Threads der PersonalLocation statt. In Java ist es so, dass kein eingebauter Mechanismus vorliegt, der eine direkte Kommunikation mehrerer Threads untereinander ermöglicht. Im Allgemeinen werden Message-Queues für auszutauschende Objekte genutzt, über Synchronisation mit wait und notify werden Hilfskonstrukte zur Benachrichtigung verwendet. Da es unter Android häufig notwendig ist, multiple Threads einzusetzen, stellt die API eine deutlich elegantere Lösung für die Kommunikation mehrerer Threads zur Verfügung. Diese basiert im Wesentlichen auf den Klassen Looper, Handler und Message. Sie sollen hier kurz eingeführt werden, da sie auch in PersonalLocation eine wichtige Rolle spielen. Messages sind Objekte, die zur Versendung von Informationen dienen. Sie können eine Reihe von Daten speichern, dazu stellen sie zum einen zwei öffentliche Attribute vom Typ int bereit, eine öffentliche Referenz von Typ Object und ein weiteres int-attribut what, über das bequem eine Art Betreff für die Nachricht gesetzt werden kann. Reichen diese Datenfelder mit Schnellzugriff nicht

66 60 KAPITEL 6. UMSETZUNG aus, kann außerdem noch ein Bundle angehängt werden, in dem weitere Daten abgelegt werden können. Wie bereits im Abschnitt zu Android beschrieben, können diese primitive Datentypen, Strings und serialisierbare Objekte umfassen. Außerdem besteht die Möglichkeit, der Message ein Ziel zuzuweisen, an das sie dann geschickt wird. Ein solches Ziel ist die Klasse Handler. Jeder Handler dient dazu, Nachrichten zu empfangen und zu verarbeiten. Wie diese Verarbeitung vonstatten geht, ist Sache des Entwicklers. Er muss hierfür eine Klasse schreiben, die von Handler erbt, und die Methode handlemessage(message) überschreiben. An diese Methode wird jede Nachricht, die an den Handler geschickt wird, weitergeleitet. Hier hat der Programmierer dann die Möglichkeit, je nach Art der Message verschiedene Aktionen durchzuführen. Es gibt dabei eine Vielzahl von Möglichkeiten der Versendung einer Nachricht. Zum einen kann an einem Message-Objekt mit gesetztem Ziel die Methode sendtotarget() aufgerufen werden. Auch stellt der Handler sendmessage(message) und sendmessageattime(message, long) zur Verfügung, mit denen eine Nachricht zu beliebigen Handlern geschickt werden kann. Der folgende Code zeigt, wie von einem Handler eine Nachricht erzeugt wird, die dann mit Werten gefüllt an ihn geschickt wird: handler.obtainmessage(personallocation.position_changed, arg).sendtotarget(); In dem vorliegenden Fall wird der Handler in PersonalLocation benachrichtigt, dass eine Positionsänderung stattgefunden hat. obtainmessage() erzeugt eine zu einem Handler gehörende Nachricht, die bereits gesetzte Attribute beinhaltet. Im vorliegenden Fall wären dies die Attribute what und die Object-Referenz. Auch eine andere Erzeugungsrichtung ist möglich, indem man die statische Methode Message.obtain(...) aufruft, der wiederum verschiedene Parameter zum Setzen der Attribute übergeben werden können. Zwar kann eine Message auch über einen Konstruktor erzeugt werden, die Verwendung der statischen Methoden wird allerdings empfohlen, da systemintern so eventuell Leistungsverbesserungen umgesetzt werden können. 1 public void run () { 2 // get message queue for this thread 3 Looper. prepare (); 4 // register handler for this queue 5 handler = new Handler ( Looper. mylooper ()) { 6 public void handlemessage ( Message m) { 7 // code missing 8 } 9 }; 10 // wait for messages 11 Looper. loop (); 12 } Listing 6.4: Beispiel für die Nutzung eines Loopers Grundsätzlich sind Handler auch unabhängig von Threads für die Kommunikation zwischen Objekten einsetzbar, indem sie zum Beispiel als Delegate oder Listener benutzt werden. In Bezug auf Parallelisierung werden sie besonders mit der Klasse Looper interessant. Ein Thread, der

67 6.2. DATENVERWALTUNG 61 Nachrichten empfangen und verarbeiten soll, kann mit ihr genau diese Aufgabe erfüllen. Dafür muss er zunächst mit Looper.prepare() eine Nachrichten-Warteschlange anlegen, die von der Klasse Looper verwaltet wird. Über Looper.myLooper() erhält man das dem Thread zugeordnete Looper-Objekt, das man dann bei der Initialisierung eines neuen Handlers übergeben kann. Auf diese Weise wird der Handler an die Nachrichten-Schlange des passenden Loopers gebunden. Ist der Handler angebunden, so muss der Entwickler noch Looper.loop() aufrufen, womit von nun an der Thread auf eingehende Nachrichten wartet, bis quit() aufgerufen wird. Weil die quit- Methode im Gegensatz zu den anderen nicht statisch ist, kann nur der betroffene Thread über die mylooper-methode für eine Beendigung sorgen Datenaustausch mit PersonalLocation Der vorangegangene Abschnitt hat erklärt, wie man unter Android einen Austausch von Informationen über Threadgrenzen hinweg realisieren kann. Dieses System wird auch in PersonalLocation angewandt. Die Klasse stellt eine Reihe von Konstanten zur Verfügung, mit denen eine Nachricht eingeordnet werden kann. Diese Konstanten werden bidirektional genutzt: Zum einen informieren beauftragte Objekte wie der PositionListener die PersonalLocation darüber, dass eine Änderung der Daten vorgenommen werden kann, zum anderen reicht PersonalLocation die Benachrichtigung über geänderte Inhalte weiter an interessierte Objekte. PersonalLocation verwaltet zwei Listen von Beobachtern: Eine Gruppe, die auf Änderungen die Position und Lage des Geräts betreffend warten, und eine Menge, die über Änderungen bei Routeninformationen informiert werden. Diese Unterscheidung soll dazu beitragen, dass ohne größeren Aufwand nur notwendige Kommunikation stattfindet. 1 /** status telling that the orientation of the device changed */ 2 public static final int ORIENTATION_ CHANGED = 1; 3 4 /** status telling that the position of the device changed */ 5 public static final int POSITION_ CHANGED = 2; 6 7 /** status telling that a new route was calculated */ 8 public static final int ROUTING_ DONE = 4; 9 10 /** status telling that a routing call was unsuccessful */ 11 public static final int ROUTING_ FAILED = 5; Listing 6.5: Status-Konstanten für Nachrichten in PersonalLocation Auch die Datenübermittlung zu anderen Objekten geschieht über Handler-Instanzen. Um Nachrichten über Aktualisierungen zu erhalten, muss sich jeder Handler in der entsprechenden Liste registrieren. Hierfür besitzt PersonalLocation die add-methoden addmovementhandler(handler) und addroutinghandler(handler). Entscheidend ist an dieser Stelle, dass auf die Einführung eines zusätzlichen Interfaces verzichtet wird, das feste Callbacks vorschreibt. Der Handler ist selbst dafür verantwortlich, dass er übergebene Nachrichten verarbeiten kann.

68 62 KAPITEL 6. UMSETZUNG Die Daten, die ein Handler erwarten kann, werden über einen informellen Vertrag geregelt, in dem jeweils der Statuscode und ein Datenobjekt festgelegt werden. Welche Status-Meldungen in Frage kommen, sieht man in Listing 6.5. Ein Positionswechsel wird dabei mit einer neuen Position ergänzt, bei einem Wechsel der Gerätelage wird das beschreibende Array mitgeliefert und beim Abschluss einer Routenfindung die ermittelten Wegpunkte in einem Array. Für den Fall, dass keine Route gefunden werden konnte, werden keine zusätzlichen Daten weitergereicht. Wichtig ist der Zusatz, dass Daten nicht auf dem übergebenen Original geändert werden sollten. Aus Leistungsgründen wird darauf verzichtet, jedem Handler eine Kopie der Daten zu übergeben, daher kann eine Änderung zu Seiteneffekten führen, die nicht nur die Datenhaltung in PersonalLocation betreffen, sondern ebenso die Verarbeitung der Nachrichten anderer Handler. Mit diesen Ausführungen soll die Betrachtung der Klasse PersonalLocation zunächst abgeschlossen sein. Ein Blick in die Dokumentation verrät, dass noch eine Reihe weiterer Methoden vorhanden ist. Diese werden im Rahmen der nächsten Abschnitte behandelt, da sie wesentliche Bestandteile anderer Komponenten darstellen. 6.3 Sehenswürdigkeiten An dieser Stelle wird nun betrachtet, wie die Applikation die Ermittlung von Informationen über Sehenswürdigkeiten, im Weiteren auch Englisch Point Of Interest genannt, abwickelt. Die Idee dabei ist, eine möglichst leicht erweiterbare Schnittstelle zur Verfügung zu stellen, um Informationen aus multiplen Quellen zu erhalten. Grundlage für alle Sehenswürdigkeiten bildet das Interface POI. Es beschreibt die Grundeigenschaften jedes Points of Interest. So sind bereits Methoden vorgegeben, durch die für jede Sehenswürdigkeit auf eine eindeutige Position oder eine Beschreibung zugegriffen werden kann. Außerdem können Klassen die Methode getview(context) so implementieren, dass eine fertige, auf dem Bildschirm anzeigbare, grafische Oberfläche zur Verfügung gestellt wird. Da aber eventuell auch Orte verwaltet werden sollen, die keine eigene Darstellung besitzen, darf die Methode ausdrücklich auch null zurückgeben. Abbildung 6.2 zeigt ein Klassendiagramm der enthaltenen Basisklassen für Sehenswürdigkeiten. Neben dem Interface POI ist dies insbesondere die Klasse PointOfInterest, welche genutzt werden kann, um auf eine Grundimplementation aufzubauen. Die Klasse ist eine um die Methoden von POI ausgebaute Erweiterung von GeoCoordinate, über die daher die Positionsdatenverwaltung erledigt wird. Weil die Art der Sehenswürdigkeit hier nicht eindeutig ist, wird auf die Bereitstellung einer View verzichtet. Dafür implementiert die Klasse zusätzlich das Markerinterface ContextMenu.ContextMenuInfo. Hierdurch wird ermöglicht, dass ein Kontextmenü Instanzen der Klasse nutzen kann, um bei einem Klick zusätzliche Informationen für das registrierte Callback bereitzustellen. Eine Erweiterung des Interfaces POI stellt das Interface WebPOI dar. Die einzige zusätzliche Fähigkeit hier ist, dass die Sehenswürdigkeit eine Methode bereitstellt, mit der man eine URL erhält, die auf eine Homepage mit weiterführenden Informationen leitet.

69 6.3. SEHENSWÜRDIGKEITEN 63 Abbildung 6.2: Basisklassen der Sehenswürdigkeiten Um nun Sehenswürdigkeiten von bestimmten Quellen abrufen zu können, müssen mehrere Schritte durchgefüht werden. Zunächst einmal muss eine Klasse geschrieben werden, die diese Daten ermitteln kann. Sie muss das Interface POIProvider implementieren. Es enthält genau eine Methode, receivepois(geocoordinate, int, String). Diese gibt ein Array mit gefundenen Sehenswürdigkeiten zurück. Als Übergabeparameter braucht die Methode dafür einen Ort, in dessen Umgebung gesucht werden soll, in Form einer GeoCoordinate. Außerdem muss eine Maximalentfernung zu diesem Punkt, in deren Radius noch gesucht werden soll, angegeben werden. Diese Angabe erfolgt in Metern, der Übergabewert ist vom Typ int. Soll nach einer besonderen Sehenswürdigkeit gesucht werden, kann außerdem noch ein String übergeben werden, der eine Beschreibung des gesuchten Ortes beinhaltet. Damit der Provider bei der verwaltenden Instanz von PersonalLocation registriert werden kann, muss der voll qualifizierte Name der Klasse in ein Array eingetragen werden, das bei Erzeugung der PersonalLocation ausgelesen wird. Dieses Array ist statischer Inhalt und befindet sich in der Ressourcen-Datei res/values/arrays.xml. Die Instanziierung der Provider geschieht letztlich durch Reflection, um den Code in PersonalLocation nicht ändern zu müssen, wenn ein weiterer Provider zur Applikation hinzugefügt wird. Das genaue Vorgehen der Initialisierung ist in Listing 6.6 dargestellt. Hier sieht man den Ausschnitt des Konstruktors, der für die Umsetzung verantwortlich ist. Deutlich wird, dass PersonalLocation den Namen des Providers nicht selbst kennt. Jeder angelegte Provider wird in einer Map gespeichert, in der als Wert zusätzlich hinterlegt wird, an welchem Ort die letzte Abfrage stattgefunden hat. Das Konzept der Vorhaltung des letzten Aktualisierungsortes soll unnötigen Datenverkehr unterbinden. So kann sichergestellt werden, dass Daten nicht erneut abgefragt werden, obwohl nur

70 64 KAPITEL 6. UMSETZUNG eine Positionsänderung von wenigen Metern stattgefunden hat. Wie die Abfrage der Daten genau vonstatten geht, sieht man im Listing 6.6 in der Methode updatepois(). Hier werden zunächst die Einträge aus der Map ausgelesen und dann sämtlich durchlaufen. Für jeden Provider wird geprüft, ob seit der letzten Aktualisierung eine Distanz von mindestens einem Kilometer zurückgelegt wurde. Nur wenn das der Fall ist, wird wirklich nach neuen Sehenswürdigkeiten gefragt. 1 public PersonalLocation ( Context c) { 2 // find POI providers 3 String [] providerstrings = c. getresources (). getstringarray ( 4 R. array. poiproviders ); 5 for ( String pr : providerstrings ) { 6 try { 7 // create them 8 POIProvider p = ( POIProvider ) Class. forname ( pr ). newinstance (); 9 poiproviders. put (p, new GeoCoordinate (0., 0.)); 10 } catch ( Exception e) { 11 e. printstacktrace (); 12 } 13 } 14 // } private void updatepois () { 18 // go through all providers 19 Set < Map. Entry < POIProvider, GeoCoordinate >> entries = poiproviders 20. entryset (); 21 for ( Map. Entry < POIProvider, GeoCoordinate > entry : entries ) { 22 GeoCoordinate ort = entry. getvalue (); 23 final POIProvider prov = entry. getkey (); 24 // if moved more than 1000 metres, start a search 25 if ( ort. distanceto ( position ) * GeoCoordinate. EARTH_ RADIUS > 1000.) { 26 // as we do not know how long this may take, do it in a new thread 27 Runnable r = new Runnable () { 28 public void run () { 29 // receive data 30 POI [] erg = prov. receivepois ( position, distance, null ); 31 // give it back to the system 32 Message m = new Message (); 33 m. obj = erg ; 34 poidelegate. sendmessage ( m); 35 } 36 }; 37 new Thread (r). start (); 38 entry. setvalue ( new GeoCoordinate ( position )); 39 } 40 } 41 } Listing 6.6: Instanziierung der POI-Provider Die Abfrage der Points of Interest ist ein synchroner Aufruf, die aufgerufene receivepois-methode

71 6.3. SEHENSWÜRDIGKEITEN 65 muss also vollständig durchgelaufen sein, bevor der Aufrufer mit seinem eigenen Code fortfahren kann. Aus diesem Grund wird der Abruf der Daten parallelisiert. Für jeden zu aktualisierenden Provider wird ein eigener Thread gestartet, der die Informationen abfragt. Das Ergebnis reicht er dann weiter an ein Delegate, das für die Einordnung der Inhalte in den Speicher der PersonalLocation zuständig ist. Dieses Delegate ist ein POIHandler, der als Callback-Objekt für einen Handler fungiert. Indem ein Programmierer eine Implementation von Handler.Callback bereitstellt, kann er darauf verzichten, eine Unterklasse des Handlers erzeugen zu müssen. An dieser Stelle soll die gewählte Umsetzung beispielhaft die Verwendung des Callbacks zeigen und das Delegation-Pattern verstärken. Inhaltlich hat der POIHandler wenig Arbeit zu erledigen. Er durchläuft das erhaltene Array und fügt die einzelnen Einträge in das Set ein, in dem PersonalLocation die Sehenswürdigkeiten verwaltet. Anschließend lässt er die angemeldeten Movement-Handler informieren, indem er eine entsprechende Nachricht an den Thread der PersonalLocation schickt. Zum Abschluss wird in der update-methode noch in der Map vermerkt, dass der gerade behandelte Provider auf der aktuellen Position zum letzten Mal Daten geliefert hat Beispiel-Implementierung Wikipedia-Provider Als Datenquelle für Sehenswürdigkeiten in der Applikation dient die Wikipedia. Sie stellt eine große Zahl von Artikeln zur Verfügung, von denen viele mit Geotags ausgestattet sind. Wie schon im vorangegangenen Kapitel beschrieben, wird zur Auffindung der Artikel ein Webservice von Geonames eingesetzt, der Ergebnisse im XML-Format liefert. Die Umsetzung benötigt insgesamt drei Klassen, die in diesem Abschnitt nacheinander ausführlich vorgestellt werden sollen. Den Beginn macht dabei die Klasse WikipediaArticle, die als Subklasse von PointOfInterest den in einem Artikel beschriebenen Ort repräsentiert. Abbildung 6.3: WikipediaArticle als Klassendiagramm Eine der Erweiterungen, die ein Wikipedia-Artikel gegenüber einem normalen Point of Interest

72 66 KAPITEL 6. UMSETZUNG besitzt, ist eine zugeordnete Internetseite. Aus diesem Grund implementiert WikipediaArticle zusätzlich das Interface WebPOI. Außerdem stellt die Klasse eine Kurzzusammenfassung des Artikels über die Methode getcontent() zur Verfügung. Ebenso wurde die Methode getview überschrieben. In ihr wird nun ein Wikipedia-Symbol in einer ImageView zurückgegeben. Um den Speicherbedarf zu reduzieren, wird dabei das Singleton-Pattern angewandt, indem das Bild selbst nur einmal geladen und dann von allen Instanzen genutzt wird. Auf diese Weise soll vor allem Speicher gespart werden. Zweiter Teil der Implementierung ist der eigentliche Provider. WikipediaProvider implementiert das Interface POIProvider und stellt entsprechend die Methode receivepois bereit. Außerdem existiert eine private Methode parseurl(geocoordinate, int), in der die URL für die Webservice-Anfrage zusammengesetzt wird. Die entsprechenden Informationen für Radius, Breiten- und Längengrad werden in die im letzten Kapitel beschriebene Form gebracht und anschließend als Gesamt-URL zurückgegeben. Die maximale Anzahl Artikel, die eine Abfrage liefern soll, wird dabei fest auf 100 begrenzt. Um das Ergebnis der Anfrage an den Webservice verarbeiten zu können, wird ein SAX-Parser eingesetzt. Dieser verarbeitet ein XML-Dokument in einer ereignisorientierten Form. Das bedeutet, dass er für den Fall, dass er beim Einlesen des Dokuments ein neues Element erreicht oder das aktuelle Element verlässt, ein Delegate benachrichtigt. Dieses ist vom Typ ContentHandler und wird vor dem Einlesen beim Parser angemeldet. 1 // getting a SAX xml parser and reader 2 SAXParserFactory factory = SAXParserFactory. newinstance (); 3 XMLReader xml ; 4 try { 5 SAXParser parser = factory. newsaxparser (); 6 xml = parser. getxmlreader (); 7 } catch (...) { 8 //... 9 } 10 // content handler implementation 11 WikiHandler x = new WikiHandler (); 12 xml. setcontenthandler ( x); 13 // webservice url 14 URL url = parseurl ( where, radius ); 15 InputSource input = new InputSource (); 16 try { 17 // open the stream and parse 18 input. setbytestream ( url. openstream ()); 19 xml. parse ( input ); 20 return x. getentries (); 21 } catch (...) { 22 // } Listing 6.7: Nutzung eines SAX-Parsers in Android Um eine Parser-Instanz zu erhalten, muss man eine eingebaute Factory-Klasse ansprechen, die ein entsprechendes Objekt zurückliefert. Dieses Vorgehen ist in den ersten sechs Zeilen von Lis-

73 6.3. SEHENSWÜRDIGKEITEN 67 ting 6.7 dargestellt. Anschließend wird, wie schon beschrieben, der ContentHandler erzeugt und eingehängt. Um dem Parser mitzuteilen, woher das XML-Dokument stammt, wird eine Instanz der Klasse InputSource angelegt, das intern einen Datenstrom verwaltet. Dem Aufruf der Methode parse() am Parser wird diese Quelle übergeben. Nun arbeitet der Parser das angegebene Dokument sequentiell ab und verlässt die parse-methode erst, wenn der gesamte Vorgang abgeschlossen ist. Wie im Codeausschnitt zu sehen, ist das Delegate ein Objekt der Klasse WikiHandler. Die Klasse, die das Interface ContentHandler implementiert, beschränkt sich dabei im Wesentlichen auf drei Methoden. Acht weitere Methoden, die das Interface ebenfalls vorschreibt, sind nur leer implementiert, weil sie für die Datenverarbeitung nicht nötig sind. Die gesammelten Daten speichert der WikiHandler in einem Set von WikiArticles. Die Tatsache, dass der XML-Baum im vorliegenden Fall nur maximal drei Elemente tief ist, macht die Verarbeitung entsprechend leicht. Reagiert werden muss jeweils auf den Beginn eines Elementes, das Ende eine Elementes und das Einlesen dazwischen liegender Zeichen. Der Text innerhalb eines Elements wird mit der Methode characters(char[], int, int) an den Handler übergeben. Die beiden ints geben an, wo im Array das erste neue Zeichen steht und wie lang die gelesene Sequenz ist. Es ist möglich, dass nicht der gesamte Text auf einmal gelesen wird, entsprechend muss an dieser Stelle ein Buffer mitgeführt werden. 1 public void endelement ( String uri, String localname, String qname ) 2 throws SAXException { 3 if ( localname. equals (" entry ")) { 4 // entry means : that s a whole article 5 // find the attributes for latitude and longitude 6 double lat = Double. parsedouble ( attributes. get (" lat " )); 7 double lon = Double. parsedouble ( attributes. get (" lng " )); 8 GeoCoordinate gc = new GeoCoordinate ( lat, lon ); 9 // find the article s title, summary and url 10 String name = attributes. get (" title "); 11 String summary = attributes. get (" summary "); 12 String wikiurl = attributes. get (" wikipediaurl "); 13 URL url ; 14 try { 15 url = new URL ( wikiurl ); 16 } catch ( MalformedURLException e) { 17 return ; 18 } 19 // build the article and add it to the list 20 WikipediaArticle put = new WikipediaArticle ( gc, name, url, summary ); 21 articles. add ( put ); 22 } else { 23 // here we only found an attribute 24 attributes. put ( localname, buffer. tostring ()); 25 } 26 } Listing 6.8: Umgang des ContentHandlers mit einem schließenden Knoten

74 68 KAPITEL 6. UMSETZUNG Über startelement(string, String, String, Attributes) wird der ContentHandler informiert, wenn ein neuer Knoten geöffnet wurde. Als Parameter werden der Namespace des Knotens, sein Kurzname und sein voll qualifizierter Name übergeben, außerdem die im Tag enthaltenen Attribute in einem Objekt als Schlüssel-Wert-Paare. Für den WikiHandler einzig interessant sind beginnende entry-knoten. Wird ein solcher gefunden, legt der Handler intern ein neues Set an, in dem die Daten der benötigten Unterknoten gespeichert werden sollen. Liegt ein anderen Knotentyp vor, wird lediglich ein neuer Buffer für die folgende Textpassage erzeugt. Aufwendiger ist der Umgang mit der Methode endelement(string, String, String). Auch hier werden ein Namespace, der lokale und der voll qualifizierte Name des gerade geschlossenen Knotens übergeben. Aufgrund der Tatsache, dass gleichnamige Knoten nicht ineinander geschachtelt werden, ist die Zuordnung eines schließenden Tags zu einem öffnenden immer sehr einfach. Ein zuvor geöffnetes Nicht-entry-Tag wird immer direkt als nächstes wieder geschlossen, zu jedem öffnenden entry gehört auch der nächste schließende mit gleichem Namen. Listing 6.8 zeigt den Code des WikiHandlers beim schließenden Tag. Wird ein Knoten abgeschlossen, der nicht entry ist, so werden der Name des Knotens und der eingelesene Inhalt zwischen den Tags in die Map des aktuell geöffneten entry-elements abgelegt. Endet der entry-knoten, werden die benötigten Einträge aus dieser Map wieder ausgelesen. Dazu gehören die geographische Breite und Länge, außerdem Titel, Artikelzusammenfassung und natürlich die Adresse des Artikels. Diese Daten werden in einem WikipediaArticle-Objekt gekapselt und in die Menge der bereits existierenden Artikel eingefügt. Ist der Parser vollständig durch das gelieferte XML-Dokument gelaufen, so liest der Provider anschließend über eine get-methode die Inhalte der Artikelmenge aus und reicht sie, in ein Array kopiert, an den aufrufenden Thread zurück. Dieser übermittelt sie dann in dem schon beschriebenen Verfahren wieder an die PersonalLocation-Instanz, die alle angemeldeten Movement-Handler benachrichtigt. 6.4 Routenbestimmung Grundlegender Bestandteil jeder Navigationssoftware ist die Routenberechnung. Ihre Bestandteile sollen in diesem Abschnitt ausführlich präsentiert werden. Ziel ist es, neben der eigentlichen Implementierung auch eine Schnittstelle zu definieren, die es erlaubt, zu einem späteren Zeitpunkt bei Bedarf die einzelnen Bestandteile auszutauschen, um beispielsweise auf anderes Kartenmaterial zurückgreifen zu können. Die Bestimmung einer Route zerfällt in zwei wesentliche Schritte, wie sie auch schon in Abbildung 3.2 angedeutet wurden. Damit der Anwender überhaupt einem Weg folgen kann, muss er sich zunächst für ein Ziel entscheiden und vom System anschließend die Route berechnen lassen. Beide Vorgänge werden in der Applikation von unterschiedlichen Komponenten vorgenommen. Die Zielpunktbestimmung wird in vielen Fällen von einer eigenen Activity vorgenommen, der RoutingActivity. Ihr genauer Aufbau wird im Abschnitt zur Programmoberfläche genauer beschrieben. Sie stellt, wie auch das Anwendungsfalldiagramm bereits gezeigt hat, im Wesentlichen

75 6.4. ROUTENBESTIMMUNG 69 zwei verschiedene Suchmöglichkeiten zur Verfügung. Der Anwender hat die Möglichkeit, eine Adresse einzugeben, die das System dann auflöst und als Ziel der Reise verwendet. Außerdem kann er auf einer Umgebungskarte einen beliebigen Punkt auswählen, zu dem er dann vom Programm geleitet wird. Außerhalb der RoutingActivity kann der Benutzer alternativ auch direkt eine angezeigte Sehenswürdigkeit auswählen, die von der Applikation dann wiederum zur Wegberechnung herangezogen wird. Die nach der Zielpunktbestimmung angestoßenen Mechanismen zur eigentlichen Routenberechnung sind sehr übersichtlich. Zunächst muss das jeweils zuständige Modul aus dem gewählten Ziel eine gültige geographische Koordinate erzeugen, also den auf der Karte gewählten Punkt auswerten, die Adresse auflösen oder den Ort der Sehenswürdigkeit ermitteln. Die so entstandene Instanz vom Typ GeoCoordinate kann an eine innere Klasse der Hauptkomponente MainActivity weitergereicht werden, die über eine Methode findroute(geocoordinate) verfügt. Diese kommuniziert mit der PersonalLocation-Instanz des Programms. PersonalLocation erhält eine Nachricht, deren Status entweder CALCULATE_ROUTE_TO oder aber CALCULATE_ROUTE_FROM_TO sein muss und der zudem die GeoCoordinate beigefügt wird. Die Datenverwaltung verfügt über die Methode calculateroute(...), die entweder eine oder zwei Koordinaten als Parameter erwartet. Wird nur der Zielort übergeben, verwendet sie als Startpunkt die aktuelle Position des Geräts. In der Applikation wird dieses Vorgehen immer das gewählte sein. Die aufgerufene Methode leitet die Anfrage dann in einem asynchronen Aufruf an die für Routenberechnungen zuständige Routing-Komponente weiter. Wurde eine Route berechnet oder liegt ein Fehler vor, erhält die PersonalLocation per Callback eine Benachrichtigung über diesen Umstand. Sie benachrichtigt dann alle angemeldeten Routing- Handler über die berechnete Route oder den bei der Berechnung aufgetretenen Fehler. In der Reihenfolge der Nutzerinteraktion werden nun auch die einzelnen Teile der Routenberechnung abgehandelt. Begonnen werden soll damit, wie aus der Aktion des Anwenders das gewünschte Ziel als GeoCoordinate bestimmt wird Zielbestimmung Der einfachste Fall der Zielpunktbestimmung ist die Auswahl einer Sehenswürdigkeit. Geschehen wird dies über einen Eintrag im Kontextmenü, das der View des Points of Interest zugeordnet ist. Die Basisklasse PointOfInterest aller Sehenswürdigkeiten kann, wie zuvor schon beschrieben, als zusätzliches Argument bei Kontextmenü-Interaktionen übergeben werden. Da sie zudem schon von GeoCoordinate abgeleitet ist, stellt es kein Problem mehr dar, den Ort der Sehenswürdigkeit zu bestimmen, da das repräsentierende Objekt selbst die nötige Information ist. Es kann direkt an die findroute-methode übergeben werden. Ähnlich einfach ist die Ermittlung bei der Auswahl eines Punktes auf einer Kartenansicht. Die für die Bereitstellung der Karte zuständige Klasse MapWrapper ermöglicht es dem Entwickler, ein Delegation-Objekt TouchDelegate zu übergeben, das eine Methode ontouch(geocoordinate) beinhaltet. Wird auf der Karten-View ein Touch-Event ausgelöst, ruft die Wrapperklasse diese Methode am Delegate auf. So lässt sich ebenfalls schnell der gewünschte Zielort bestimmen.

76 70 KAPITEL 6. UMSETZUNG Komplizierter wird das Auflösen einer Adresse in eine geographische Position. Hierfür muss man sich einer Datenbank mit Georeferenzinformationen bedienen. Da diese aufgrund ihrer zu erwartenden Größe und ansonsten fehlender Aktualität wiederum als Onlinedienst abgefragt werden soll, wird zuerst eine Schnittstelle für generische Dienste entworfen. Abbildung 6.4: Geocoding-Schnittstelle mit Callback Das Interface GeoCodingComponent stellt zwei Methoden zur Verfügung, die entweder synchron oder asynchron die zu einer Adresse gehörenden Orte ermitteln. Die Dokumentation erlaubt es hierbei ausdrücklich, dass eine Geocoding-Klasse nur eine der beiden Methoden implementiert und bei der anderen eine Exception wirft. Um den asynchronen Methodenaufruf nutzen zu können, muss die benutzende Klasse ein Objekt bereitstellen, welches das Callback-Interface GeoCodingListener implementiert. Dieses beinhaltet die Methoden onplacesfound(string, PointOfInterest...), die für einen Suchbegriff die gefundenen Orte übergeben bekommt, und onerror(), die über einen aufgetretenen Fehler informiert. Sowohl die asynchrone als auch die synchrone Suchmethode findnear haben die restlichen Parameter gemeinsam, wie auch Abbildung 6.4 zeigt. Benötigt wird zunächst der Begriff, durch den die Adresse beschrieben wird. Er kann eine Postanschrift ebenso repräsentieren wie den Namen einer Pizzeria. Als weiterer Parameter muss eine Koordinate angegeben werden, in deren Nähe gesucht werden soll. Sie soll helfen, die Suche näher einzugrenzen. Der dritte Parameter ist vom Typ int, er gibt die maximale Entfernung zwischen der gefundenen Adressposition und dem zuvor genannten Bezugsort an. Durch Eintragen des konstanten Wertes EVERYWHERE kann diese Beschränkung ignoriert werden Umsetzungen der Adresssuche Eine Implementation, die Adresssuche umsetzt, verwendet Daten von OpenStreetMap. Der OSM- Namefinder-Webservice wurde bereits im vorangegangenen Kapitel ausführlich beschrieben. Er stellt die Daten im XML-Format zur Verfügung. Die Datenverarbeitung geschieht wiederum mit Hilfe eines SAX-Parsers, wie er schon im Abschnitt über Sehenswürdigkeiten beschrieben wurde. Vom Prinzip her läuft die Analyse des XML-Dokuments entsprechend ab, nur die Details der Verarbeitung unterscheiden sich, da die Dokumentstruktur deutlich anders ist. Da ein SAX-Parser verwendet wird, der die abgerufenen Daten sequentiell abarbeitet, unterstützt die Klasse OSMGeoCoding nur den synchronen Suchaufruf. Auf eine zusätzliche Klasse zur Be-

77 6.4. ROUTENBESTIMMUNG 71 reitstellung der Daten wird verzichtet, da PointOfInterest bereits die nötigen Datenfelder liefert und die Suchmethode ohnehin ein entsprechendes Array von diesem Typ zurückliefern soll. Das Vorgehen beginnt vergleichbar mit dem des WikipediaProviders. Es wird eine neue Parser- Instanz erzeugt und der ContentHandler initialisiert. Nach Erzeugung der aufzurufenden URL und der Zuweisung der Datenquelle beginnt dann das Parsing. 1 String name = atts. getvalue ("", " name "); 2 double lat = Double. parsedouble ( atts. getvalue ("", " lat " )); 3 double lon = Double. parsedouble ( atts. getvalue ("", " lon " )); 4 GeoCoordinate gc = new GeoCoordinate ( lat, lon ); 5 if ( pos. distanceto ( gc) * GeoCoordinate. EARTH_ RADIUS <= maxdistance 6 maxdistance == GeoCodingComponent. EVERYWHERE ) { 7 // if the location is within radius, take it 8 PointOfInterest put = new PointOfInterest ( gc, name ); 9 articles. add ( put ); 10 } Listing 6.9: Erzeugung eines Ergebnisses bei der Adresssuche Die meisten Inhalte des ContentHandlers OSMGeoHandler finden sich in startelement. Eine Vereinfachung der Struktur gegenüber dem XML-Dokument von Geonames ist, dass der Namefinder fast alle Daten eines Knotens als Attribut übergibt, wie auch Listing 5.2 zeigte. Alle inneren Knoten unterhalb der named-elemente können zudem ignoriert werden. Entsprechend ist es möglich, nur durch Auslesen der Attribute eines öffnenden named-tags einen PointOfInterest zu erzeugen. Listing 6.9 zeigt, wie die benötigten Attribute ausgelesen werden. Sie werden der Methode startelement als Parameter atts in Form eines Objekts übergeben, das Schlüssel-Wert-Paare speichert. Die Werte können immer mit getvalue(string, String) anhand des Schlüssels abgefragt werden. Übergabeparameter sind dabei der Namespace und der Schlüssel. Für die Erzeugung einer PointOfInterest-Instanz werden Name, geographische Breite und geographische Länge benötigt. Weil es nicht möglich ist, dem Namefinder einen maximalen Suchradius zu übergeben, muss vor dem Einfügen des Ortes in die Liste der Ergebnisse zuerst noch dessen Entfernung vom angegeben Suchort ermittelt werden. Nur wenn sie gering genug ist, kann er wirklich aufgenommen werden. Ein weiteres Problem ist, dass gleichnamige Knoten ineinander verschachtelt sein können. Entsprechend muss eine Unterscheidung zwischen äußeren und inneren Knoten erfolgen. Diese wird einfach dadurch geregelt, dass eine Variable bei jedem öffnenden named-tag erhöht und beim Schließen wieder erniedrigt wird. Ist sie beim Ausgangswert angelangt, ist der Knoten von Belang für die Suche. In der Android-API ist mit android.location.geocoder ebenfalls eine eingebaute Klasse für Geocoding vorhanden. Auch für sie liegt eine Implementierung der GeoCodingComponent vor. Die Klasse AndroidGeoCoding kann dabei auf das Einlesen externer Datenquellen verzichten, da der Geocoder bereits Objekte zurückliefert. Weil die zum Geocoder gehörende Suchmethode synchron arbeitet, tut es auch die wrappende Klasse AndroidGeoCoding.

78 72 KAPITEL 6. UMSETZUNG Der Suchbereich für eine Adresse wird bei der Android-eigenen Klasse nicht durch einen Punkt und einen Abstand angegeben, sondern durch eine Angabe von vier Gradzahlen, die eine westliche, östliche, nördliche und südliche Grenze beschreiben. Entsprechend muss zuerst eine Umrechnung der übergebenen Werte in diese Bounding Box erfolgen. Der Abstand in Grad nach Norden und Süden ergibt sich aus der Formel d π R, wobei d der Suchradius in Metern und R der Erdradius in Metern ist. In Richtung Westen und Osten muss beachtet werden, dass der Abstand zwischen zwei Längengraden ebenfalls von der geographischen Breite abhängt. Die Formel ist d 360 daher etwas komplizierter: 2 π R cos(b), wobei B die geographische Breite des Suchmittelpunktes beschreibt. Mit diesen Angaben lässt sich die Methode getfromlocationname(...) aufrufen. Sie erwartet zunächst den Suchbegriff, der die Adresse beschreibt, dann eine maximale Anzahl von Ergebnissen. Anschließend werden die Gradzahlen der Süd-, West-, Nord- und Ostbegrenzung der Suchfläche angegeben. Die Methode liefert dann eine typsichere Liste mit Address-Objekten zurück. Address-Objekte speichern ihre Daten im von OASIS definierten xal-standard [OAS07]. Sie bieten eine ausführliche Beschreibung einer Adresse an, die aus diversen Teilen besteht. Die Applikation fragt von der Vielzahl von Daten den beschreibenden Namen, Ort, die Straße und Postleitzahl ab und bringt diese in einem String unter, der dann als Beschreibung für den zu erzeugenden PointOfView dient. Problematisch ist hierbei, dass die einzelnen Attribute einer Adresse nie mit Sicherheit gesetzt sind, so dass hier immer zusätzlich eine Inhaltsüberprüfung stattfinden muss. Die geographische Länge und Breite werden allerdings immer mitgeliefert, wodurch es kein Problem ist, die Daten für die GeoCoordinate zu erhalten. Auf diese Weise werden alle erhaltenen Address-Objekte in PointOfInterest-Instanzen umgewandelt und dann im Array zurückgegeben. Es bleibt noch anzumerken, dass die Dokumentation der Geocoder-Klasse ausdrücklich darauf hinweist, dass sie keinen eingebauten Datenservice beinhaltet, sondern auf eine zusätzliche, externe Quelle angewiesen ist, die nicht zu den Android-Standardklassen gehört. Welche Quelle das sein kann und wie der zugehörige Service aussieht, wird allerdings nicht näher definiert, daher bleibt der Ursprung der ermittelten Daten dem Programmierer verborgen. Als Beispiel für eine asynchrone Implementierung der Geocoding-Schnittstelle dient die Nutiteq- API. Sie stellt ebenfalls eine eigene Klasse zur Verfügung, mit der Adressabfragen möglich sind. GeocodingService verwendet dabei einen von Nutiteq gestellten Server, der wiederum auf Daten von Cloudmade zugreifen soll. Bei der Erzeugung einer Instanz der Klasse werden eine Reihe von Parametern benötigt. Zum einen muss ein Callback angegeben werden, das vom Typ GeocodingResultWaiter ist, dann muss die URL des zu benutzenden Service übergeben werden. Die Klasse stellt hierfür eine Konstante zur Verfügung. Es muss ein String angegeben werden, der die Sprache der Suchanfrage angibt, außerdem ein WgsPoint, der die Position beschreibt, in deren Umgebung gesucht werden soll. Es folgt der Suchausdruck, anschließend eine Angabe der Suchart, welche nach Geocoding und der Suche nach Sehenswürdigkeiten unterscheidet. Es kann dann noch ein Array mit beschreibenden Kategorien der Ergebnisse übergeben werden, außerdem eine maximale Trefferzahl und ob das Ergebnis komprimiert übertragen werden soll.

79 6.4. ROUTENBESTIMMUNG 73 Um die eigentliche Suche zu starten, wird die Methode execute() am erzeugten Objekt aufgerufen. Sobald ein Ergebnis vorliegt, wird das Callback benachrichtigt und diesem entweder eine Reihe von Orten oder ein Fehler mitgeteilt. Da das Ergebnisarray wiederum API-interne Objekte beinhaltet, werden diese in passende Instanzen von PointOfInterest umgewandelt. Hierfür werden, wie üblich, geographische Länge und Breite ausgelesen, außerdem besitzen die Objekte einen Namen und eine Beschreibung. Beides zusammen bildet die Beschreibung der Adresse, die schließlich an den wartenden GeoCodingListener übergeben wird. Die Dokumentation der Klasse GeocodingService ist leider sehr unbefriedigend, was eine vernünftige Nutzung der Klasse nahezu unmöglich macht. So werden zum Beispiel die Suchkategorien, die man als int-array übergibt, an keiner Stelle auch nur ansatzweise erklärt. Ebenso fehlt eine Erklärung, wie ein entsprechendes Backend zur Datenübermittlung aussehen müsste und welche Formate die Klasse beim Einlesen unterstützt. Selbst ein Hinweis, ob die Suchanfrage speziell formatiert sein muss, ist nicht vorhanden. All das macht es in der Praxis leider unmöglich, diese Komponente sinnvoll einzusetzen, weshalb sie hier zwar implementiert wurde, im Programm aber keine Verwendung findet Routenberechnung Nachdem der Nutzer nun eine Reihe von Möglichkeiten hat, den Zielort seines Weges zu bestimmen, muss der Weg selbst noch berechnet werden. Auch bei diesem Teil der Anwendung ist das Ziel, eine Schnittstelle zu schaffen, die es ermöglicht, zu einem späteren Zeitpunkt leicht auf eine andere Quelle umzusteigen. Wie bei der Adressauflösung soll deshalb wiederum zuerst ein einfach gehaltenes Interface beschrieben werden, das als Grundlage für mögliche Umsetzungen dient. Wie in Abbildung 6.5 zu sehen ist, folgt das Interface dem schon vom Geocoding bekannten Schema. Grundsätzlich bietet es zwei verschiedene Arten der Routenberechnung an, einmal zwischen einem Start- und einem Zielpunkt und einmal zwischen zwei Punkten mit beliebig vielen Zwischenpunkten. Beide Arten der Berechnung können, je nach verwendeter Implementierung, synchron oder asynchron umgesetzt sein. Für die asynchronen Aufrufe existiert wieder ein passendes Callback-Interface, das auf eine Antwort des Berechners wartet. Auch in diesem Fall sind die Methoden optional und können mit einer entsprechenden UnsupportedOperationException reagieren, falls sie nicht umgesetzt wurden. Jede berechnete Route wird beschrieben in einem Array von einzelnen Routenpunkten, die durch Objekte der Klasse RoutePoint gegeben sind. Wie schon in Abbildung 6.1 zu sehen war, ist sie eine indirekte Unterklasse von GeoCoordinate. Sie wurde um eine Beschreibung und einen Abstand ergänzt. Dieser Abstand soll dabei immer die Distanz zum nächstfolgenden Wegpunkt angeben. Der Ablauf, der zu einer Beauftragung einer Routenberechnung notwendig ist, wurde bereits beschrieben. Nach einigen Methodenaufrufen ist es dann letztlich die PersonalLocation, in der eine Implementierung der RoutingComponent vorgehalten wird. An dieser wird dann die entsprechende Methode zur Berechnung aufgerufen und das Ergebnis dann an die wartenden Routing-Handler

80 74 KAPITEL 6. UMSETZUNG Abbildung 6.5: Routing-Schnittstelle mit Callback und Routenpunkten zurück gereicht. Sollte bei der Berechnung der Route ein Fehler auftreten, verfügt RoutingListener über eine Methode onerror(int), die einen vorgefertigten Statuscode übergibt. Entsprechende konstante Werte sind in RoutingComponent vorhanden, sie beschreiben verschiedene Fehlerzustände. Da die synchrone Berechnung das Übergeben solcher Fehlerwerte nicht unterstützt, muss immer darauf geachtet werden, dass im Fehlerfall ein null-wert zurückgegeben werden kann Realisierung der Wegfindung Die eigentliche Umsetzung der Routenberechnung erfolgt über die Nutiteq-API. Die Klasse NutiteqRouting implementiert das RoutingComponent-Interface und stellt eine asynchrone Methode zur Berechnung des Weges zwischen genau zwei Punkten zur Verfügung. Durch die Verwendung der externen Klassenbibliothek ist der Aufruf zur Berechnung sehr kurz, wie Listing 6.10 zeigt. Benötigt wird im Wesentlichen die Klasse CloudMadeDirections, über die auf die gewünschte Funktionalität zugegriffen werden kann. Sie implementiert das Interface DirectionsService. Dem Konstruktor muss ein geeignetes Callback übergeben werden, außerdem Start- und Zielort

81 6.4. ROUTENBESTIMMUNG 75 der Route, die Art der Fortbewegung und ein API-Schlüssel. Als Koordinaten erwartet Cloud- MadeDirections Objekte vom Typ WgsPoint, die aus den gegebenen GeoCoordinate-Instanzen erzeugt werden. Als Standardnutzer für die Anwendung wird von Fußgängern ausgegangen, der Service würde aber ebenso Strecken für Radfahrer und Autos unterstützen. Der API-Key wird benötigt, um auf Cloudmade-Dienste zugreifen zu können, wie bei der Vorstellung des Projekts zuvor schon beschrieben wurde. 1 public void calculateroute ( RoutingListener l, GeoCoordinate from, 2 GeoCoordinate to) throws UnsupportedOperationException { 3 WgsPoint start = new WgsPoint ( from. getlongitude (), from. getlatitude ()); 4 WgsPoint dest = new WgsPoint ( to. getlongitude (), to. getlatitude ()); 5 CloudmadeWaiter waiter = new CloudmadeWaiter ( l); 6 new CloudMadeDirections ( waiter, start, dest, 7 CloudMadeDirections. ROUTE_TYPE_FOOT, apikey ). execute (); 8 } Listing 6.10: Nutzung der Nutiteq-API zur Wegberechnung Als Callback-Objekt dient eine Instanz vom Typ DirectionsWaiter, ebenfalls ein Interface der Nutiteq-API. Es stellt einige Methoden zur Verfügung, für auftretende Fehler sind dies networkerror() bei Verbindungsfehlern, routingerrors(int) für unbekannte Start- oder Zielpunkte und routingparsingerror(string), falls keine Route erzeugt werden konnte. Wurde eine Route gefunden, ruft der Service beim Callback routefound(route) auf. Der DirectionsWaiter ist als innere Klasse in NutiteqRouting umgesetzt. CloudmadeWaiter benötigt seinerseits wieder ein Callback, das über das Ergebnis der Routenberechnung informiert werden soll, um den asynchronen Aufruf abzuschließen. Bei einem Fehler wandelt er den von CloudMadeDirections gegebenen Fehlercode in einen entsprechenden aus RoutingComponent um und reicht ihn an die onerror-methode des Callbacks weiter. Abbildung 6.6: Sequenzdiagramm der Routenberechnung

82 76 KAPITEL 6. UMSETZUNG Der erfolgreiche Abschluss einer Routenberechnung erfordert den Aufwand, die Nutiteq-interne Routendarstellung in die Darstellung der Applikation umzuwandeln. Hierfür werden zuerst die einzelnen Punkte aus der Route ausgelesen, an denen eine Richtungsänderung vorgenommen werden muss. Diese Punkte liegen als Array aus geographischen Koordinaten in Form von WgsPoint- Objekten vor. Eine Umwandlung in Objekte vom Typ GeoCoordinate ist einfach möglich durch Auslesen der Längen- und Breiteninformation. Weil an jedem RoutePoint auch vermerkt werden soll, wie groß die Distanz zum nächsten Punkt ist, wird dieser ebenfalls schon vorberechnet. Außerdem versucht der CloudmadeWaiter, die Anzahl der Routenpunkte zu reduzieren, um eine höhere Performanz zu erreichen. Er überprüft dafür, wie groß die Richtungsänderung am nächsten Wegpunkt ist. Beträgt diese weniger als fünf Grad, wird der Punkt ignoriert und die Berechnung mit dem darauffolgenden Punkt fortgesetzt. In Abbildung 6.6 ist der Ablauf der Routenberechnung noch einmal dargestellt. Wie zu erkennen ist, reicht der CloudmadeWaiter die von ihm erstellte Route im korrekten Format an PersonalLocation weiter. Weil die Klasse für das Speichern der Route und das Informieren aller Routing-Handler zuständig ist, implementiert sie auch das Interface RoutingListener. 6.5 Benutzeroberfläche Wichtig für die Applikation ist es, dass sie über eine intuitive, leicht zu bedienende grafische Oberfläche verfügt. Im Wesentlichen werden zwei Bestandteile im Fokus der Betrachtung stehen, erstens die Straßenkarte in ihren verschiedenen Verwendungen, und zweitens die Realitätssicht mit Kamera und zusätzlich eingebetteten Funktionen. Beide Teile sind modular gehalten, so dass sie unabhängig voneinander existieren können. Besonders die Ansicht der erweiterten Realität zerfällt noch einmal in mehrere Unterbestandteile, die jeweils wieder voneinander getrennt sind. Für die Anzeige liegen außerdem zwei elementare Anwendungsfälle vor. Der eine umfasst die Darstellung der Navigationsfunktion, mit deren Hilfe der Anwender zu seinem Ziel geleitet werden soll, sowohl durch Karte als auch durch Augmented Reality. Als anderer Anwendungsfall existiert die Ansicht zur Zielauswahl, die der eigentlichen Navigation vorangeht. Für beide Komponenten existiert jeweils eine eigene Activity, die das Anzeigen der grafischen Oberfläche und die Interaktion mit dem Nutzer steuert Kartenanzeige Bestandteil beider Activites ist die Anzeige der Straßenkarte. Sie wird daher als erste aller Views genauer betrachtet, zumal sie auch eine komplexe Struktur vorweist. Wie schon bei der Routenberechnung und der Ermittlung von Sehenswürdigkeiten ist eine Anforderung an das Design, dass der verwendete Kartenanbieter in Zukunft ohne größere Probleme gewechselt werden kann. Entsprechend wird zunächst eine Wrapperklasse definiert, die von der eigentlichen Umsetzung abstrahieren soll. Bei dieser Klasse handelt es sich um MapWrapper, eine abstrakte Oberklasse für die Kartenverwaltung.

83 6.5. BENUTZEROBERFLÄCHE 77 MapWrapper ist nicht als View-Komponente vorgesehen, sondern soll als Controller für die Kartendarstellung fungieren. Es sind daher sowohl Methoden vorhanden, die genutzt werden, um Eigenschaften der Anzeige an die eigentliche View weiterzuleiten, als auch Methoden, die zur Informationsübertragung dienen. Speziell die Eigenschaften, über die eine Kartenanzeige verfügen soll, und ihre get- und set-methoden sind bereits in der abstrakten Klasse definiert. Es soll möglich sein, über den MapWrapper zu steuern, ob eine View gerade angezeigt wird, ob sie bei der Anzeige den gesamten Bildschirm ausfüllt oder nur einen Teil bedeckt, ob sie beweglich sein soll oder immer den gleichen Ausschnitt anzeigt, und ob sie durch eine visuelle Hilfe darstellen soll, in welche Richtung der Benutzer gerade schaut beziehungsweise wie das Gerät ausgerichtet ist. Entsprechende Setter ermöglichen, dass verschiedene Komponenten, welche die Karte verwenden möchten, sie entsprechend ihrer Bedürfnisse einstellen können. Möchte eine Komponente auf Eingaben der vom Wrapper verwalteten View reagieren, besteht die Möglichkeit, ihm ein TouchDelegate zu übergeben. Das Interface beinhaltet die Methode ontouch. Die Subklasse des Wrappers muss in ihrer Implementierung beachten, das Delegate bei Events auf der Kartenview zu benachrichtigen, soweit eines vorhanden ist. Neben diesen konkreten Methoden verfügt der MapWrapper auch über eine Reihe von abstrakten Methoden, die erst im Zusammenhang mit der eigentlichen Karte umgesetzt werden können. Dazu gehört die Fähigkeit, die Kartenansicht auf eine bestimmte geographische Koordinate zu zentrieren mit Hilfe von centerat(geocoordinate) und den Mittelpunkt der View als GeoCoordinate mit getcenter() abzufragen. Wichtig ist selbstverständlich auch, überhaupt an eine View-Instanz der Karte zu gelangen, was über den gleichnamigen Getter geschieht. Weil die wenigsten Kartenansichten in einem festen Maßstab verbleiben können, gibt es außerdem Zoom-Buttons in der View. Deren Position soll mit Hilfe von positionzoomcontrols(int, int) ausgerichtet werden können. Die übergebenen int-werte beschreiben dabei Konstanten, wie sie im Android-eigenen RelativeLayout zur Positionierung eingesetzt werden. Ebenso abstrakt sind auch die Methoden, mit denen dem MapWrapper eine neue Position oder Gerätelage mitgeteilt wird. In onpositionchanged(geocoordinate, POI[]) soll die implementierende Klasse die Karte neu ausrichten und ihre Anzeige der aktuellen Position und der bekannten Sehenswürdigkeiten aktualisieren. onorientationchanged(float[]) ist gedacht, damit die zuvor schon genannte Ausrichtung der Anzeige in Blickrichtung erfolgen kann. Das übergebene float-array hat die weiter vorne schon näher erklärten Werte des Lagesensors gespeichert. Als letzte zu nennende Operationen bleiben noch setroute(routepoint[]) und removeroute(). Auf diese Weise wird die Karte über den Weg informiert, den der Nutzer zurücklegen möchte. So kann diese dann auf der Kartenview angezeigt werden. Die Applikation verwendet für die Kartendarstellung die Nutiteq-API. Die elementaren Klassen sind MapView und BasicMapComponent. Außerdem wird ein Objekt benötigt, das den verwendeten Web-Dienst kapselt, in diesem Fall ist es CloudMade, das wiederum auf die Inhalte des Cloudmade-Projekts zugreift. Der Entwickler muss sich beim CloudMade-Objekt nur um die Instanziierung kümmern. Die

84 78 KAPITEL 6. UMSETZUNG Verwendung erfolgt dann ausschließlich über die BasicMapComponent. Als Konstruktor stellt die Klasse CloudMade(String, int, int) zur Verfügung. Die drei Parameter sind der API- Schlüssel von Cloudmade, eine Größenangabe in Pixeln für die Kartenkacheln und das gewünschte Layout der Karte. Als mögliche Kantenlänge der Kacheln kommen durch Vorgaben des Dienstes nur 64 und 256 Pixel in Frage, andere Werte haben zur Folge, dass keine Grafiken geladen werden. Ähnliches gilt für das Layout. Hier wird nur der Wert 1 unterstützt, bei einem anderen aktuellen Parameter erfolgt ebenfalls keine Kartendarstellung mehr. Die BasicMapComponent erwartet in ihrem Konstruktor eine Reihe von Parametern zur Beschreibung. Zunächst sind dies drei Strings, die den Nutzer der API identfizieren sollen, was über einen eigenen API-Schlüssel erfolgt, außerdem über einen Anbieternamen, der ebenfalls bei der Registrierung angegeben werden muss, und über den Namen des Programms, an das der API-Key gebunden ist. Die weiteren Angaben beschreiben dann schon die Karte. Zu ihnen gehören, der Reihe nach, die Breite und Höhe der Kartenanzeige in Pixeln, der geographische Punkt, auf dem die Anzeige zu Beginn zentriert sein soll, und der anfängliche Zoom-Faktor der Karte. Die genutzten Standardwerte sind die Breite und die Höhe des Displays, außerdem der letzte bekannte Ort und ein Zoom-Faktor von 14. Abbildung 6.7: Bild der Karte Um die letzte bekannte Position zu ermitteln, wird der LocationManager befragt. Ist das GPS- Modul aktiv, wird dessen letzte Positon über getlastknownlocation() abgefragt. Ist das nicht der Fall, durchläuft die Applikation alle bekannten Provider und wählt die Position aus, die zeitlich am wenigsten weit zurückliegt. Der Zoom-Faktor beschreibt die Größe des darstellbaren Kartenausschnitts. Ihm liegt eine Kachelgröße von 256 Quadratpixeln zugrunde. Im Zoomfaktor 0 wird die gesamte Erdoberfläche in einer Kachel dargestellt. Jeder Zoomschritt halbiert die Kantenlängen, so dass sich die Kachelzahl vervierfacht. Bei der Darstellung der Kacheln wird die Mercator-Projektion angewendet, was die Karte in Richtung der Pole verzerrt. Wenn die BasicMapComponent erzeugt wurde, kann ihr mit setmap(geomap) der Datendienst

85 6.5. BENUTZEROBERFLÄCHE 79 mitgeteilt werden. Anschließend werden noch diverse weitere Voreinstellungen vorgenommen. Zunächst wird ein MapListener eingehängt, der dafür sorgt, dass Interaktionen mit der View an das TouchDelegate des MapWrappers geleitet werden. Außerdem wird die aktuelle Position des Benutzers durch ein Icon angezeigt, das in der entsprechenden Liste der BasicMapComponent per addplace(place) gespeichert wird. Ein Place ist dabei ein Objekt, das sehr ähnlich zu den POI-Instanzen ist. Es verfügt über einen Namen, ein Symbol und eine geographische Position. Da die interne Verarbeitung von Daten in BasicMapComponent auf Threads basiert, muss man diese durch den Aufruf von startmapping() noch in Gang setzen. Die eigentliche Darstellung der Karte geschieht über die Klasse MapView der Nutiteq-API. Ihr werden bei Erzeugung der Applikations-Kontext und die BasicMapComponent, die sie mit Daten beliefert, übergeben. Sie stellt ebenfalls einen MapListener dar. Bei der MapView handelt es sich um eine Unterklasse der Android-View. Da die View der Karte zusätzlich Knöpfe zur Vergrößerung und Verkleinerung des Bildausschnitts beinhaltet, die über Konstanten des RelativeLayouts positioniert werden, wird sie in eben diesem Layout gekapselt und die Zoom-Felder darin ergänzt und mit entsprechenden Listenern versehen. Das fertige Layout kann dann über getview abgefragt werden. Ihre Darstellung wird in Abbildung 6.7 gezeigt. Um zusätzliche Daten auf ihr anzeigen zu können, wird sie von der Applikation noch ein weiteres mal abgeleitet zur Klasse ExtendedMapView. Diese überschreibt das für die Darstellung zuständige Callback ondraw(canvas), das Android beim Zeichnen einer View aufruft. 1 protected void ondraw ( Canvas c) { 2 try { 3 // draw map 4 super. ondraw (c); 5 } catch ( Exception e) { } 6 if ( showviewdirection ) { 7 // height 8 Rect r = c. getclipbounds (); 9 int h = r. bottom - r. top ; 10 // width 11 int w = r. right - r. left ; 12 // overlay with arc pointing into viewing direction 13 RectF rf = new RectF (( w - h) / 2, 0, w - ( w - h) / 2, h); 14 c. drawarc (rf, angle - 20, 40, true, paint ); 15 } 16 } Listing 6.11: Anzeige der Blickrichtung in der MapView Die zusätzlich einzubettende Information ist die Blickrichtung des Anwenders. Sie soll es erleichtern, Realität und Karte in Beziehung zu setzen. Hierbei war die ursprüngliche Idee, die Karte passend zu drehen, so dass die Himmelsrichtung, in welche die Kamera des Gerätes zeigt, auf dem Display immer oben ist. Durch die vorliegende Implementation der Nutiteq-API war eine solche Umsetzung allerdings nicht möglich. Das der ondraw-methode übergebene Objekt vom Typ Canvas beschreibt den Ausschnitt des Bildschirms, auf dem die View dargestellt wird. Hierfür liegen Clipping-Grenzen vor, die ein lokales Koordinatensystem beschreiben. Zeichnet man im Punkt (0, 0) auf dem Canvas, so entspricht dieser Punkt der oberen, linken Ecke des

86 80 KAPITEL 6. UMSETZUNG Clipping-Rechtecks. So muss der Programmierer sich nicht eigenhändig darum kümmern, die richtige Position auf dem Bildschirm für seine Objekte zu errechnen. Der Nachteil beim Canvas liegt darin, dass der Clipping-Bereich angepasst werden kann und sich eine View so mehr Platz in ihrem Elternelement verschaffen kann. Genau das macht die MapView. Wird die Zeichenfläche durch eine Rotationsmatrix gedreht, ragen die Ecken der Karte über das Clipping-Rechteck hinaus. Anstatt nun aber dort abgeschnitten zu werden, vergrößert die MapView die Fläche so, dass ihr Inhalt weiterhin komplett sichtbar ist. In der Konsequenz bedeutet das, dass die Karte im späteren Programm bei Drehung immer weit in die Kamerasicht hineinragt und diese im schlimmsten Fall sogar verdeckt. Als alternative Umsetzung wurde daher ein transparenter Kreisbogen gewählt, der das Sichtfeld der Kamera widerspiegeln soll. Listing 6.11 stellt dar, wie dieser in die View eingebettet wird. Es werden zunächst die Höhe und Breite des Clipping-Rechtecks bestimmt und dann daraus ein Quadrat berechnet, das als Kantenlänge die Höhe des Clipping-Rechtecks verwendet und horizontal zentriert wird. In diesem Quadrat wird ein Kreisbogen über 40 Grad geschlagen, zentriert um den aktuellen Blickwinkel. Nachrichten über die Änderung des Blickwinkels erhält der NutiteqWrapper über die schon in der abstrakten Oberklasse angekündigten Methode onorientationchanged(float[]). Hier wird zum einen die Rotation um die z-achse, also der Kompass, abgefragt und an die ExtendedMap- View für die Anzeige weitergereicht. Außerdem wird der Kippwinkel des Geräts benötigt. Da die Anwendung immer im Querformat angezeigt wird, ist hier die Rotation um die y-achse entscheidend. Über sie kann bestimmt werden, wie viel von der Karte zu sehen ist, wennn sie sich nicht im Vollbildmodus befindet. Die Berechnung ist identisch zu der, die später in der erweiterten Realität angewandt und dort auch vorgestellt wird. Erhält der NutiteqWrapper über onpositionchanged(geocoordinate, POI[]) eine Nachricht, dass sich die Position des Geräts geändert hat, muss die Karte entsprechend aktualisiert werden. Das die Position anzeigende Icon muss verschoben und die Karte neu zentriert werden. Außerdem werden alle zusätzlichen Sehenswürdigkeiten in der Karte verzeichnet. Ein großer Nachteil ist, dass die Datendienste in der BasicMapComponent als Threads umgesetzt sind. Jeder dieser Threads kann in einer Applikation nur einmal zur Zeit gestartet werden. Erzeugen nun zwei Activities einen entsprechenden Controller und aktivieren ihn über startmapping(), erzeugt dieses Vorgehen eine Exception. Aus diesem Grund ist der Nutiteq- Wrapper als Singleton ausgelegt. So existiert von der BasicMapComponent nur eine einzige Instanz, die an mehreren Stellen verwendet werden kann. Zugriff erfolgt über die statische Methode getinstance(context), wobei für den Fall einer Initialisierung der Applikations-Kontext übergeben werden muss. Bei späteren Aufrufen wird dieser ignoriert RoutingActivity Die erste Anwendung findet die Karte bei der Bestimmung eines Zielpunktes. Hierfür kann der Anwender über einen Menüeintrag die RoutingActivity starten, die ihm sowohl die Karte als auch ein Textfeld zur Suche zur Verfügung stellt. Sie ist darauf ausgelegt, von einer anderen Kom-

87 6.5. BENUTZEROBERFLÄCHE 81 ponente über startactivityforresult(intent, int) aufgerufen zu werden. Den ermittelten Zielort gibt sie dann in einem Intent zurück. Auf der Oberfläche der RoutingActivity finden sich drei Views. Einen Großteil des Displays nimmt die Karte ein. Darüber befinden sich außerdem noch ein Textfeld und ein Suchbutton, über die eine Adresssuche möglich ist. Die beiden Bestandteile werden in einem LinearLayout angeordnet. Abbildung 6.8: Oberfläche der RoutingActivity Beim Suchfeld wurde ein statisches Layout umgesetzt. Der Inhalt der zugehörigen XML-Datei wurde bereits in Listing 2.4 gezeigt. Zu sehen ist dort ein relatives Layout, das Suchfeld und Button nebeneinander anordnet. Mit Hilfe von View.inflate(Context, int, ViewGroup) kann für einen gegebenen Kontext die entsprechende, durch ihre ID angegebene, XML-Datei in den angegebenen Container eingefügt werden. 1 ImageButton im = ( ImageButton ) findviewbyid ( R. id. search_ button ); 2 im. setonclicklistener ( new View. OnClickListener () { 3 public void onclick ( View v) { 4 EditText e = ( EditText ) RoutingActivity. this 5. findviewbyid (R.id. search_text ); 6 String search = e. gettext (). tostring (); 7 PointOfInterest [] pois = new AndroidGeoCoding ( RoutingActivity. this ) 8. findnear ( search, map. getcenter (), MAX_DIST ); 9 PointOfInterest [] pois2 = new OSMGeoCoding (). findnear ( search, map 10. getcenter (), MAX_DIST ); 11 onplacesfound ( search, concat ( pois, pois2 )); 12 } 13 }); Listing 6.12: Bestimmung möglicher Zielorte

88 82 KAPITEL 6. UMSETZUNG Der Button wird später in der Anwendung mit einem Listener versehen, der die Geocoding- Komponenten aufruft. Listing 6.12 zeigt hierbei das Vorgehen. Nach Ermittlung des Buttons über seine ID wird der OnClickListener eingehängt. Er liest bei Aufruf den Text im Suchfeld aus und startet dann die Suche zuerst beim Android-Geocoding-Dienst, anschließend auch noch beim Openstreetmap-Namefinder. Diese Ergebnisse werden zu einem einzelnen Array zusammengefasst und dann als modaler Dialog, wie in Abbildung 6.9 zu sehen, angezeigt. Dieses Vorgehen illustriert Listing Der Übersichtlichkeit halber wurde der Fall, dass keine passenden Orte gefunden werden konnten, ausgelassen. Hier wird ebenfalls ein Dialog mit einer kurzen Mitteilung angezeigt, die der Benutzer zur Kenntnis nehmen muss. Abbildung 6.9: Auswahldialog für Routenziele Um die einzelnen Suchtreffer anzeigen zu können, wird zunächst eine textuelle Beschreibung erzeugt. Die Erzeugung des eigentlichen Dialogs ist ein unübersichtlicher Aufruf. Es muss ein AlertDialog.Builder erzeugt werden, der den eigentlichen Dialog vom Typ AlertDialog instanziiert. Dem Builder muss der Applikations-Kontext mitgegeben werden. Danach wird über setsinglechoiceitems(string[], int, OnClickListener) der Inhalt des künftigen Dialogs gesetzt, wobei der angewählte Eintrag und ein Listener, der bei Klick auf einen der Einträge aufgerufen wird, angegeben werden. Durch die Wahl der Methode wird auch festgelegt, dass nur ein Element der zu zeigenden Liste ausgewählt werden kann. Durch den folgenden Aufruf von create() am Builder wird die Instanz schließlich erschaffen. Der Dialog verfügt grundsätzlich über drei Buttons zum Bestätigen, Ignorieren oder Abbrechen. Um sie anzuzeigen, muss die Methode setbutton(int, CharSequence, OnClickListener) aufgerufen werden, der übergeben wird, welcher Button angezeigt werden soll, welche Beschriftung er hat und welcher Listener auf Klicks reagieren soll. Drückt der Anwender den Bestätigen-Button, so wird der ausgewählte Ort an die aufrufende Activity übertragen und die RoutingActivity beendet. Hat er kein Item ausgewählt, wird die Activity beendet, ohne einen Zielort festzulegen. Der Druck auf den Abbrechen-Button hat zur Folge, dass der Dialog geschlossen wird und der

89 6.5. BENUTZEROBERFLÄCHE 83 Anwender einen anderen Zielort wählen kann. der Ignorieren-Button wird in dieser Funktion nicht genutzt. 1 public void onplacesfound ( String search, final PointOfInterest... places ) { 2 if ( places. length == 0) { 3 //... 4 } 5 String [] strings = new String [ places. length ]; 6 for ( int i = 0; i < places. length ; i ++) { 7 strings [ i] = places [ i]. getdescription (); 8 } 9 AlertDialog dialog = new AlertDialog. Builder ( this ). setsinglechoiceitems ( 10 strings, -1, new OnClickListener () { 11 public void onclick ( DialogInterface dialog, int which ) { 12 selecteditem = which ; 13 } 14 }). create (); 15 dialog. settitle ( getresources (). getstring ( R. string. geo_ choose_ title );); 16 String btext = getresources (). getstring ( R. string. geo_ choose_ positive ); 17 dialog. setbutton ( DialogInterface. BUTTON_POSITIVE, btext, 18 new OnClickListener () { 19 public void onclick ( DialogInterface dialog, int which ) { 20 Intent answer = new Intent (); 21 if ( selecteditem < 0 selecteditem >= places. length ) { 22 RoutingActivity. this. setresult ( Activity. RESULT_CANCELED, answer ); 23 } else { 24 GeoCoordinate arg0 = places [ selecteditem ]; 25 answer. putextra (" target ", arg0 ); 26 RoutingActivity. this. setresult ( Activity. RESULT_OK, answer ); 27 } 28 dialog. dismiss (); 29 finish (); 30 } 31 }); btext = getresources (). getstring ( R. string. geo_ choose_ negative ); 34 dialog. setbutton ( DialogInterface. BUTTON_NEGATIVE, btext, 35 new OnClickListener () { 36 public void onclick ( DialogInterface dialog, int which ) { 37 dialog. dismiss (); 38 } 39 }); dialog. show (); 42 } Listing 6.13: Erzeugung des Dialogs zur Zielwahl Die andere Möglichkeit, in der RoutingActivity einen Zielort zu bestimmen, ist der Klick auf die Karte. Hierfür werden am MapWrapper die Eigenschaften so gesetzt, dass die Karte beweglich und im Vollbildmodus ist. Außerdem meldet die RoutingActivity ein eigenes TouchDelegate an, die innere Klasse ClickDelegate. Listing 6.14 zeigt in der ontouch-methode noch einmal

90 84 KAPITEL 6. UMSETZUNG deutlich, wie die Übermittlung eines Ergebnisses beim Aufruf einer Activity über startactivity- ForResult funktioniert. Die Rückgabe der Ergebnisinhalte findet immer über einen Intent statt. Jede Activity besitzt die Methode setresult(int, Intent). Dieser kann ein Status in Form einer Zahl mitgegeben werden und eben ein Intent. In diesem werden die Daten im Extra-Bundle gespeichert, wie zuvor schon im Abschnitt über Intents erklärt. Im Falle der RoutingActivity handelt es sich dabei um ein Objekt vom Typ GeoCoordinate. Als identifizierender Schlüssel wird, wie in den Listings 6.13 und 6.14 zu sehen, target gesetzt. Damit der Aufrufer wieder die Kontrolle über das Programm übernimmt, wird die Activity über finish() beendet. 1 private class ClickDelegate implements TouchDelegate { 2 public void ontouch ( GeoCoordinate arg0 ) { 3 // the clicked position is where the user wants to go 4 Intent answer = new Intent (); 5 answer. putextra (" target ", arg0 ); 6 RoutingActivity. this. setresult ( Activity. RESULT_OK, answer ); 7 finish (); 8 } 9 } Listing 6.14: TouchDelegate, das einen auf der Karte gewählten Punkt übermittelt MainActivity Den Großteil der Zeit verbringt der Anwender in der MainActivity. Sie ist für die auf erweiterte Realität gestützte Navigationshilfe zuständig. In ihr werden Kamerasicht, Zusatzinformationen und Karte verbunden. Außerdem können hier über das Menü Einstellungen für die Anwendung vorgenommen werden. Desweiteren dient sie zur Initialisierung aller benötigten Dienste, da sie als Einsprungspunkt der Applikation dient. MainActivity bezieht alle Daten, die PersonalLocation bereitstellt. Sie ist dafür zuständig, dass die einzelnen Unterkomponenten wiederum mit den für sie relevanten Informationen beliefert werden. Entsprechend besitzt sie eine innere Klasse, InformationHandler, die genau dafür zuständig ist. Bei Aufruf der onresume()-methode wird PersonalLocation gestartet und der InformationHandler als Callback hinzugefügt. Intern verwaltet er wiederum Mengen von Listenern, die sich für Änderungen der Lage oder Position des Geräts oder neue Routendaten interessieren. Der Handler reicht diese Daten bei Erhalt zum einen weiter, zum anderen speichert er sie auch zwischen. Um auch die ausgehende Kommunikation zum Daten haltenden Objekt gekapselt über den Handler abwickeln zu können, verfügt er über die asynchrone Methode findroute(geocoordinate), mit der die Routenfindung in Auftrag gegeben werden kann. Innerhalb von onpause() in MainActivity wird die PersonalLocation deaktiviert, so dass sie zum Energiesparen keine weiteren Updates der Systemdienste bezieht und diese bei Bedarf abgeschaltet werden können.

91 6.5. BENUTZEROBERFLÄCHE 85 Das Optionsmenü der Activity enthält vier Einträge. Diese stellen Möglichkeiten bereit, in die RoutingActivity zu wechseln, eine Neuberechnung des aktuellen Weges durchführen zu lassen, die momentane Route zu löschen und die Programmeinstellungen aufzurufen, um sie zu ändern. Das zugehörige Menü wird statisch in den XML-Dateien vorgehalten und wurde in Listing 2.5 in Ausschnitten schon dargestellt. Welche Aktionen je nach gewählter Option ausgeführt werden, wird in onoptionsitemselected(menuitem) festgelegt. Abbildung 6.10: MainActivity mit Karte und Routenangaben Besondere Beachtung soll hier noch finden, wie auf das Ergebnis einer Activity, die über die Methode startactivityforresult aufgerufenen wurde, reagiert wird. Um bei der Rückkehr festzustellen, welche Activity aufgerufen wurde, wird der Methode ein int übergeben, der dann wieder erster Parameter in der Methode onactivityresult(int, int, Intent) wird. Listing 6.15 zeigt die Verarbeitung der Zielortbestimmung in MainActivity. Wurde ein Ziel festgelegt, wird die Koordinate aus dem mitgelieferten Intent ausgelesen und an den InformationHandler weitergereicht, um eine Route berechnen zu lassen. Andernfalls wird das Ergebnis verworfen. Die Activity bietet in den unteren Ecken Buttons zur Schnellwahl der Anzeige. Hier können unabhängig voneinander sowohl die Karte als auch die Kamera an- und abgeschaltet werden. So hat der Anwender die Möglichkeit, je nach Bedarf und Vorliebe, zum Beispiel nachts, die erweiterte Realität zu deaktivieren. Realität und Karte werden gebündelt in der CombinationView, einer zusammenfassenden View- Group. Sie meldet sich als Listener an den InformationHandler an und verteilt neue Daten an ihre untergeordneten Views. Diese sind gruppiert in erweiterte Realität und Karte. Wesentliche Funktionen sind neben der Datenverteilung das An- und Abschalten jener Oberflächenkomponenten. Außerdem verfügt sie über Callback-Methoden onstart() und onstop(), die bei Aktivierung und Pausieren der MainActivity aufgerufen werden. Hier werden die Grundeinstellungen der Karte jeweils neu festgelegt, da sie, wie schon beschrieben, ein Singleton ist, das sich die CombinationView mit der RoutingActivity teilen muss. Die Anzeige in der Navigationsumge-

92 86 KAPITEL 6. UMSETZUNG bung setzt voraus, dass die Karte nicht vom Benutzer bewegt werden kann und die Blickrichtung eingeblendet werden soll. 1 public void onactivityresult ( int requestcode, int resultcode, Intent data ) { 2 super. onactivityresult ( requestcode, resultcode, data ); 3 if ( requestcode == ROUTING_ REQUEST ) { 4 switch ( resultcode ) { 5 case Activity. RESULT_ OK : 6 Bundle content = data. getextras (); 7 GeoCoordinate target = ( GeoCoordinate ) content. get (" target "); 8 sc. findroute ( target ); 9 break ; 10 case Activity. RESULT_ CANCELED : 11 break ; 12 default : 13 Log.i(" GuideMe ", " ROUTING_REQUEST : illegal result "); 14 } 15 } 16 } Listing 6.15: Verarbeitung des Ergebnisses der RoutingActivity Die Anordnung der einzelnen Elemente innerhalb der CombinationView muss pixelgenau geschehen. Daher ist sie eine Unterklasse des AbsoluteLayout, das allerdings in der Android-API als deprecated geführt wird. Erstes Element ist die RealityView mit Kamerabild und Sehenswürdigkeiten, darüber wird die MapView gelegt. Oberste Schicht aller Views ist der FlatArrow, der die Richtungsanzeige bei der Navigation übernimmt. Die RealityView deckt, wenn angezeigt, immer den vollständigen Bildschirm ab, der FlatArrow liegt fest unterhalb der Bildschirmmitte. Lediglich die MapView ändert je nach Handylage ihre Position. Bei flachem Kippwinkel soll sie mehr Platz einnehmen als bei senkrechter Haltung des Geräts. Geregelt wird ihre Darstellungsfläche über ein AboluteLayout.LayoutParams-Objekt, das linke, rechte, obere und untere Grenze der View auf dem Bildschirm festlegt. Die obere Grenze der am unteren Bildschirmrand verankerten Kartenansicht wird in statechanged(float[]) neu berechnet, wenn sich der Kippwinkel geändert hat Anzeige des Kamerabildes Wesentlicher Bestandteil der Augmented Reality ist das Livebild der im Gerät verbauten Kamera. Ihre Daten müssen von der Hardware übermittelt und in einer geeigneten View angezeigt werden. Diese ist vom Typ SurfaceView. Umgesetzt wird die Darstellung in der Klasse CameraSurface. Um Daten an die Oberfläche der View zu übergeben, wird der zur SurfaceView gehörige Holder benötigt, eine Controller-Klasse. Ihm wird ein Callback hinzugefügt, das benachrichtigt wird, wenn eine Änderung an der Oberfläche aufgetreten ist. Über diese Callback-Methoden finden die wichtigsten Interaktionen zwischen View und Kamera statt. surfacecreated(surfaceholder) wird aufgerufen, sobald die Oberfläche der View erzeugt wird. Ab diesem Zeitpunkt ist es möglich, Daten auf der View darzustellen. Es wird versucht, Zugriff

93 6.5. BENUTZEROBERFLÄCHE 87 auf die Kamera zu erlangen, indem sie über Camera.open() aktiviert wird. Ihr wird dann per setpreviewdisplay(surfaceholder) mitgeteilt, wer die Darstellung ihrer Daten übernimmt. Wann immer sich an der Oberfläche der View etwas ändert, wird vom SurfaceHolder die Methode surfacechanged(surfaceholder, int, int, int) des Callbacks aufgerufen. Ein gesicherter Aufruf erfolgt nach der Kreierung der Oberfläche. Die letzten beiden Parameter sind die Breite und Höhe der Fläche, die in diesem Fall an die Kamera übergeben werden, damit diese weiß, wie groß ihre Vorschau sein wird. Mit startpreview() wird schließlich die Vorschau der Kamera aktiviert und auf der SurfaceView angezeigt. 1 public void surfacecreated ( SurfaceHolder holder ) { 2 try { 3 camera = Camera. open (); 4 camera. setpreviewdisplay ( holder ); 5 } catch ( Exception exception ) { 6 if ( camera!= null ) 7 camera. release (); 8 camera = null ; 9 } 10 } public void surfacedestroyed ( SurfaceHolder holder ) { 13 if ( camera!= null ) { 14 camera. stoppreview (); 15 camera. release (); 16 camera = null ; 17 } 18 } public void surfacechanged ( SurfaceHolder holder, int format, int w, int h) { 21 if ( camera!= null ) { 22 camera. stoppreview (); 23 Camera. Parameters parameters = camera. getparameters (); 24 parameters. setpreviewsize (w, h); 25 camera. setparameters ( parameters ); 26 camera. startpreview (); 27 } 28 } Listing 6.16: Verknüpfung der Kamera mit einer SurfaceView Wird die Oberfläche zerstört, weil die View nicht mehr angezeigt wird, ruft der Holder am Callback surfacedestroyed(surfaceholder) auf. In diesem Fall ist es sehr wichtig, die Kameraressourcen wieder freizugeben. Sobald eine Komponente über open() Zugriff auf die Kamera nimmt, ist es keiner weiteren Komponente mehr möglich. Wird die Ressource daher nicht mit release() freigegeben, bleibt die Kamera im schlimmsten Falle bis zu einem Neustart des Geräts blockiert. Aus diesem Grund werden, wie in Listing 6.16 zu sehen, auch im Fehlerfall sofort alle Ressourcen der Hardware wieder freigegeben. Weil die Daten der Kamera vom CameraSurface gebuffert werden, droht ein Speicherleck, wenn mehrmals eine Instanz der View erzeugt und die vorhergehende nicht zerstört wird. Im Ver-

94 88 KAPITEL 6. UMSETZUNG lauf der Programmentwicklung hat dieses Verhalten mehrfach zu Laufzeitfehlern geführt, die applikationsübergreifend bei allen Kameraanwendungen auftraten. Aus diesem Grund ist CameraSurface als Singleton implementiert worden, so dass die Navigationssoftware immer nur eine Instanz halten wird Einbettung von Sehenswürdigkeiten in das Kamerabild Als Bestandteil der Augmented Reality sollen die Sehenswürdigkeiten der Umgebung in das Kamerabild eingebettet werden. Wie die Darstellung am Ende aussieht, wird in Abbildung 6.12 gezeigt. Diese Aufgabe übernimmt die Klasse RealityView, die als ViewGroup mit einem AbsoluteLayout die exakte Positionierung der Objekte vornimmt. Da die Frage, welche Sehenswürdigkeiten im Bild zu sehen sein sollen, sowohl von der Position als auch von der Ausrichtung des Geräts abhängt, muss die RealityView auf alle Änderungen des Geräts reagieren. 1 <? xml version =" 1.0 " encoding ="utf -8"?> 2 <menu xmlns : android =" http :// schemas. android. com / apk / res / android "> 3 < item android : titlecondensed / poi_ context_ menu_ web " 4 android : title / poi_context_menu_web " 5 android item_web "> 6 </item > 7 <item android item_route " 8 android : titlecondensed / poi_ context_ menu_ route " 9 android : title / poi_context_menu_route "> 10 </item > 11 </menu > Listing 6.17: Kontextmenü von Sehenswürdigkeiten Sie verwaltet intern eine Map bekannter Sehenswürdigkeiten. In dieser werden diejenigen Points of Interest hinterlegt, die sich in einer Entfernung von maximal fünf Kilometern zum aktuellen Standort befinden. Bei jeder Positionsänderung wird in onpositionchanged(geocoordinate, POI[]) überprüft, ob sich der Abstand zu den vorliegenden Orten so weit vergrößert hat, dass diese aus der Map entfernt werden müssen, außerdem werden noch nicht vorhandene Sehenswürdigkeiten aus dem gegebenen Array in die Map übertragen, wenn sie nahe genug an der neuen Position sind. Als Wert wird in der Map zu jeder Sehenswürdigkeit gleich die sie repräsentierende View gespeichert, so dass ein schneller Zugriff darauf möglich ist. Jeder View wird bereits bei Ablage in der Map ein Listener angehängt, der bei Klick einen Toast auslöst. Bei Toasts handelt es sich um Textnachrichten, die kurzzeitig auf dem Bildschirm des Nutzers angezeigt werden und selbständig wieder ausblenden. Im Falle der Sehenswürdigkeiten zeigen sie dem Anwender den Namen des gewählten Elements. Desweiteren wird die View für die Anzeige von Kontextmenüs, deren Aussehen in Abbildung 6.11 zu sehen ist, bei der zuständigen Activity registriert. Dieses Kontextmenü ist statisch als XML-Datei definiert, wie Listing 6.17 zeigt. Es bietet die Möglichkeit, den ausgewählten Point of Interest als Zielpunkt für eine Route festzulegen und zusätzliche Informationen über das Internet als Webseite anzeigen zu lassen.

95 6.5. BENUTZEROBERFLÄCHE 89 Abbildung 6.11: Kontextmenü der Sehenswürdigkeiten Die Auswertung eines gewählten Eintrags im Kontextmenü übernimmt die übergeordnete Activity. Soll eine Route berechnet werden, ruft sie findroute des InformationHandlers auf. Soll eine Webseite angezeigt werden, so wird ein impliziter Intent eingesetzt, um eine geeignete Komponente zu finden, die dies übernehmen kann: Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url.toString())); Wichtig ist die Positionierung der POI-Views auf dem Bildschirm, damit diese mit der Position der Sehenswürdigkeiten übereinstimmen, die sie repräsentieren. Um den optimalen Punkt auf dem Display zu bestimmen, benötigt man die Koordinate der Sehenswürdigkeit, den aktuellen Ort des Geräts und seine Lage. Zunächst muss man die Richtung bestimmen, in der die Sehenswürdigkeit vom Betrachter aus liegt. Dann bestimmt man den Winkel α zwischen dieser Richtung und der Blickrichtung der Kamera. Hierbei wird der Winkel zwischen -180 und +180 Grad angegeben, wobei positive Werte Objekte rechts vom Betrachter beschreiben. Ist der Winkel im Betrag größer als 45 Grad, wird die View nicht angezeigt. Ansonsten gilt für den Mittelpunkt der View x = w d 2 + α w d 90 wv 2, wobei w d die Breite des Displays und w v die Breite der View ist. Die Größe w v wird maßgeblich durch die Entfernung zwischen Gerät und Sehenswürdigkeit beeinflusst. Es gilt w v = d Auf diese Weise werden nahe Objekte durch ein größeres Symbol als weiter entfernte dargestellt. w v wird ebenfalls für den Abstand der View vom oberen Rand des Displays verwendet Richtungsanzeige per Pfeil Das Hauptinstrument zur Navigation in der erweiterten Realität ist ein Pfeil, der die Richtung zum nächsten Wegpunkt anzeigt. Dieser ist zwar Teil der Augmented Reality, wird aber außerhalb der RealityView gehalten. Grund hierfür ist die Tatsache, dass der Pfeil nicht nur als Overlay

96 90 KAPITEL 6. UMSETZUNG des Kamerabildes vorliegen soll, sondern ebenfalls die Kartenansicht überlagern muss, damit er jederzeit zu sehen ist. Entsprechend verwaltet die CombinationView eine Instanz der Klasse FlatArrow eigenständig. 1 Paint fillpaint = new Paint (); 2 fillpaint. setantialias ( true ); 3 fillpaint. setargb (0 xc0, 0x40, 0xFF, 0); 4 fillpaint. setstyle ( Paint. Style. FILL ); 5 fillpaint. setpatheffect ( new CornerPathEffect (1)); Listing 6.18: Paint-Objekt zum Füllen des Richtungspfeils Der Pfeil selbst beinhaltet drei Informationen. Zum ersten zeigt er, wie bereits erwähnt, in Richtung des nächsten Wegpunktes einer Route. Desweiteren trägt er eine doppelte Farbcodierung. Die Füllfarbe des Pfeils spiegelt wider, wie weit der Anwender noch vom Zielpunkt seiner Route entfernt ist, der Rand des Pfeils gibt farblich die Entfernung zur nächsten Wegmarke an. Die Zeichnung des Pfeils erfolgt manuell auf einem Canvas. FlatArrow ist von View abgeleitet und besitzt daher eine Methode ondraw(canvas). Um auf einem Canvas zeichnen zu können, werden Paint-Objekte benötigt, welche Eigenschaften wie Farbe, Linienstärke oder Füllart beinhalten. Listing 6.18 zeigt die Erzeugung eines solchen Objekts. Hier wird ein leicht durchsichtiger Grünton verwendet, ein gezeichnetes Objekt wird Anti-Aliasing unterzogen und ausgefüllt. Außerdem werden Ecken des Objektes abgerundet gezeichnet. 1 double mindist = Double. POSITIVE_ INFINITY ; 2 int mindistat = 1; 3 GeoCoordinate intersect = null ; 4 GeoCoordinate bestintersect = null ; 5 for ( int i = 0; i < route. length - 1; i ++) { 6 intersect = getnearestintersectwithline ( route [ i], route [ i + 1], location ); 7 double dist = location. distanceto ( intersect ); 8 if ( dist < mindist ) { 9 bestintersect = intersect ; 10 mindist = dist ; 11 mindistat = i + 1; 12 } 13 } 14 next = mindistat ; 15 if ( location. distanceto ( route [ next ]) * EARTH_ RADIUS < MIN_ DIST 16 && next < route. length - 1) 17 next ++; 18 if ( mindist < MIN_ DIST ) 19 trackangle = ( float ) ( bestintersect. trackangle ( route [ next ]) * RAD_ TO_ DEG ); 20 else 21 trackangle = ( float ) ( location. trackangle ( route [ next ]) * RAD_ TO_ DEG ); Listing 6.19: Berechnung des nächsten Wegpunktes Die Farbe zur Angabe der Entfernung durchläuft linear die Farben von Rot über Gelb nach Grün zwischen einem maximalen und einem minimalen Abstand. Dieser maximale Abstand beträgt

97 6.5. BENUTZEROBERFLÄCHE 91 für die Entfernung zum Ziel etwa 800 Meter, zum nächsten Wegpunkt nur 100 Meter. In Abbildung 6.12 befindet sich der Anwender in der Nähe eines Wegpunktes, wie die grüne Färbung des Pfeilrandes zeigt. Die Entfernung zum Zielort ist hingegen noch relativ hoch, wie an der orangefarbenen Füllung zu erkennen ist. Als einziges Navigationsobjekt ist der Pfeil darauf angewiesen, über einen nächsten Punkt auf der Route zu verfügen. Daher wird ein Algorithmus benötigt, der diesen Punkt sicher bestimmen kann. Die Anwendung errechnet dafür den Abstand des Anwenders mit seinem Gerät zu jeder Teilstrecke der Route, die durch zwei Wegpunkte begrenzt ist. Der Endpunkt der Strecke, von welcher der Nutzer am wenigsten weit entfernt ist, stellt den nächsten Routenpunkt dar. Das Vorgehen ist in Listing 6.19 aufgeführt. Zuerst wird der Punkt auf der Strecke zwischen zwei Routenpunkten bestimmt, der nächstgelegen zur aktuellen Position ist. Ist dieser Abstand geringer als zum bisher nächstgelegenen Punkt, werden der neue Punkt, der Abstand und der Index des Endpunktes der Strecke im Routenarray gespeichert. Ist dann die Entfernung zum nächsten Routenpunkt geringer als ein Schwellwert, wird noch einen Punkt weitergegangen. Anschließend wird der Kurswinkel zwischen Position und Wegpunkt berechnet. Abbildung 6.12: Kameransicht mit Sehenswürdigkeiten und Richtungspfeil Der Schwellwert dient dazu, die Ungenauigkeiten von Kartenwerk und GPS-Ortung auszugleichen. So werden Punkte im Abstand von bis zu 20 Metern als gleich angesehen. Auch die letzten vier Zeilen in Listing 6.19 haben diesen Zweck. Bei geringen Entfernungen kann der Kurswinkel bereits dadurch beeinflusst werden, auf welcher Straßenseite sich der Benutzer befindet. Um dieses Problem zu beheben, wird die Position des Geräts bei der Kurswinkelberechnung exakt auf die Strecke zwischen den Wegpunkten verschoben, wenn der Abstand zur echten Position geringer als der Schwellwert ist. Eine so durchgeführte Wegpunktbestimmung erlaubt es dem Anwender, eventuell nicht vermerkte Abkürzungen zu verwenden oder Umwege zu gehen, ohne dass die Anwendung völlig

SEMINARVORTRAG ANDROID ENTWICKLUNG ETIENNE KÖRNER EMBEDDED SYSTEMS SS2013 - HSRM

SEMINARVORTRAG ANDROID ENTWICKLUNG ETIENNE KÖRNER EMBEDDED SYSTEMS SS2013 - HSRM SEMINARVORTRAG ANDROID ENTWICKLUNG ETIENNE KÖRNER EMBEDDED SYSTEMS SS2013 - HSRM ÜBERSICHT Android Android Dalvik Virtuelle Maschine Android und Desktop Applikationen Android Entwicklung Tools R Activity

Mehr

1. Software-Plattform Android Android. Was ist Android? Bibliotheken, Laufzeitumgebung, Application Framework

1. Software-Plattform Android Android. Was ist Android? Bibliotheken, Laufzeitumgebung, Application Framework 1. Software-Plattform Android Android Was ist Android? Plattform und Betriebssystem für mobile Geräte (Smartphones, Mobiltelefone, Netbooks), Open-Source Linux-Kernel 2.6 Managed Code, Angepasste Java

Mehr

4. Software-Komponenten in Android

4. Software-Komponenten in Android 4. Software-Kompponenten in Android Lernziele 4. Software-Komponenten in Android Themen/Lernziele Intents: Explizite und implizite Intents Intent-Filter Intent-Resolution Sub-Activities Broadcast Intents

Mehr

1. Software-Plattform Android Android. Was ist Android? Managed Code, Angepasste Java Virtual Machine

1. Software-Plattform Android Android. Was ist Android? Managed Code, Angepasste Java Virtual Machine 1. Software-Plattform Android Android Was ist Android? Plattform und Betriebssystem für mobile Geräte (Smartphones, Mobiltelefone, Netbooks), Open-Source Linux-Kernel ab 2.6, aktuell 3.8 Managed Code,

Mehr

Smartphone Entwicklung mit Android und Java

Smartphone Entwicklung mit Android und Java Smartphone Entwicklung mit Android und Java predic8 GmbH Moltkestr. 40 53173 Bonn Tel: (0228)5552576-0 www.predic8.de info@predic8.de Was ist Android Offene Plattform für mobile Geräte Software Kompletter

Mehr

Einführung in Android. 9. Dezember 2014

Einführung in Android. 9. Dezember 2014 Einführung in Android 9. Dezember 2014 Was ist Android? Software für mobile Geräte: Betriebssystem Middleware Kernanwendungen Android SDK: Tools und APIs zur Entwicklung von Anwendungen auf der Android-Plattform

Mehr

ANDROID. Analyse der Android Plattform. Andre Rein, Johannes Florian Tietje. 28. Oktober 2010. FH-Gieÿen-Friedberg Android Praktikum

ANDROID. Analyse der Android Plattform. Andre Rein, Johannes Florian Tietje. 28. Oktober 2010. FH-Gieÿen-Friedberg Android Praktikum Analyse der Android Plattform Andre Rein, Johannes Florian Tietje FH-Gieÿen-Friedberg Android Praktikum 28. Oktober 2010 Topics 1 Übersicht Android Plattform Application Framework Activities und Services

Mehr

Mobile App Development. - Einführung -

Mobile App Development. - Einführung - Mobile App Development - Einführung - Inhalt Organisatorisches Vorlesungsinhalt Mobile Geräte Android Architektur App Aufbau Praktikum Organisatorisches 4 SWS, 5 ECTS 2 Vorlesung / 2 Praktikum ca. 10 Wochen

Mehr

Mobile App Development. - Einführung -

Mobile App Development. - Einführung - Mobile App Development - Einführung - Inhalt Organisatorisches Vorlesungsinhalt Mobile Geräte Android Architektur App Aufbau Praktikum Organisatorisches 4 SWS, 5 ECTS 2 Vorlesung / 2 Praktikum 10 Wochen

Mehr

Sicherheit in Android

Sicherheit in Android Motivation Aufbau Sicherheit Ausblick Quellen Sicherheit in Android Peter Salchow INF-M2 - Anwendungen 1 Sommersemester 2008 Department Informatik HAW Hamburg 20. Mai 2008 Peter Salchow Sicherheit in Android

Mehr

App-Entwicklung für Android

App-Entwicklung für Android App-Entwicklung für Android Einleitung - Systemarchitektur Hochschule Darmstadt WS15/16 1 Inhalt Historie Systemarchitektur Sandbox 2 Motivation Kontra Pro Limitierte Größe Begrenzte Ressourcen Kein Standardgerät

Mehr

Android Processes & Services

Android Processes & Services Android Processes & Services Jürg Luthiger University of Applied Sciences Northwestern Switzerland Institute for Mobile and Distributed Systems Ziele heute Arbeitsblatt 4 besprechen (inkl. Repetition)

Mehr

Mobile Application Development

Mobile Application Development Mobile Application Development Android: Einführung Jürg Luthiger University of Applied Sciences Northwestern Switzerland Institute for Mobile and Distributed Systems Lernziele Der/die Kursbesucher/in kann

Mehr

Willkommen zur Vorlesung. Objektorientierte Programmierung Vertiefung - Java

Willkommen zur Vorlesung. Objektorientierte Programmierung Vertiefung - Java Willkommen zur Vorlesung Objektorientierte Programmierung Vertiefung - Java Zum Dozenten Mein Name: Andreas Berndt Diplom-Informatiker (TU Darmstadt) Derzeit Software-Entwickler für Web- Applikationen

Mehr

Datenhaltung für Android. Model First

Datenhaltung für Android. Model First Datenhaltung für Android Model First Frederik Götz, Johannes Tysiak 26.05.2011 Unser Ziel! 26.05.2011 Datenhaltung in Android - Model First» Frederik Götz, Johannes Tysiak 2 Agenda Android Quickstart Datenhaltung

Mehr

Programmieren für mobile Endgeräte SS 2013/2014. Dozenten: Patrick Förster, Michael Hasseler

Programmieren für mobile Endgeräte SS 2013/2014. Dozenten: Patrick Förster, Michael Hasseler Programmieren für mobile Endgeräte SS 2013/2014 Programmieren für mobile Endgeräte 2 Organisatorisches Anmelden im Web: ZIV Lehre Anmelden Anwesenheitsliste Anwesenheitsschein bei 75% Anwesenheit Allgemeine

Mehr

Google's Betriebssystem für mobile Plattformen. Vortrag von Michaela Rindt Universität Siegen

Google's Betriebssystem für mobile Plattformen. Vortrag von Michaela Rindt Universität Siegen Google's Betriebssystem für mobile Plattformen Vortrag von Michaela Rindt Universität Siegen Übersicht Einleitung Softwarearchitektur Softwareentwicklung für Android Unterschied zu anderen mobilen Plattformen

Mehr

Seminar Multimediale Werkzeuge Sommersemester 2011

Seminar Multimediale Werkzeuge Sommersemester 2011 Seminar Multimediale Werkzeuge Sommersemester 2011 Dipl.-Ing. Marco Niehaus marco.niehaus@tu-ilmenau.de 09.06.2011 Page 1 Android Development - Installation Java SDK wird benötigt (http://www.oracle.com/technetwork/java/javase/downloads/index.html)

Mehr

Mobile App Development

Mobile App Development Mobile App Development - Einführung - Inhalt Organisatorisches Vorlesungsinhalt Mobile Geräte Android Architektur App Aufbau Praktikum Organisatorisches 4 SWS, 5 ECTS 2 Vorlesung / 2 Praktikum 10 Wochen

Mehr

Auf einen Blick. Elementare Anwendungsbausteine. Telefonfunktionen nutzen. Dateien und Datenbanken. Organizer und Multimedia

Auf einen Blick. Elementare Anwendungsbausteine. Telefonfunktionen nutzen. Dateien und Datenbanken. Organizer und Multimedia Auf einen Blick Auf einen Blick TEIL I Grundlagen 1 Android eine offene, mobile Plattform... 21 2 Hallo Android!... 43 3 Von der Idee zur Veröffentlichung... 73 TEIL II Elementare Anwendungsbausteine 4

Mehr

Java Einführung Programmcode

Java Einführung Programmcode Java Einführung Programmcode Inhalt dieser Einheit Programmelemente Der erste Programmcode Die Entwicklungsumgebung: Sun's Java Software Development Kit (SDK) Vom Code zum Ausführen des Programms 2 Wiederholung:

Mehr

Programmieren in Java

Programmieren in Java Programmieren in Java objektorientierte Programmierung 2 2 Zusammenhang Klasse-Datei In jeder *.java Datei kann es genau eine public-klasse geben wobei Klassen- und Dateiname übereinstimmen. Es können

Mehr

Tutorium Java Ein Überblick. Helge Janicke

Tutorium Java Ein Überblick. Helge Janicke Tutorium Java Ein Überblick Helge Janicke 26. Oktober 2000 1 VORRAUSSETZUNGEN ZUM PROGRAMMIEREN MIT JAVA. 1 1 Vorraussetzungen zum Programmieren mit Java. Was braucht man, wenn man mit Java programmieren

Mehr

Projekt AGB-10 Fremdprojektanalyse

Projekt AGB-10 Fremdprojektanalyse Projekt AGB-10 Fremdprojektanalyse 17. Mai 2010 1 Inhaltsverzeichnis 1 Allgemeines 3 2 Produktübersicht 3 3 Grundsätzliche Struktur und Entwurfsprinzipien für das Gesamtsystem 3 3.1 Die Prefuse Library...............................

Mehr

App Entwicklung für Android F O R T G E S C H R I T T E N E P R O G R A M M I E R U N G I N J A V A

App Entwicklung für Android F O R T G E S C H R I T T E N E P R O G R A M M I E R U N G I N J A V A App Entwicklung für Android F O R T G E S C H R I T T E N E P R O G R A M M I E R U N G I N J A V A D O Z E N T : R E F E R E N T : P R O F. D R. K L I N K E R R I C O L O S C H W I T Z Aufbau der Präsentation

Mehr

Kapitel 6. Vererbung

Kapitel 6. Vererbung 1 Kapitel 6 2 Ziele Das sprinzip der objektorientierten Programmierung verstehen Und in Java umsetzen können Insbesondere folgende Begriffe verstehen und anwenden können: Ober/Unterklassen Subtyping Überschreiben

Mehr

Apps-Entwicklung mit Eclipse

Apps-Entwicklung mit Eclipse JDroid mit Eclipse Seite 1 Apps-Entwicklung mit Eclipse Version 1.1, 30. April 2013 Vorbereitungen: 1. JDK installieren JDK SE neuste Version (64 oder 32 Bit) herunterladen und installieren (http://www.oracle.com/technetwork/java/javase/downloads/index.html)

Mehr

Kapitel 6. Vererbung

Kapitel 6. Vererbung 1 Kapitel 6 2 Ziele Das sprinzip der objektorientierten Programmierung verstehen Und in Java umsetzen können Insbesondere folgende Begriffe verstehen und anwenden können: Ober/Unterklassen Subtyping Überschreiben

Mehr

Übungen zur Android Entwicklung

Übungen zur Android Entwicklung Übungen zur Android Entwicklung Aufgabe 1 Hello World Entwickeln Sie eine Hello World Android Applikation und laden diese auf den Emulator. Leiten Sie hierfür die Klasse android.app.activity ab und entwerfen

Mehr

Softwareentwicklungsprozess im Praktikum. 25. April 2013

Softwareentwicklungsprozess im Praktikum. 25. April 2013 Softwareentwicklungsprozess im Praktikum 25. April 2013 Agile Softwareentwicklung Eine agile Methodik stellt die beteiligten Menschen in den Mittelpunkt und versucht die Kommunikation und Zusammenarbeit

Mehr

APPS FÜR ANDROID ENTWICKELN

APPS FÜR ANDROID ENTWICKELN jan TITTEL jochen BAUMANN ELL N H C S IEG T S N I E APPS FÜR ANDROID ENTWICKELN AM BEISPIEL EINER REALEN APP Inhalt 1 Einführung.............................................. 1 1.1 Die Android-Plattform.................................................

Mehr

Einführung in Betriebssysteme

Einführung in Betriebssysteme Einführung in Betriebssysteme APPLE ios Entwicklung von ios Entwickelt auf der Basis von MacOS X UNIX Vorgestellt am 9.1.2007 Zusammen mit iphone Markenname von Cisco Internetwork Operating System Für

Mehr

Mobile Systeme Android 07.04.2011

Mobile Systeme Android 07.04.2011 Mobile Systeme Android 07.04.2011 Android Plattform/Betriebssystem für mobile Endgeräte wie z.b. Smartphones Basiert auf dem Linux Kernel Bis auf grundlegende Prozesse werden alle Anwenden mithilfe einer

Mehr

Geschäftsbereich Mobile Services Was ist Android?

Geschäftsbereich Mobile Services Was ist Android? Geschäftsbereich Mobile Services Was ist Android? Hinter Hoben 149 53129 Bonn www.visionera.de Ansprechpartner: Arno Becker arno.becker@visionera.de +49 228 555 1111 +49 160 98965856 Einleitung Android

Mehr

Arno Becker Marcus Pant. Android. Grundlagen und Programmierung. I dpunkt.verlag

Arno Becker Marcus Pant. Android. Grundlagen und Programmierung. I dpunkt.verlag Arno Becker Marcus Pant Android Grundlagen und Programmierung I dpunkt.verlag IX 1 Ein erstes Beispiel 3 1.1 Projekt anlegen 3 1.2 Die erste Activity 4 1.3 Layout definieren 5 1.4 Activities aufrufen 8

Mehr

Mobile App Development. - Activities -

Mobile App Development. - Activities - Mobile App Development - Activities - Inhalt Activity Lifecycle Erstellen Starten & Stoppen Spezielle Activities Ac3vi3es Ac3vi3es Definition Eine Aktivität repräsentiert eine Interaktion mit einem Benutzer

Mehr

NEXT GENERATION MOBILE PHONE PLATFORMS

NEXT GENERATION MOBILE PHONE PLATFORMS Stephan Zeisberg NEXT GENERATION MOBILE PHONE PLATFORMS Ein Einblick in die Systemarchitekturen aktueller Smartphones 1 Motivation Technologischer Stillstand in der Entwicklung mobiler Betriebssysteme

Mehr

Erste Erfahrungen mit Android

Erste Erfahrungen mit Android Java User Group München, 22. 9. 2008 Erste Erfahrungen mit Android 1 Was ist Android? Die erste vollständige, offene und freie Plattform für mobile Telefone Entwickelt von der Open Handset Alliance (Telecoms,

Mehr

Dateisysteme mit Plugin-Funktion

Dateisysteme mit Plugin-Funktion Dateisysteme mit Plugin-Funktion Basierend auf Reiser 4 unter Linux http://llugb.amsee.de/logo.gif Ausgearbeitet und vorgetragen von Michael Berger 1/23 Agenda Die Idee Dateisysteme mit Plugin-Funktion

Mehr

1. Java Grundbegriffe

1. Java Grundbegriffe 1. Java Grundbegriffe Geschichte von Java Programmieren mit Java Interpretieren vs. Kompilieren Java Byte-Code Jave Virtual Machine Arbeitsmaterialien Allgemeine Informatik 2 SS09 Folie 1.1 Java, eine

Mehr

Programmieren I. Die Programmiersprache Java. www.kit.edu. Institut für Angewandte Informatik

Programmieren I. Die Programmiersprache Java. www.kit.edu. Institut für Angewandte Informatik Programmieren I Die Programmiersprache Java KIT Universität des Landes Baden-Württemberg und nationales Großforschungszentrum in der Helmholtz-Gemeinschaft www.kit.edu Eigenschaften von Java Java ist eine

Mehr

Android 2. Grundlagen und Programmierung. dpunkt.verlag. Arno Becker Marcus Pant. 2., aktualisierte und erweiterte Auflage

Android 2. Grundlagen und Programmierung. dpunkt.verlag. Arno Becker Marcus Pant. 2., aktualisierte und erweiterte Auflage Arno Becker Marcus Pant Android 2 Grundlagen und Programmierung 2., aktualisierte und erweiterte Auflage Unter Mitarbeit von David Müller dpunkt.verlag IX I Inhaltsverzeichnis I Einführung 1 1 Ein erstes

Mehr

Java Einführung Methoden in Klassen

Java Einführung Methoden in Klassen Java Einführung Methoden in Klassen Lehrziel der Einheit Methoden Signatur (=Deklaration) einer Methode Zugriff/Sichtbarkeit Rückgabewerte Parameter Aufruf von Methoden (Nachrichten) Information Hiding

Mehr

Apps-Entwicklung mit Netbeans

Apps-Entwicklung mit Netbeans JDroid mit Netbeans Seite 1 Apps-Entwicklung mit Netbeans Version 2.2, 30. April 2013 Vorbereitungen: 1. JDK SE neuste Version installieren, (http://www.oracle.com/technetwork/java/javase/downloads/index.html)

Mehr

Drei-Schichten-Architektur. Informatik B - Objektorientierte Programmierung in Java. Vorlesung 16: 3-Schichten-Architektur 1 Fachkonzept - GUI

Drei-Schichten-Architektur. Informatik B - Objektorientierte Programmierung in Java. Vorlesung 16: 3-Schichten-Architektur 1 Fachkonzept - GUI Universität Osnabrück Drei-Schichten-Architektur 3 - Objektorientierte Programmierung in Java Vorlesung 6: 3-Schichten-Architektur Fachkonzept - GUI SS 2005 Prof. Dr. F.M. Thiesing, FH Dortmund Ein großer

Mehr

Walkabout: Location Based Services mit Android und dem Google Phone

Walkabout: Location Based Services mit Android und dem Google Phone Walkabout: Location Based Services mit Android und dem Google Phone Teilbereich 1: Die Android Plattform für mobile Geräte (Software) Von: Sebastian Schul Inhalt Einleitung Was ist Android Exkurs: Wie

Mehr

Erste Schritte mit Eclipse

Erste Schritte mit Eclipse Erste Schritte mit Eclipse März 2008, KLK 1) Java Development Kit (JDK) und Eclipse installieren In den PC-Pools der HAW sind der JDK und Eclipse schon installiert und können mit dem Application Launcher

Mehr

Connecting Android. Externe Hardware mit dem grünen Roboter verbinden. Alexander Dahmen Dominik Helleberg

Connecting Android. Externe Hardware mit dem grünen Roboter verbinden. Alexander Dahmen Dominik Helleberg Connecting Android Externe Hardware mit dem grünen Roboter verbinden Alexander Dahmen Dominik Helleberg Speaker Dominik Helleberg Mobile Development Android / Embedded Tools http://dominik-helleberg.de/+

Mehr

JDroidLib mit Eclipse (Mac/Linux/Windows)

JDroidLib mit Eclipse (Mac/Linux/Windows) JDroidLib mit Eclipse (Mac/Linux/Windows) Version 1.3, 25. März 2013 (Unter Windows besser die ADT-Bundle Version installieren, siehe entsprechende Anleitung) Vorbereitungen: 1. JDK SE neuste Version installieren,

Mehr

Automatische Modellierung des Lebenszyklus von Android-Anwendungen

Automatische Modellierung des Lebenszyklus von Android-Anwendungen Institut für Programmstrukturen und Datenorganisation (IPD) Lehrstuhl Programmierparadigmen Prof. Dr.-Ing. Gregor Snelting Diplomarbeit an der Fakultät für Informatik Automatische Modellierung des Lebenszyklus

Mehr

Ein mobiler Electronic Program Guide für Android

Ein mobiler Electronic Program Guide für Android Whitepaper Telekommunikation Ein mobiler Electronic Program Guide für Android Prototyp für Android Apps 2011 SYRACOM AG 1 Einleitung Apps Anwendungen für mobile Geräte sind derzeit in aller Munde. Durch

Mehr

Diese Anleitung bezieht sich auf FixFoto, V 3.40. In älteren oder neueren Versionen könnte die Arbeitsweise anders sein.

Diese Anleitung bezieht sich auf FixFoto, V 3.40. In älteren oder neueren Versionen könnte die Arbeitsweise anders sein. Pfade einstellen Stand: Dezember 2012 Diese Anleitung bezieht sich auf FixFoto, V 3.40. In älteren oder neueren Versionen könnte die Arbeitsweise anders sein. Diese Anleitung soll zeigen, wie man Pfad-Favoriten

Mehr

Praktikum Internetprotokolle - POP3

Praktikum Internetprotokolle - POP3 Technische Universität Ilmenau Fakultät für Informatik und Automatisierung Institut für Praktische Informatik und Medieninformatik Fachgebiet Telematik/Rechnernetze 19. Mai 2008 1 Aufgabenstellung Praktikum

Mehr

Benutzerdokumentation Web-Portal

Benutzerdokumentation Web-Portal GRUPP: SWT0822 Benutzerdokumentation Web-Portal Yet Another Reversi Game Martin Gielow, Stephan Mennicke, Daniel Moos, Christine Schröder, Christine Stüve, Christian Sura 05. Mai 2009 Inhalt 1. Einleitung...3

Mehr

Einführung in Android

Einführung in Android Einführung in Android FH Gießen Friedberg 12. Oktober 2010 Inhalt Historie / Prognosen / Trends Grundlagen Entwicklungsumgebung Standardbeispiel Erweitertes Beispiel Übung / Quellen 2 Grundlagen - Historie

Mehr

Objektorientierte Programmierung. Kapitel 12: Interfaces

Objektorientierte Programmierung. Kapitel 12: Interfaces 12. Interfaces 1/14 Objektorientierte Programmierung Kapitel 12: Interfaces Stefan Brass Martin-Luther-Universität Halle-Wittenberg Wintersemester 2012/13 http://www.informatik.uni-halle.de/ brass/oop12/

Mehr

Android GUI Entwicklung

Android GUI Entwicklung Android GUI Entwicklung Aktuelle Technologien verteilter Java Anwendungen Referent: Stefan Haupt Hello World! Agenda Einführung & Motivation Android Applikationen UI-Komponenten Events Ressourcen Kommunikation

Mehr

Das Interceptor Muster

Das Interceptor Muster Das Interceptor Muster Implementierung des Interceptor Musters basierend auf OSGi and Friends Benjamin Friedrich Hochschule für Technik und Wirtschaft des Saarlandes Praktische Informatik - Entwurfsmuster

Mehr

Java Desktop Anwendungen

Java Desktop Anwendungen 5 1 Java Desktop Anwendungen das Problem: Desktop-Anwendungen werden mit Hilfe der Swing- Bibliothek programmiert, aber die Swing-Bibliothek ist riesig und unübersichtlich es gab kein Programmiergerüst

Mehr

Userhandbuch. Version B-1-0-2 M

Userhandbuch. Version B-1-0-2 M Userhandbuch Version B-1-0-2 M Inhaltsverzeichnis 1.0 Was bietet mir SERVRACK?... 3 1.1 Anmeldung... 3 1.2 Passwort vergessen?... 3 1.3 Einstellungen werden in Realtime übernommen... 4 2.0 Die SERVRACK

Mehr

Erstellung eines SharkNet Installers für Windows mit Inno Setup Compiler 5.4.2

Erstellung eines SharkNet Installers für Windows mit Inno Setup Compiler 5.4.2 Erstellung eines SharkNet Installers für Windows mit Inno Setup Compiler 5.4.2 1. Benötigte Software Zur Erstellung des Installers wird folgende Software benötigt. Es wird sich in dieser Dokumentation

Mehr

Technische Beschreibung: EPOD Server

Technische Beschreibung: EPOD Server EPOD Encrypted Private Online Disc Technische Beschreibung: EPOD Server Fördergeber Förderprogramm Fördernehmer Projektleitung Projekt Metadaten Internet Foundation Austria netidee JKU Linz Institut für

Mehr

Mobile Computing I. Tickapp Projekt. Dustin Augstein, Thomas Filbry, Eric Jahn Sommersemester 2011. Prof. Dr. Jörg Sahm

Mobile Computing I. Tickapp Projekt. Dustin Augstein, Thomas Filbry, Eric Jahn Sommersemester 2011. Prof. Dr. Jörg Sahm Mobile Computing I Tickapp Projekt Dustin Augstein, Thomas Filbry, Eric Jahn Sommersemester 2011 Prof. Dr. Jörg Sahm Inhaltsverzeichnis Abbildungsverzeichniss... 3 1. Beschreibung der Anwendung... 4 1.1

Mehr

eclipse - Entwicklungsumgebung und mehr ETIS SS05

eclipse - Entwicklungsumgebung und mehr ETIS SS05 eclipse - Entwicklungsumgebung und mehr ETIS SS05 Gliederung Motivation Geschichte Architektur Platform Runtime Eclipse Platform Java Development Tools (JDE) Plugin Development Environment (PDE) Zusammenfassung

Mehr

2. ERSTELLEN VON APPS MIT DEM ADT PLUGIN VON ECLIPSE

2. ERSTELLEN VON APPS MIT DEM ADT PLUGIN VON ECLIPSE 2. ERSTELLEN VON APPS MIT DEM ADT PLUGIN VON ECLIPSE 2.1 Die Einrichtung der Benutzeroberfläche Das Einrichten einer Android-Eclipse-Entwicklungsumgebung zur Android-Entwicklung ist grundsätzlich nicht

Mehr

Ausarbeitung zum Vortrag Java Web Start von Adrian Fülöp Fach: Komponentenbasierte Softwareentwicklung WS 06/07 Fachhochschule Osnabrück

Ausarbeitung zum Vortrag Java Web Start von Adrian Fülöp Fach: Komponentenbasierte Softwareentwicklung WS 06/07 Fachhochschule Osnabrück Ausarbeitung zum Vortrag Java Web Start von Adrian Fülöp Fach: Komponentenbasierte Softwareentwicklung WS 06/07 Fachhochschule Osnabrück Adrian Fülöp (297545) - 1 - Inhaltsverzeichnis: 1. Einführung 2.

Mehr

2. GUI-Programmierung für mobile Geräte

2. GUI-Programmierung für mobile Geräte 2. GUI-Programmierung für mobile Geräte Lernziele 2. GUI-Programmierung für mobile Geräte Themen/Lernziele: Einführung Lebenszyklus einer Android-Anwendung Beispiele GUI-Elemente Einbindung externer Ressourcen

Mehr

Dynamische Plug-ins mit Eclipse 3. Martin Lippert (martin.lippert@it-agile.de, www.it-agile.de) Tammo Freese (freese@acm.org)

Dynamische Plug-ins mit Eclipse 3. Martin Lippert (martin.lippert@it-agile.de, www.it-agile.de) Tammo Freese (freese@acm.org) Dynamische Plug-ins mit Eclipse 3 Martin Lippert (martin.lippert@it-agile.de, www.it-agile.de) Tammo Freese (freese@acm.org) Überblick Die Ausgangslage Dynamische Plug-ins Warum? Eclipse 3 Die OSGi-basierte

Mehr

Datenhaltung für Android Model First. 30.03.2011 Christian Ingenhaag, Frederik Götz, Carl Steeg

Datenhaltung für Android Model First. 30.03.2011 Christian Ingenhaag, Frederik Götz, Carl Steeg Datenhaltung für Android Model First 30.03.2011 Christian Ingenhaag, Frederik Götz, Carl Steeg Agenda Datenhaltung in Android Motivation / Projektziele Projekt Umsetzung Stand der Entwicklung Fazit 2 Datenhaltung

Mehr

Programmieren für mobile Endgeräte SS 2013/2014. Dozenten: Patrick Förster, Michael Hasseler

Programmieren für mobile Endgeräte SS 2013/2014. Dozenten: Patrick Förster, Michael Hasseler Programmieren für mobile Endgeräte SS 2013/2014 Programmieren für mobile Endgeräte 2 Besprechung der Aufgaben 1) Legen Sie das Android-Projekt HelloWorldApp an so wie es in den vorherigen Folien beschrieben

Mehr

Android. 2 24.09.2013 Mobile Systeme - Android

Android. 2 24.09.2013 Mobile Systeme - Android Android 24.09.2013 Android Plattform/Betriebssystem für mobile Endgeräte wie z.b. Smartphones Basiert auf dem Linux Kernel Bis auf grundlegende Prozesse werden alle Anwenden mithilfe einer speziellen JVM

Mehr

3. Konzepte der objektorientierten Programmierung

3. Konzepte der objektorientierten Programmierung 3. Konzepte der objektorientierten Programmierung 3.1 Basiskonzepte 3.2 Generalisierung / Spezialisierung 3.3 Aggregation 3.4 Assoziation 3.5 Nachrichten 3.6 Polymorphismus 3. Konzepte der Objektorientierung

Mehr

Hello World in Java. Der Weg zum ersten Java-Programm

Hello World in Java. Der Weg zum ersten Java-Programm Vorwort Hello World in Java Der Weg zum ersten Java-Programm Diese Anleitung wurde unter Windows XP verfasst. Grundsätzlich sollte sie auch unter späteren Windows Versionen wie Windows Vista oder Windows

Mehr

Erste Schritte zum lauffähigen Java Programm

Erste Schritte zum lauffähigen Java Programm Erste Schritte zum lauffähigen Java Programm Diese kleine Einführung ist eine Hilfe für Studenten der Vorlesung SWT I zur Meisterung der sich ergebenden Hürden bei der Erstellung eines ersten kleinen Java-Programms.

Mehr

PIWIN I. Praktische Informatik für Wirtschaftsmathematiker, Ingenieure und Naturwissenschaftler I. Vorlesung 3 SWS WS 2007/2008

PIWIN I. Praktische Informatik für Wirtschaftsmathematiker, Ingenieure und Naturwissenschaftler I. Vorlesung 3 SWS WS 2007/2008 PIWIN I Kap. 7 Objektorientierte Programmierung - Einführung 1 PIWIN I Praktische Informatik für Wirtschaftsmathematiker, Ingenieure und Naturwissenschaftler I Vorlesung 3 SWS WS 2007/2008 FB Informatik

Mehr

Glossar. Launching auf.

Glossar. Launching auf. 243 Ad Hoc Distribution Die Ad Hoc Distribution ist eine Möglichkeit, um Ihre entwickelte Anwendung auf anderen Endgeräten zu verteilen. Diese Art der Verteilung erfolgt ohne den App Store. Die Anzahl

Mehr

Android - Basics. 16.10.2013 Praktikum Enwicklung von Mediensystemen WS13/14

Android - Basics. 16.10.2013 Praktikum Enwicklung von Mediensystemen WS13/14 Android - Basics 1 Heute Was ist Android? Programmieren für Android App-Struktur Activities und Intents App-Design GUI und Layout 2 Android in a nutshell Open-Source (Open Headset Alliance) Basiert auf

Mehr

1 Der Einstieg in Java für Android

1 Der Einstieg in Java für Android 1 1 Der Einstieg in Java für Android Diese Ergänzung zum Buch Programmieren in Java will Ihnen dabei helfen, erste Programme für Smartphones der Android Plattform von Google zu erstellen und diese Programme

Mehr

Android. LUG-LD Christoph Maya 2011 http://demaya.de. Lizenz: http://creativecommons.org/licenses/by-nc/3.0/de/

Android. LUG-LD Christoph Maya 2011 http://demaya.de. Lizenz: http://creativecommons.org/licenses/by-nc/3.0/de/ Android LUG-LD Christoph Maya 2011 http://demaya.de Lizenz: http://creativecommons.org/licenses/by-nc/3.0/de/ Inhalt Inhalt: ein Mix für Einsteiger und Fortgeschrittene Was ist Android und wo kommts her?

Mehr

ANT. Kurzvortrag von Manuel Schulze. mschulze@inf.fu-berlin.de

ANT. Kurzvortrag von Manuel Schulze. mschulze@inf.fu-berlin.de ANT Kurzvortrag von Manuel Schulze mschulze@inf.fu-berlin.de ANT Überblick Teilprojekt der Apache Software Foundation [1] ANT ist Opensource Build-Tool ähnlich wie make (?) jedoch voll auf Java zugeschnitten

Mehr

C# - PROGRAMME MIT PLUGINS ERWEITERN

C# - PROGRAMME MIT PLUGINS ERWEITERN C# - PROGRAMME MIT PLUGINS ERWEITERN Schreibt man ein Programm welches erweiterbar sein soll, dann gibt es häufig mehrere Möglichkeiten dies umzusetzen. Die Objektorientierung ist dabei der erste Schritt,

Mehr

Variablen manipulieren per JDI

Variablen manipulieren per JDI Variablen manipulieren per JDI Zusammenfassung Jede moderne Java IDE verfügt über eine mächtige und dennoch meist einfach zu bedienende Benutzeroberfläche die das finden von Fehlern in lokalen oder entfernt

Mehr

3827260108 Private Homepage vermarkten So laden Sie Ihre Website auf den Server Das lernen Sie in diesem Kapitel: n So funktioniert FTP n Diese FTP-Programme gibt es n So laden Sie Ihre Website mit WS-FTP

Mehr

White Paper. Embedded Treiberframework. Einführung

White Paper. Embedded Treiberframework. Einführung Embedded Treiberframework Einführung White Paper Dieses White Paper beschreibt die Architektur einer Laufzeitumgebung für Gerätetreiber im embedded Umfeld. Dieses Treiberframework ist dabei auf jede embedded

Mehr

Fachseminar Android. Tobias Braumann Wintersemester 2009/10 Matrikelnummer.: 353640

Fachseminar Android. Tobias Braumann Wintersemester 2009/10 Matrikelnummer.: 353640 Fachseminar Android Wintersemester 2009/10 Matrikelnummer.: 353640 Inhalt 0 Vorwort... 3 1 Allgemeines zu Android... 4 1.1 Einleitung... 4 1.2 Warum Android?... 4 1.3 Entwicklungshistorie... 5 1.4 Open

Mehr

Software Engineering II

Software Engineering II Software Engineering II Codegenerierung für den SmartIO Editor mit der Modeling Workflow Engine Wintersemester 10/111 Fachgebiet Software Engineering Albert Zündorf / Wiederholung Bisher im Laufe des Semesters

Mehr

Datensicherung. Beschreibung der Datensicherung

Datensicherung. Beschreibung der Datensicherung Datensicherung Mit dem Datensicherungsprogramm können Sie Ihre persönlichen Daten problemlos Sichern. Es ist möglich eine komplette Datensicherung durchzuführen, aber auch nur die neuen und geänderten

Mehr

JAVA. Ein kurzer Überblick. Thomas Karp

JAVA. Ein kurzer Überblick. Thomas Karp JAVA Ein kurzer Überblick Thomas Karp WAS IST JAVA? Java ist eine fast rein objektorientierte Sprache nicht JavaScript eine professionelle Sprache eine im Unterricht weit verbreitete Sprache für verschiedene

Mehr

Version 4.4. security.manager. Systemvoraussetzungen

Version 4.4. security.manager. Systemvoraussetzungen Version 4.4 security.manager Systemvoraussetzungen Version 4.4 Urheberschutz Der rechtmäßige Erwerb der con terra Softwareprodukte und der zugehörigen Dokumente berechtigt den Lizenznehmer zur Nutzung

Mehr

Kundenanforderungen. Hochschule Luzern Technik & Architektur. Software Komponenten FS13. Gruppe 03 Horw, 24.05.2013

Kundenanforderungen. Hochschule Luzern Technik & Architektur. Software Komponenten FS13. Gruppe 03 Horw, 24.05.2013 Software Komponenten FS13 Gruppe 03 Horw, 24.05.2013 Bontekoe Christian Estermann Michael Moor Simon Rohrer Felix Autoren Bontekoe Christian Studiengang Informatiker (Berufsbegleitend) Adresse Telefon

Mehr

Installation und Benutzung AD.NAV.ZipTools

Installation und Benutzung AD.NAV.ZipTools Installation und Benutzung AD.NAV.ZipTools Version 1.0.0.0 ALTENBRAND Datentechnik GmbH Am Gelicht 5 35279 Neustadt (Hessen) Tel: 06692/202 290 Fax: 06692/204 741 email: support@altenbrand.de Die Komponente

Mehr

Modellierung einer Android-App. 2. Mai 2013

Modellierung einer Android-App. 2. Mai 2013 Modellierung einer Android-App 2. Mai 2013 Taentzer Software-Praktikum 2013 42 Überblick Modellierung der wesentlichen Aspekte Welche Anwendungsfälle haben wir? Übersicht durch Anwendungsfalldiagramme

Mehr

ALM mit Visual Studio Online. Philip Gossweiler Noser Engineering AG

ALM mit Visual Studio Online. Philip Gossweiler Noser Engineering AG ALM mit Visual Studio Online Philip Gossweiler Noser Engineering AG Was ist Visual Studio Online? Visual Studio Online hiess bis November 2013 Team Foundation Service Kernstück von Visual Studio Online

Mehr

Zeiterfassung-Konnektor Handbuch

Zeiterfassung-Konnektor Handbuch Zeiterfassung-Konnektor Handbuch Inhalt In diesem Handbuch werden Sie den Konnektor kennen sowie verstehen lernen. Es wird beschrieben wie Sie den Konnektor einstellen und wie das System funktioniert,

Mehr

Hyper-V Server 2008 R2

Hyper-V Server 2008 R2 Hyper-V Server 2008 R2 1 Einrichtung und Installation des Hyper-V-Servers 1.1 Download und Installation 4 1.2 Die Administration auf dem Client 9 1.3 Eine VM aufsetzen 16 1.4 Weiterführende Hinweise 22

Mehr

So nutzen Sie die HiDrive App mit Ihrem Android Smartphone

So nutzen Sie die HiDrive App mit Ihrem Android Smartphone So nutzen Sie die HiDrive App mit Ihrem Android Smartphone Die STRATO HiDrive App ermöglicht Ihnen die bequeme Nutzung Ihres Kontos mit Ihrem Android Smartphone. Betrachten Sie direkt Ihre Inhalte und

Mehr

UI-Testing mit Microsoft Test Manager (MTM) Philip Gossweiler / 2013-04-18

UI-Testing mit Microsoft Test Manager (MTM) Philip Gossweiler / 2013-04-18 UI-Testing mit Microsoft Test Manager (MTM) Philip Gossweiler / 2013-04-18 Software Testing Automatisiert Manuell 100% 70% 1 Überwiegender Teil der Testing Tools fokusiert auf automatisiertes Testen Microsoft

Mehr

Übersicht. Informatik 2 Teil 3 Anwendungsbeispiel für objektorientierte Programmierung

Übersicht. Informatik 2 Teil 3 Anwendungsbeispiel für objektorientierte Programmierung Übersicht 3.1 Modell Konto 3.2 Modell Konto - Erläuterungen 3.3 Benutzer Ein- und Ausgabe mit Dialogfenster I 3.4 Benutzer Ein- und Ausgabe mit Dialogfenster II 3.5 Klassen- und Objekteigenschaften des

Mehr