Capture error
Grundlage für diesen Fehler sind zwei Handlungsabläufe, die sich ähneln und insbesondere einen gleichen Anfangszustand besitzen. Hierbei ist ein Handlungsablauf ein ganz alltäglicher Ablauf, der sehr häufig ausgeführt wird, während der andere eher eine Ausnahme darstellt. Führt man nun eine seltenere Handlung aus, so ist es aufgrund des Capture erros möglich, dass diese Handlung durch die alltäglichere ersetz wird.
Beispiel:
Alice fährt jeden Tag morgens mit dem Bus in die Uni. Am ersten Tag der Semesterferien hat sie sich vorgenommen, einkaufen zu fahren. An der Bushaltestelle steigt sie trotzdem in den Bus zur Uni, anstatt in Richtung des Einkaufszentrums, da der gewohnte Handlungsablauf den selteneren übernommen hat.
Description error
Ein Description error ist möglich, wenn eine beabsichtigte Handlung sehr viel Ähnlichkeiten mit anderen Alternativen aufweist. Grund hierfür ist, dass die mentale Beschreibung des Ziels nicht genau genug ist um bei der Ausführung eindeutig zu sein.
Beispiel:
Bob will eine Milchpackung aus der Tür des Kühlschrank holen, um sich ein Glas zu füllen. Seine mentale Beschreibung weist ihn an, die Packung aus der Kühlschranktür zu holen. Nun öffnet Bob die Türe des Kühlschrankes, greift allerdings zur benachbarten Packung Orangensaft, da sie in diesem Moment exakt auf die mentale Beschreibung passt. Der Description error hat zugeschlagen.
Data-driven error
Viele Aspekte menschlichen Verhaltens passieren unbewusst und werden direkt durch das Empfangen sensorischer Daten ausgelöst. Ein Beispiel für dieses Verhalten ist die Reaktion auf ein Insekt auf der Haut, das man ohne Überlegung beiseite schiebt. Nun kann es passieren, dass beim Eintreffen neuer sensorischer Daten diese mit gerade laufenden mentalen Prozessen interferieren und einen Data-driven error auslösen.
Beispiel:
Caroline schreibt gerade einen Blogeintrag über eine Person. Plötzlich poppt eine ICQ-Nachricht von einem Freund auf. Fälscherlicherweise schreibt sie nun in ihrem aktuellen Satz nicht den Namen der ursprünglichen Person, sondern den Namens des ICQ-Kontaktes.
Associative activation error
Genauso wie externe Daten können auch interne Gedanken oder Assoziationen laufende Handlungen beeinflußen.
Beispiel:
Daniels Telefon klingelt und er begrüßt sein Gegenüber mit „Herein!“
Diese Art von Fehler ist übrigens eng mit dem verwandt, was allgemein unter Freud’schen Fehlleistungen bekannt ist. Auch das Verwechseln von Namen enger Verwandter bei Familienfesten fällt in diese Kategorie.
Loss of activation error
Diesen Fehler dürfte wohl jedem bekannt sein. Es geht darum, eine gewisse Aktion durchführen zu wollen, jedoch bei der Ausführung das eigentliche Ziel zu vergessen.
Beispiel:
Erik geht in das Schlafzimmer, um ein T-Shirt aus dem Schrank zu holen. Als er im Schlafzimmer ankommt, weiß er nicht mehr, wieso er eigentlich ins Schlafzimmer gegangen ist.
Mode error
Diese Fehler entstehen, wenn bestimmte Aktionen abhängig vom Zustand zu anderen Ergebnissen führen, und man sich mental in einem anderen Zustand befindet, als man in Wirklichkeit ist.
Beispiel:
Felizitas ist auf dem Heimweg von einer Party. Sie möchte wissen wie viel Zeit sie benötigt und startet ihre Stopfunktion an ihrer digitalen Armbanduhr. Nach ein paar Minuten möchte sie wissen, wie lange sie schon unterwegs ist und drückt die Taste, die im normalen Modus zur Aktivierung der Hintergrundbeleuchtung der Anzeige zuständig ist. Leider sorgt die gleiche Taste im Stopmodus dafür, dass die Zeitmessung zurückgesetzt wird. Sie ist Opfer eines typischen Mode errors geworden.
Insbesondere der letzte Fehler ist ein wichtiger Fehler im Zusammenhang mit interaktiven System, denn er zeigt auf, wie wichtig es ist, dem Benutzer ein klares, verständliches mentales Modell der Anwendung zu liefern, welches solche Fehler verhindert. Ein anderes Beispiel im Bereich des Computers zur Verhinderung ist fehlertolerantes Löschen. Oft möchte man eine gewisse Datei löschen, vertut sich aber der Auswahl, zum Beispiel aufgrund eines Description errors, und selektiert die falsche Datei. Auch die direkt darauf folgende Löschbestätigung hilft nicht, da die Person sich noch in einem Handlungsablauf befindet, in der sie davon ausgeht, dass sie die richtige Datei löscht. Erst später bei der Bewertung des Handlungablaufes erkennt sie wohlmöglich den ausgeführten Fehler. Durch einen Papierkorb, der gelöschte Objekte zunächst zwischenspeichert, oder durch die Implementierung von Undo-Funktionen lassen sich solche Fehler benutzerfreundlich beheben.
Weiterführende Literatur: The Psychology of Everyday Things, Donald A. Norman (1988)
]]>Die eigentliche Semantik des Aufrufs im Sinne von HTTP bestimmt die verwendete Methode, die Bestandteil des Requests ist. Das absolute Arbeitstier des Webs ist GET, allerdings existieren noch weitere Methoden: POST, HEAD, PUT, DELETE und OPTIONS. Deren Bedeutung und korrekte Verwendung ist allgemein jedoch weniger bekannt. Deswegen möchte ich diese Methoden kurz vorstellen und ihre Funktionen erklären. Dafür gehe ich nun zunächst auf die verschiedenen Merkmale ein, die Methoden besitzen nach RFC 2616 können.
Eine Methode gilt als sicher, wenn sie keine Seiteneffekte erzeugt. Mögliche Seiteneffekte wären zum Beispiel das Ändnern einer Resource oder das Durchführen eines Logouts. Zu einem gewissen Grad sind auch Seiteneffekte bei sicheren Methoden erlaubt. Allerdings nur dann, wenn sie nicht explizit vom Benutzer intendiert sind. Darunter fallen zum Beispiel Logfiles, die Zugriffe aufzeichnen oder Zähler, die Zugriffe aufsummieren.
Als idempotent bezeichnet man eine HTTP Methode dann, wenn die mehrfache Ausführung eines Requests die gleichen Seiteneffekte besitzt wie ein einmaliges Ausführen. Ob ein Request für das Löschen einer Resource einfach oder mehrfach ausgeführt wird, das Resultat bleibt ist identisch, die Resource wurde gelöscht. Idempotenz ist für verteilten Systemten eine interessante Aufrufsemantik, da sie es im Fehlerfall ermöglicht, einen Request zu wiederholen (At-least-once). Bei nicht idempotenten Methoden ist dies nicht möglich. Ein Beispiel hierfür wäre eine Onlinebanking-Sitzung, die zur Übermittlung einer Überweisung eine nicht-idempotente Methode verwendet. Bricht nun während des Requests die Verbindung ab, so ist unklar, ob eine erneute Ausführung zu mehrfachen Überweisungen führt oder nicht (in der Praxis wird dies z.B. durch Tokens auf Anwendungsebene verhindert).
Eine dritte Eigenschaft einer Methode ist Möglichkeit, ob bei ihrer Verwendung Responses gecachet werden können. Es ist offensichtlich, dass dies nur für Methoden ohne Seiteneffekte funktioneren kann. Ebenfalls ist Caching nur dann sinnvoll und erlaubt, wenn die Responses entsprechende Caching-Informationen bereitstellen. Dies ist unter anderem möglich durch die Angabe von Expire-Zeiten, Datum der letzten Änderungen oder Entity-Tag-Angaben. Sie beschreiben, wie lange das Resultat einer Response weiterverwendet werden kann, ohne einen erneuten Request zu versenden. Beziehungsweise geben sie an, wie bei einem konditionellen Request übermittelt werden kann, was die bekannte Version des Entities ist. Sofern sich die Resource nicht verändert hat, muss sie somit nicht erneut übertragen werden.
safe | idempotent | cachable | |
HEAD | ✓ | ✓ | (✓) |
GET | ✓ | ✓ | (✓) |
PUT | ✓ | ||
DELETE | ✓ | ||
POST |
GET
GET ist die meistverwendete Methode und dient zur Abfrage einer Resource. Sie ist sowohl sicher als auch idempotent, und erlaubt Caching. Erweiterte Funktionen von GET erlauben konditionale Abfragen, sowie partielle Abfragen. Ersteres erlaubt eine effiziente Benutzung von Caching, um nur dann eine Resource erneut zu laden, wenn sie sich auch wirklich verändert hat oder aktualisiert wurde. Letzteres dient dazu, nur Teile einer Resource abzurufen. Das kann zum Beispiel bei einer Resource, die eine Videodatei darstellt, hilfreich sein und erlaubt das Springen im Bytestream des Videos.
HEAD
HEAD besitzt prinzipiell die gleiche Semantik wie GET, allerdings wird keine Repräsentation der Resource mitgesendet, das heißt die Response enthält kein Entity. Somit ist HEAD nützlich, um Metadaten über die Resource abzufragen, oder überhaupt des Existenz zu überprüfen.
PUT
Mit PUT kann eine Resource erstellt oder ersetzt werden. Hierfür ist im Request ein Entity enthalten, dass eine neue Repräsentation der Resource enthält. Die zu erstellende/ändernde Resource wird durch die URI eindeutig beschrieben.
DELETE
DELETE entfernt die durch die URI identifizierte Resource.
POST
Als letzte Methode wird POST für alle weiteren Aufrufe verwendet, insbesondere wenn keinerlei Zugeständnisse bezüglich der Semantiken gemacht werden (können), insbesondere Idempotenz. In der Praxis dient POST ebenfalls zum Erzeugen und Ändern von Resourcen, aber auch für viele weitere Aktionen, die via POST quasi ‘getunnelt’ werden.
Für weitere Informationen empfiehlt es sich, RFC 2616 zu lesen.
]]>Im diesem Beitrag soll gezeigt werden, wie man mit der offenen Karten-Library Leaflet und jquery.couch.js geographische Daten aus CouchDB heraus auf einer Karten anzeigen kann.
Wir verwenden die CouchApp aus dem ersten Teil weiter, und fügen zu den bisherigen Map/Reduce Views noch statische HTML- und Javascript-Dateien hinzu (im _attachments
Ordner), die dann im Browser abgerufen werden können. Beim Aufruf dieser Webseite wird ein HTML-Grundgerüst übertragen, sowie eine JavaScript-Datei, die beim Aufruf die eigentlichen Datensätze via jquery.couch.js aus der CouchDB nachlädt.
Als Mapping-Library verwenden wir Leaflet, eine Open-Source Bibliothek für Kartendarstellungen im Browser. Leaflet abstrahiert von verschiedenen Kartenprovidern und erlaubt es somit, unterschiedliche Datenquellen zu verwenden, wie zum Beispiel auch Bing Maps oder Cloudmade. Letzteres ist ein Service, der auf Basis der Open Street Map Daten Kartenkacheln mit individuellen Stilen rendert und hostet – für Visualisierungen oft sehr hilfreich, da reguläre Karten meist zu viele Karteninformationen enthalten oder farblich überladen sind. In unserem Fall haben wir einen einfach Graustufenkarte gewählt. Leaflet selbst lässt sich relativ leicht verwenden, es muss eine CSS-Datei sowie eine JavaScript-Datei importiert werden, und ein div-Block im HTML enthalten sein, worin später die Karte gerendert werden soll. Somit sieht unser HTML-Gerüst zu Beginn so aus:
<!doctype html> <html> <head> <link rel="stylesheet" href="style/leaflet.css" /> <script type="text/javascript" src="js/jquery.min.js"></script> <script type="text/javascript" src="js/jquery.couch.js"></script> <script type="text/javascript" src="js/leaflet.js"></script> <script type="text/javascript" src="js/maploader.js"></script> </head> <body> <div id="map"></div> </body> </html>
Es werden jQuery, jquery.couch.js und die Leaflet-Libs geladen, und die letzte importierte JavaScript-Datei soll nun unseren Code zum initialisieren der Karte und dem Laden der Daten aus der CouchDB enthalten. Zunächst erstellen wir eine Karte und rendern sie, sobald die Seite vollständig geladen wurde (jQuery Callback für document.ready):
$(document).ready(function(){ var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/[YOUR_API_KEY]/33481/256/{z}/{x}/{y}.png'; var cloudmadeAttribution = 'UlmApi.de / Shape Files: Stadt Ulm (cc-by-sa), Map data © 2011 OpenStreetMap contributors, Imagery © 2011 CloudMade'; var cloudmade = new L.TileLayer( cloudmadeUrl, { maxZoom : 18, attribution : cloudmadeAttribution }); var map = new L.Map('map', { center : new L.LatLng(48.399976,9.995399), zoom : 12, layers : [ cloudmade ], zoomControl : false }); });
In der cloudmadeUrl
muss für Cloudmade Karte ein korrekter API-Key angegeben werden, der nächste Parameter im Pfad identifiziert den Kartentyp. Beim Initialisieren der Karte wird dann die ID des div
s angeben, bei uns ‘map’. Nun sollte unsere Karte bereits dargestellt werden, nachdem wir die CouchApp neu deployen (außerhalb des Fokus dieses Artikels, mehr dazu auf couchapp.org).
Was nun noch fehlt, ist das Nachladen der Geodaten aus der CouchDB und die Anzeige auf der Karte. Hierfür verwenden wir jquery.couch.js als Wrapper für die AJAX-Requests gegen CouchDB und die GeoJSON-Funktionalität von Leaflet:
$.couch.db('database_name').view('design_doc_name/view_name', { success: function(data){ if(data && data.rows && data.rows.length){ var geoJsonLayer = new L.GeoJSON(); for(var i = 0;i<data.rows.length;i++){ geoJsonLayer.addGeoJSON(data.rows[i].value.geometry); } map.addLayer(geoJsonLayer); } } });
Das obige Snippet sollte im vorherigen Code hinter der Erzeugung der Karte eingefügt werden. Es ruft von der Datenbank ‘database_name’ den View ‘view_name’ des Design-Dokuments ‘design_doc_name’ auf, und iteriert bei erfolgreicher Abfrage über alle Zeilen. Von jeder Zeile wird dabei die geometry-Property zu einem GeoJSON-Layer hinzugefügt, der am Ende an die Karte übergeben wird. Da unser View aus Teil 1 bereits GeoJSON generiert, und Leaflet nativ GeoJSON lesen und darstellen kann, ist das Hinzufügen von Geodaten auf die Karte sehr einfach.
Hier noch ein paar weiterführende Links mit vertiefenden Inhalten zu den einzelnen Themen:
Für unser Vorhaben habe ich als Persistenzlösung die dokumentenorientierte Datenbank CouchDB gewählt, da sie für uns mehrere interessante Features bietet:
Von der Stadt Ulm haben wir als ersten Datensatz Shapefiles der Ulmer Stadtteile und Stadtviertel bekommen. Diese wurden zunächst vom Ausgangsformat (Gauss-Krueger-Shapefiles) in das GeoJSON-Format mit WGS84-Koordinaten konvertiert. Mithilfe eines kleinen node.js Skriptes wurden die einzelnen Shapes dann als Dokumente auf die Couch geladen.
Ein Dokument hat hierbei folgende Form (Originaldokument):
{ "_id": "ul-st14", "_rev": "1-797187e292d93b6d661ca8f7fec3f6c9", "type": "stadtteil", "name": "Weststadt" "geometry": { "type": "Feature", "properties": { "identifier": "ST 14", "name": "Weststadt" }, "geometry": { "type": "Polygon", "coordinates": [ [ [ 9.981459, 48.395751 ], … ] ] } }, "creator": "Stadt Ulm", "license": { "name": "Creative Commons - Namensnennung-Weitergabe unter gleichen Bedingungen 3.0 Deutschland (CC BY-SA 3.0)", "link": "http://creativecommons.org/licenses/by-sa/3.0/de/" }, }
Die _id
bestimmt das Dokument eindeutig, das _rev
Feld ist für die Versionskontrolle verantwortlich. Als ID haben wir einen internen Identifier der Stadt genommen und noch mit einem “ul” Präfix versehen. Der Rest des Dokuments kann frei strukturiert werden. In unserem Fall verwenden wir noch ein type
Feld, durch das wir später Dokumente unterschiedlichen Typs unterscheiden können (z.B. stadtteil
oder stadtviertel
). Das geometry Feld (hier gekürzt) enthält die geografischen Daten im GeoJSON-Format. Die sonstigen Felder beschreiben noch den Urheber und die Lizenz der Daten sowie den Namen des Eintrags.
Nun ist die Datenbank gefüllt, später sollen aber die Daten auch wieder abgefragt werden können. Als “NoSQL” Datenbank bietet CouchDB hierfür aber keine SQL-Statements an, stattdessen muss man mithilfe von MapReduce beschreiben, wie aus den Daten Indizes gebildet werden sollen:
function(doc) { if (doc.type) { if(doc.type === 'stadtviertel'){ emit(['stadtviertel', doc._id], { 'geometry' : doc.geometry, 'label' : "<b>Stadtviertel "+doc.name+"</b><br/>ID: "+doc._id+"<br/>(Stadteil "+doc.stadtteil+")" }); } else if(doc.type === 'stadtteil'){ emit(['stadtteil', doc._id], { 'geometry' : doc.geometry, 'label' : "<b>Stadtteil "+doc.name+"</b><br/>ID: "+doc._id }); } } };
Damit erzeugen wir eine sortiere Liste von Schlüssel-Wert-Paaren. Der Schlüssel ist selbst wieder komplex und besteht aus zwei Teilen. Der erste Teil ist der Typ, der zweite Teil die ID des Dokuments. Damit kann man später durch die sogenannte View Collation Abfragen durchführen, die sich auf einen bestimmtem Typ beschränken (zur Wiederholung: ohne SQL gibt es hier auch kein WHERE Statement). In diesem Fall werden bisher nur Dokumente des Typs stadtteil
oder stadtviertel
eingetragen, und als Wert eines Eintrages wird bereits die spätere Nutzung auf einer Karte vorbereitet – es werden die Geodaten sowie ein Label indiziert. Damit lassen sich nun schon Stadtteile/Stadtviertel abfragen.
Ergänzt man dies noch um einen räumlichen Index, so werden auch räumliche Abfragen ermöglicht. Hierfür werden in den Index als Schlüssel die Geodaten (unverändert im GeoJSON Format) eingetragen, den Wert selbst bliebt leer, da das Feld _id
sowieso eingetragen wird und vorest keine weiteren Daten mehr benötigt werden:
function(doc){ if(doc.geometry && doc.geometry.geometry){ emit(doc.geometry.geometry, null); } };
(Das etwas merkwürdig anmutende doc.geometry.geometry
entstand einerseits dadruch, dass unser Feld mit dem GeoJSON-Objekt geometry
heißt, das GeoJSON-Objekt selbst aber komplex ist und nur in einem Teil davon die eigentlichen Geodaten hinterlegt sind.)
Mithilfe dieses Index lässt sich nun bei einem gegebenen geografischen Raum überprüfen, welche Objekte darin enthalten sind. Also zum Beispiel ausgehend von einer Koordinate, ob sie sich in Ulm befindet und wenn ja, in welchem Stadtteil/Stadtviertel.
Im nächsten Teil wird näher betrachtet, wie die nun abgespeicherten und indizierten Geodaten im Browser auf einer Karte dargestellt werden können, und zwar direkt aus CouchDB heraus.
]]>Nicht nur die Frage, wie man richtig präsentiert (Stichwort Zen vs. Death by PowerPoint), sondern auch die Frage, mit welchen Anwendungen man präsentiert, ist oft umstritten. Ich persönlich konnte mich bisher mit Powerpoint und Konsorten eher wenig anfreunden – vor allem Typografie und Einschränkungen bei der Gestaltung waren problematisch. Als Alternative habe ich bisher oft LaTeX Beamer verwendet, was allerdings je nach visueller Komplexität auch oft relativ zeitaufwendig ist, sich aber zumindest bei Grafiken in Vektorformaten und mathematischen Inhalten auch schnell auszahlt.
HTML5-basierte Foliensätze
Mit dem Aufkommen von HTML5 entstand eine zusätzliche Möglichkeit. Dank der neuen Multimedia-Tags wie <audio> und <video> sowie mächtigeren CSS Stilen bietet HTML nun die Grundlagen für browser-basierte Präsentationen. Mittlerweile gibt es hierfür auch schon mehrere Templates:
Noch mehr Alternativen gibt es in dieser Auflistung. Ein weiteres sehr schönes Beispiel ist ein Foliensatz zu HTML5, der selbst quasi eine Technologiedemonstration enthält: http://slides.html5rocks.com
Der Vorteil von HTML-basierten Präsentationen ist die hohe Anzahl von Medien (u.a. auch SVG, Flash Videos oder ganze Webseiten als IFrames), die man einbetten kann. Ein einfaches, weitläufig bekanntes Markup ermöglicht das schnelle Erstellen von Folien, und mit einer Kombination aus HTML, CSS und JavaScript lassen sich dennoch auch komplexe Spezialfunktionen realisieren.
Mir persönlich hat das html5slides Template ganz gut gefallen, das Google entwickelt und für die Google I/O Slides eingesetzt hat. Da das Template unter einer Apache License veröffentlicht wurde, habe ich zunächst damit begonnen, es an das Uni Ulm Corporate Design anzupassen. Außerdem hatte ich ein paar kleine Änderungen am Code vorgenommen, um zum Beispiel eine Nummerierung der Folien zu ermöglichen.
Presenter Mode?
Prinzipiell war das Ergebnis schon mal akzeptabel, allerdings wurden die oft genannten Probleme bei solchen HTML-Foliensätzen schnell offensichtlich – fehlende Notizen für den Vortragenden und nur eine Ausgabe.
Eher durch Zufall bin ich auf ein anderes Konstrukt gestoßen, dass seit HTML5 Cross-Frame-Communication erlaubt, also den Austausch von Nachrichten zwischen zwei verschiedenen Frames (mit einigen Einschränkungen): window.postMessage()
Die Möglichkeit, zwischen Frames zu kommunizieren, ist natürlich auch ideal dafür, Daten zwischen Frames zu synchronisieren. Übertragen auf zwei verschiedene Präsenationsframes ermöglicht dies beim Weiterschalten der Folien in einem Frame, den zweiten Frame zu aktualisieren. Schematisch sieht das so aus:
Im Hauptfenster kann per Tastendruck ein zusätzliches Popup geöffnet werden (1). Das neue Popup öffnet die gleiche URI der Präsentation und wird auf dem Bildschirm des Vortragenden platziert. Schaltet der Vortragende nun im Hauptfenster weiter zu nächsten Folie, so erzeugt dies ein Nachricht an das zweite Frame (2), das dann ebenfalls weiterschaltet.
Eine weitere Ergänzung war die Unterstützung von Notizen als Overlay über die Folien. Kombiniert mit dem Dual-Screen-Ansatz ermöglicht dies, dem Publikum die Folien zu zeigen, dem Vortragenden auf einem zweiten Bildschirm die Folien plus den verfügbaren Notizen.
Ausführliche Beispiele mit Code gibt es in einer Beispielpräsentation, den kompletten Code auf github: https://github.com/berb/html5slides-uulm
Auf der Feature Wishlist steht noch ein alternativer CSS-Stylesheet für den Druckexport. Außerdem ein Tool, dass externe Daten wie Bilder per Base64 encoding als Data URI integriert und JavaScript sowie Stylesheets inline einbindet, sodass die Präsentation als einzelne HTML5 Datei ohne externe Abhängigkeiten abgespeichert werden kann.
]]>Als ansatzweise reales Szenario hierfür dient die Mensa. Zu Stoßzeiten ist es häufig schwierig, in einer größeren Gruppe gemeinsam zu essen. Spätestens an den Kassen teilt sich die Gruppe auf und es ist schwierig, die Anderen zu finden. Ein Teil sitzt vielleicht auch schon an irgendeinem Tisch, während andere immer noch an der Essensausgabe warten. Was man also unbedingt braucht, ist ein Mensafinder. Der Mensafinder ermöglicht es, anderen seine grobe Position in der Mensa mitzuteilen oder aufzuzeigen, wo andere Leute sitzen. Aufgrund der Einschränkungen vor Ort (kein GPS-Empfang, WLan-Ortung zu ungenau) haben wir uns auf eine einzige Kontextinformationen beschränkt, die bereits eine ausreichende Lösung bietet – die Kompassausrichtung. Anstatt die genaue Position zu ermitteln, verwenden wir eine grobe Richtung abhängig von einem Fixpunkt im Zentrum des Raums (Wendeltreppe). Bereits sitzende Personen richten ihr Mobilgerät in Richtung des Fixpunktes aus, suchende Personen können ausgehend vom Fixpunkt den Richtungen folgen.
Technisch besteht der Mensafinder aus einem Webservice und mobilen Anwendungen. Der Webservice basiert auf REST und bietet als besonderes Feature das ‘Streamen’ neuer Events (neue/aktualisierte Peilungen oder Abmeldungen). Hierfür wird durch den Client eine HTTP-Verbindung geöffnet und serverseitig nicht direkt geschlossen. Stattdessen werden neue Events via Chunked Encoding in die offene Verbindung geschrieben, ähnlich wie bei Streaming API von Twitter. Der Service wurde mit node.js implementiert, Quellcode sowie Dokumentation der REST API sind auf github verfügbar.
Da es sich nur um eine Demo-Applikation handelt, fehlen einige wichtige Features. Es gibt keine Authentisierung der Benutzer und es werden alle Peilungen aller Benutzer übertragen (es gibt keine Kontaktlisten). Interessant wäre natürlich die Anbindung an bestehende Dienste, die bereits eine Authentisierung und Kontaktlisten bereitstellen, so wie beispielsweise Facebook Connect.
]]>Bei interaktiven Anwendungen, die Inhalte aus dem Internet laden oder komplexe Berechnungen durchführen, ist es besonders wichtig, die Benutzerschnittstelle durch diese Aktionen nicht vollständig zu blockieren. So können zum Beispiel HTTP Requests in mobilen Netzwerken mehrere Sekunden benötigen – die Anwendung sollte trotzdem benutzbar bleiben oder zumindest dem Anwender über den Ladevorgang informieren.
Wie auch bei Desktop-Applikationen liegt hierbei der Schlüssel im Umgang mit Nebenläufigkeit. So wie es in gewöhnlichen Java-Anwendungen mit GUI einen AWT-Thread gibt, der mit einer Event-Queue auf GUI-Ereignisse wartet und diese verarbeitet, gibt es auch in Java ME ein ähnliches Konstrukt für die Benutzerschnittstelle. Wichtig ist es nun, länger dauernde Operationen nicht in dem jeweiligen Thread auszuführen, sondern in einem separaten Thread. So können auch weiterhin GUI-Ereignisse abgefangen werden, auch wenn die Operation noch andauert. Die eigentliche Aufgabe – I/O Operationen, Berechnungen etc. – wird in einem eigenen Thread oder einem Pool von Threads durchgeführt. Die Aktion wird aus dem GUI-Thread heraus gestartet (z.B. nach dem Click auf einem Button), allerdings eben nicht im GUI-Thread, sondern separat. Somit wird das Starten der Operation asynchron und somit nicht blockierend durchgeführt, und auch mit dem Resultat sollte ähnlich umgegangen werden. Das Ergebnis der Operation sendet nach Bearbeitung das Ergebnis wiederum an den GUI-Thread, der dies dann darstellt. Für den letzteren Fall gibt es bereits ein fertige Methode: javax.microedition.lcdui.Display.callSerially(Runnable r)
. Diese Methode reiht das angegebene Runnable
an das Ende der Event-Queue ein. Der GUI-Thread arbeitet wiederum die Events und Runnables
der Reihe nach ab.
Für das Absenden von Hintergrundaktionen aus dem GUI-Thread bietet sich ein ähnliches Vorgehen an: Mithilfe einer blockierenden Queue sollten neue Runnable
s angelegt und eingetragen werden. Ein Thread oder ein Pool von Threads sollte nun aus der Queue lesen und die dortigen Runnable
s ausführen. Am Ende der run()
-Methode jedes Runnable
s sollte nun das Resultat der Aufgabe wieder an die GUI zurückgegeben werden. Hierfür ist nun die Display.callSerially(Runnable r)
hilfreich.
Die folgende Grafik illustriert das grundsätzliche Vorgehen:
Projekte:
Weitere Informationen gibt es unter: http://www.uni-ulm.de/in/mi/highlights.html
]]>require('net').createServer(function(s){require('util').pump(s,s);}).listen(8000);
Und nein, das ist kein Perl ;-)
]]>
diretto ))) [Benjamin Erb, Christian Koch, Stefan Kaufmann]