Ausarbeitung - Universität Münster

Werbung
Westfälische Wilhelms-Universität Münster
Ausarbeitung
Grundlagen der MIDlet-Programmierung
im Rahmen des Seminars: „Mobile Java“
Claus Alexander Usener
Themensteller: Prof. Dr. Herbert Kuchen
Betreuer: Dipl.-Wirt.Inform. Michael Poldner
Institut für Wirtschaftsinformatik
Praktische Informatik in der Wirtschaft
Inhaltsverzeichnis
1
Einleitung................................................................................................................... 3
2
Programmierwerkzeuge............................................................................................. 4
3
2.1
Java ME Wireless Toolkit................................................................................. 4
2.2
Eclipse ME Plugin ............................................................................................ 5
2.3
Installation auf einem Endgerät ........................................................................ 5
Grundlagen der MIDlet-Programmierung ................................................................. 6
3.1
MIDlet und MIDlet-Suite ................................................................................. 6
3.2
Der MIDlet-Lebenszyklus ................................................................................ 7
3.2.1
3.2.2
4
Die MIDlet-Zustände.................................................................................... 7
Die MIDlet-Zustandsübergänge ................................................................... 9
GUI-Programmierung.............................................................................................. 12
4.1
Die Klasse Display.......................................................................................... 13
4.2
Kommandos .................................................................................................... 15
4.3
High-Level-Display-Komponenten ................................................................ 16
4.3.1
4.3.2
4.3.3
4.3.4
Alert ............................................................................................................ 16
List .............................................................................................................. 17
TextBox ...................................................................................................... 18
Form............................................................................................................ 18
5
Zusammenfassung ................................................................................................... 21
A
MIDlet ErstesMIDlet ............................................................................................... 22
B
MIDlet SkiGuide ..................................................................................................... 24
Literaturverzeichnis ........................................................................................................ 30
II
Kapitel 1: Einleitung
1 Einleitung
Schon vor 3 Jahren zeichnete sich ein Trend auf dem Handy-Markt ab: viele Mobiltelefone wurden mit einer Java Kilobyte Virtual Maschine (KVM) ausgestattet um für Java2 Micro Edition – Anwendungen gerüstet zu sein.
Warum nun Java2 Micro Edition (J2ME) und nicht Java2 Standard Edition(J2SE)? Die
Java2 Micro Edition ist speziell für Mobile Endgeräte entwickelt worden. Ob Mobiltelefon, Personal Digital Assistant (PDA) oder eine Mischform wie der Mobile Digital Assistant (MDA), alle drei haben eines gemeinsam: Sie sind für den mobilen Markt konzipiert. Sie sollen kompakt und handlich sein. Damit sie nicht ständig aufgeladen werden
müssen, sollen sie zudem noch wenig Energie verbrauchen. Diese Eigenschaften gehen
jedoch mit Abstrichen in Punkt Leistung einher. So besitzt ein modernes Mobiltelefon
nur einen sehr kleinen Prozessor (meistens wenige 100 MHz) und nur einen geringen
Speicher (ab 4 MB aufwärts).
J2ME ist genau für diese Art von Geräten mit wenig Leistung und geringen Ressourcen
entwickelt worden. Bei J2ME selbst handelt es sich nur um eine Menge von Spezifikationen. Die J2ME Architektur basiert auf Konfigurationen, die um bestimmte Profile
erweitert werden. Eine Konfiguration hält in Verbindung mit einem Profil eine bestimmte Anzahl von Application Programming Interfaces (APIs) bereit. Die Anzahl der
J2ME-APIs ist jedoch wegen der beschränkten Ressourcen im Vergleich zu den J2SEAPIs sehr gering.
Anwendungen für das Mobile Information Device Profile (MIDP) wurden in Anlehnung an die Applets der J2SE von den Sun Entwicklern MIDlets getauft.
Diese Seminararbeit führt in die Grundlagen der MIDlet-Programmierung ein und beschränkt sich dabei auf das Programmieren in der Konfiguration CLCD1.1 und in dem
Profil MIDP 2.0.
Die Arbeit ist in drei Kapitel gegliedert. Das erste Kapitel Programmierwerkzeuge geht
hierbei auf die notwendigen bzw. hilfreichen Programmierumgebungen ein. Das Kapitel
Grundlagen der MIDlet-Programmierung gibt einen Überblick über alle notwendigen
Methoden zur Steuerung der möglichen Zustände, die von einem MIDlet angenommen
werden können. Das letzte Kapitel GUI-Programmierung legt die Grundlagen für die
Erstellung von grafischen Benutzerschnittstellen.
3
Kapitel 2: Programmierwerkzeuge
Ziel ist es, mit dieser Arbeit eine Anleitung an die Hand zu geben, mit der jeder, der
schon J2SE-Programme geschrieben hat, in die Lage versetzt wird, kleine MIDlets
selbst zu programmieren.
2 Programmierwerkzeuge
Voraussetzung zur Erstellung von MIDlets ist eine Installation des Java 2 Development
Kit [JDK06], da für die Entwicklung mindestens der Compiler javac, der Interpreter
java sowie das Archivprogramm jar notwendig sind.
Zusätzlich wird mindestens die MIDP-Referenzimplementierung [MR06] benötigt. Es
enthält Klassenbibliotheken und Werkzeuge um MIDlets anzeigen, ausführen und debuggen zu können. Der große Nachteil der MIDP-RI besteht jedoch darin, dass sämtliche Aufrufe über die Eingabekonsole ausgeführt werden müssen. Die MIDP-RI stellt
nur die Funktionen zur Verfügung, jedoch keine grafische Benutzerschnittstelle. (vergl.
Sc04) Auch wichtige Dateien wie das Manifest oder der Applikationsdeskriptor müssen
vom Programmierer von Hand erstellt und in das MIDlet eingebunden werden.
Mit den oben genannten Installationen sind zwar die Grundlagen für die Erstellung von
MIDlets gelegt. Es empfiehlt sich jedoch aus Gründen der Praktikabilität an Stelle der
MIDP-RI das Java ME Wireless Toolkit zu verwenden.
2.1 Java ME Wireless Toolkit
„You write the source code and the toolkit takes care of the rest“ [UG04 S. 3]. Das Java
ME Wireless Toolkit (WTK) bietet dem Anwender eine grafische Benutzerschnittstelle,
mit der sämtliche Vorgänge rund um die Entwicklung von MIDlets schnell und benutzerfreundlich handhaben kann.
Hierbei unterstützt das WTK bei dem Anlegen des Projekts, dem Übersetzen, der Verifizierung und dem Starten der Anwendung auf dem mitgelieferten Emulator. Selbst ein
Debugger wird bei dem WTK mitgeliefert.
Eine ausführliche Installationsanleitung ist in [BM06] im Kapitel Erste Schritte zu finden.
4
Kapitel 2: Programmierwerkzeuge
2.2 Eclipse ME Plugin
Da das WTK ohne Editor installiert wird, empfiehlt es sich zum komfortablen Programmieren den Editor Eclipse [Ec06a] um das Plugin EclipseME [Ec06b] zu erweitern. Damit wird eine integrierte Möglichkeit zur Konstruktion von Java MEAnwendungen angeboten.
EclipseME bietet die wichtigsten Eigenschaften (Projekte anlegen, compilieren emulieren..), die auch von dem WTK bereitgestellt werden. Da EclipseME jedoch in die Entwicklungsumgebung Eclipse eingebunden wird, ermöglicht es zusätzlich das Entwickeln von J2ME-Anwendungen, wie man es von J2SE-Anwendungen gewöhnt ist.
Eine ausführliche Installationsanleitung ist unter [Ec06b] zu finden.
2.3 Installation auf einem Endgerät
Die Installation eines MIDlets auf dem mobilen Endgerät kann auf zwei Arten geschehen. Zum Einen über die „over-the-air“-Schnittstelle (OTA) oder zum Anderen über
den PC.
Zuvor muss das MIDlet jedoch zu einem jar-Archiv (vergl. hierzu Kapitel 3.1) paketiert
werden. Hierzu stellen die oben genannten Tools die passenden Funktionen bereit.
Damit das MIDlet über die OTA-Schnittstelle installiert werden kann, wird es erst auf
einen Web-Server übertragen, um es dann über den Browser des mobilen Endgerätes
herunterzuladen. Danach kann es durch eine Bestätigung des Benutzers installiert werden.
Soll das MIDlet über den PC übertragen werden, so kann es entweder drahtlos per Bluetooth bzw. Infrarot oder aber über ein USB-Kabel übertragen werden. Fast alle Hersteller bieten zu diesem Zweck eigene Software an. Nach der Übertragung wird auch in
diesem Fall der Benutzer noch einmal aufgefordert, die Installation zu bestätigen, bevor
das MIDlet von dem mobilen Endgerät selbständig installiert wird.
5
Kapitel 3: Grundlagen der MIDlet-Programmierung
3 Grundlagen der MIDlet-Programmierung
3.1 MIDlet und MIDlet-Suite
Ein MIDlet besteht aus mindestens einer Klasse und ist dafür bestimmt, auf einem Mobile Information Device Profile (MIDP) ausgeführt zu werden. Hierzu muss eine Klasse
des MIDlets auf der abstrakten Klasse MIDlet basieren und erbt somit alle notwendigen
Methoden, so dass die Application Management Software (AMS) des mobilen Endgerätes das MIDlet steuern, also geordnet starten, pausieren und beenden kann. (vergl.
[Sc04])
Folgender Codeausschnitt zeigt, welche abstrakten Methoden die Klasse MIDlet bereitstellt und somit zu implementieren sind:
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
public class Example extends MIDlet {
//Konstruktor des MIDlets
public Example() {...}
//Methode, die von der AMS zum Beenden des MIDlets ausgeführt wird
protected void destroyApp(boolean arg0) throws
MIDletStateChangeException {...}
//Diese Methode wird ausgeführt wenn das MIDlet pausiert wird
protected void pauseApp() {...}
//Methode zum (Neu-)Starten des MIDlets
protected void startApp() throws MIDletStateChangeException {...}
}
Darüber hinaus ist es wichtig, dass in einem MIDlet nur Methoden verwendet werden,
die auch in den APIs des MIDP definiert sind. Methoden aus der J2SE werden von dem
MIDP nicht unterstützt.
Die kleinste auf einem MIDP installierbare Einheit nennt sich MIDlet-Suite. Eine Suite
besteht aus einem jar-Archiv sowie einer optionalen jad-Datei, dem so genannten Applikationsdeskriptor.
In dem Applikationsdeskriptor sind die Merkmale einer MIDlet-Suite gespeichert. Die
Merkmale beschreiben unter anderem, für welche Konfiguration die MIDlet-Suite geschrieben ist, welches Profil verwendet wird, die Versionsnummer der Suite und wie
groß die jar-Datei ist. Der Benutzer kann auch selbst definierte Merkmale hinzufügen.
Alle Merkmale können im Programm über die Methode getAppProperty(String
name) abgefragt werden. Da der Applikationsdeskritor bereits vor der Installation des
6
Kapitel 3: Grundlagen der MIDlet-Programmierung
Programms gelesen werden kann, ist es schon an dieser Stelle möglich, die Installation
Aufgrund der falschen Konfiguration oder des falschen Profils zu untersagen.
Im Gegensatz zum Applikationsdeskriptor enthält das jar-Archiv:
•
mindestens ein MIDlet
•
eventuell benötigte Ressourcen wie Bilder, Sound-Dateien und Hilfstexte
•
sowie einem Manifest: ähnlich dem Applikationsdeskriptor, jedoch erst nach
dem Entpacken des Archivs zugreifbar.
Der Applikationsdeskriptor sowie das jar-Archiv können automatisch durch das Wireless Toolkit generiert werden. Für weiterführende Informationen zum Applikationsdeskriptor und dem jar-Archiv sei auf [Sc04] und [BM06] verwiesen.
3.2 Der MIDlet-Lebenszyklus
Ähnlich einem Applet in der Java 2 Standard Edition hat auch ein MIDlet einen besonderen Lebenszyklus und ist deshalb nicht vergleichbar mit einem J2SE-Objekt. Ein
MIDlet wird nicht mit der Methode public static void main(String[]
args) gestartet und auch nicht mit der Methode System.exit(int status) been-
det. Diese Methoden werden von der Application Management Software nicht unterstützt.
Da ein MIDlet für ein mobiles Endgerät geschrieben wird, welches für gewöhnlich wenig Rechenleistung und Ressourcen zur Verfügung stellt, ist ein besonderer MIDletLebenszyklus entwickelt worden.
3.2.1 Die MIDlet-Zustände
Ein MIDlet kann immer nur einen der drei möglichen Zustände zerstört, pausierend
oder aktiv annehmen. Diese Einteilung in unterschiedliche Zustände ist notwendig, um
das MIDlet während der Ausführung situtationsbedingt (z. B. eingehendes Telefongespräch) unterbrechen zu können. In diesen Fall müssen dann notwendige Ressourcen,
wie Bildschirm und Tasten für die Anzeige und Annahme des Telefongesprächs, zur
Verfügung gestellt werden.
Der Zustandswechsel wird nicht immer von der AMS veranlasst; in den häufigsten Fällen wird er sogar vom MIDlet selbst initiiert.
7
Kapitel 3: Grundlagen der MIDlet-Programmierung
Abbildung 1 verdeutlicht, welche Methoden von der AMS und welche von dem MIDlet
aufgerufen werden, um zwischen den einzelnen Zuständen zu wechseln.
Abbildung 1: Zustandsübergänge eines MIDlets (in Anlehnung an [Sc04])
Dieser Zyklus besteht aus den oben genannten drei Zuständen:
•
Pausierend (paused): Dieser Zustand wird direkt nach der Initialisierung über
den parameterlosen Konstruktor angenommen. In diesem Zustand darf das MIDlet nur geringe Ressourcen halten. Alle nicht benötigten Ressourcen, wie Netzwerkverbindungen, große Objekte oder unwichtige Threads, müssen freigegeben
werden. Hierbei besteht die Möglichkeit, dass ein anderes MIDlet im Vordergrund läuft und die knappen Ressourcen benötigt. Hält in diesem Fall ein pausierendes MIDlet zu viele oder von anderen benötigte Ressourcen, kann es von der
AMS terminiert werden.
•
Aktiv (active): Beim Übergang in diesen Zustand fordert das MIDlet üblicherweise sämtliche Ressourcen an, die es beim Übergang in den Zustand pausierend freigegeben hat. In diesem Zustand läuft das MIDlet im Vordergrund und
hat Zugriff auf sämtliche freien Ressourcen wie Bildschirm, Tastatur und die
Rechenleistung.
•
Zerstört (destroyed): Dieser Zustand ist im Gegensatz zu den anderen beiden
endgültig. Alle zu einem späteren Zeitpunkt wichtigen Informationen müssen
vorher persistent gespeichert werden. Sämtliche vom MIDlet noch nicht freige-
8
Kapitel 3: Grundlagen der MIDlet-Programmierung
gebenen Ressourcen werden von dem Garbage Collector für andere Zwecke zur
Verfügung gestellt.
3.2.2 Die MIDlet-Zustandsübergänge
Man unterscheidet zwei Möglichkeiten um zwischen Zuständen zu wechseln: entweder
kann der Zustandswechsel von der AMS initiiert sein, oder aber das MIDlet veranlasst
selbst den Zustandsübergang.
Um AMS initiierte Zustandswechsel durchzuführen, muss das MIDlet die drei abstrakten Methoden startApp(), pauseApp() und destroyApp() aus der Superklasse
javax.microedition.midlet.MIDlet implementieren.
Soll der Zustandsübergang jedoch durch das MIDlet selbst veranlasst werden, so muss
das MIDlet der AMS dieses durch den Aufruf der jeweiligen final Methode
notifyPaused(), notifyDestroyed(boolean) oder resumeRequest() mittei-
len.
Zu beachten ist, dass bei einem Aufruf von resumeRequest() nicht, wie es bei den
anderen beiden Methoden der Fall ist, der Zustand direkt gewechselt wird. Ein Aufruf
dieser Methode bewirkt lediglich einen Vermerk bei der AMS, dass das MIDlet ausgeführt werden will. Diesem Wunsch kann die AMS zu gegebener Zeit nachkommen.
Das Beispiel-MIDlet (siehe Anhang A) verdeutlicht die wesentlichen Aufgaben der Methoden startApp(), pauseApp() und destroyApp().
Es läuft wie folgt ab: Nach dem Start wird auf dem Display des mobilen Endgeräts fortlaufend die schon vergangene Zeit seit dem Wechsel in den Zustand aktiv angezeigt.
Der Benutzer kann das MIDlet per Tastendruck in den Zustand pausierend wechseln
oder das MIDlet beenden. Ist das MIDlet in dem Zustand pausierend, versucht es
selbstständig nach einer festgelegten Zeit wieder in den Zustand aktiv zu wechseln.
Zu Beginn eines MIDlet-Aufrufs werden die Variablen gebunden und danach der parameterlose Konstruktor ausgeführt, in dem einmalig durchzuführende Initialisierungen
durchgeführt werden können:
public class ErstesMIDlet extends MIDlet {
private
private
private
private
Form gui;
int wartezeit;
//Wiederaufwachintervall
Aktion aktion;
//Aktions-Thread zur Ausgabe der Zeit
boolean erstmalsGestartet=true;
9
Kapitel 3: Grundlagen der MIDlet-Programmierung
public ErstesMIDlet() {
//Einlesen und Speichern des Attributs Wartezeit aus dem Manifest.
String wartezeit=getAppProperty("Wartezeit");
this.wartezeit=Integer.parseInt(wartezeit)*1000;
//Anlegen einer Bildschirmausgabe mit Kommando Ende und Pause
GUIerstellen();
}
Nach dem Konstruktoraufruf ist ein Objekt des aktuellen MIDlets erzeugt worden. Das
MIDlet wechselt nun in den Zustand pausierend, deshalb sollten im Konstruktor noch
keine Ressourcen angefordert oder große Objekte erzeugt werden.
Zum Starten des MIDlets wird von der AMS die Methode startApp() ausgeführt, um
alle notwendigen Ressourcen anzufordern und das eigentliche Programm zu starten.
Falls benötigte Ressourcen noch anderweitig genutzt, aber in absehbarer Zeit frei werden (transient error), kann eine StateChangeException ausgelöst werden. Das MIDlet
verbleibt dann im Zustand pausierend.
Der eigentliche Programmcode (im Bsp. der Thread aktion) sollte immer in einen gesonderten Thread ausgelagert werden, da sonst die Methode startApp() unnötig lange ausgeführt wird und so den Zustandswechsel blockiert.
protected void startApp() throws MIDletStateChangeException {
if (erstmalsGestartet)
{
//erster Start des MIDlets
erstmalsGestartet=false;
print("1. Start des MIDlets");
}
else
{
//erneuter Start des MIDlets
gui.deleteAll();
print("MIDlet aufgewacht");
}
Display.getDisplay(this).setCurrent(gui); //Ressourcen anfordern
synchronized (this)
//Programmthread starten
{
aktion=new Aktion();
aktion.start();
}
}
Ist das MIDlet im Zustand aktiv, so kann es entweder von der AMS oder von dem MIDlet selbst in den Zustand pausierend gesetzt werden.
Ersteres führt zur Ausführung der Methode pauseApp(). In dieser Methode sollten
alle nicht benötigten Ressourcen freigegeben werden, um sie anderen Prozessen zur
Verfügung zu stellen. Diese Art des Zustandswechsels kann eintreten, wenn ein Anruf
auf dem mobilen Endgerät eintrifft.
protected void pauseApp() {
synchronized (this) { aktion=null; } //Löschen des Threads
new Timer().schedule(new TimerTask() {public void run()
{resumeRequest();}}, wartezeit);
//Versuch das MIDlet nach
"wartezeit" Sekunden wieder neu zu starten
10
Kapitel 3: Grundlagen der MIDlet-Programmierung
}
Ein gängiges Verfahren, den laufenden Thread zu unterbrechen, wird in der run()Methode verdeutlicht. Durch die Realisierung als innere Klasse hat der Thread Zugriff
auf die Instanzvariable aktion, der umgebenden Klasse ErstesMIDlet. Die Schleife
der run()-Methode wird solange ausgeführt, wie der durch aktion referenzierte
Thread mit dem aktuell ablaufenden Thread identisch ist. Andernfalls wird die Schleife
verlassen und der Thread beendet.
Ein Nachteil dieser Methode ist darin zu sehen, dass der Thread erst kurz nach dem Zustandswechsel terminieren kann. Das tritt dann ein, wenn der Schleifenkopf erst nach
dem Zustandswechsel wieder erreicht wird.
Eine weitere Möglichkeit besteht darin, den Thread durch den Aufruf von
Thread.interrupt() zu terminieren, jedoch können mit dieser Handhabung eventu-
ell kritische Passagen in der Methode unterbrochen werden.
private class Aktion extends Thread
{
public void run()
{
long startzeit=new Date().getTime();
while(aktion==this)
{
print("aktive Zeit: "+ (new Date().getTime()-startzeit)/
1000 +"s");
try {sleep(1000);} catch (Exception e) {}
}
}
}
Veranlasst das MIDlet selbst den Zustandsübergang, indem das Programm dem Benutzer ermöglicht durch eine Interaktion in den Zustand pausierend zu wechseln, dann
muss dieser Zustandswechsel der AMS durch den Methodenaufruf notifyPaused()
mitgeteilt werden. Sämtliche nicht mehr benötigten Ressourcen müssen vorher vom
MIDlet freigegeben werden.
In dem Beispiel-MIDlet wird durch eine Benutzerinteraktion der Zustandswechsel
durchgeführt, indem die Methode pausieren() aufgerufen wird:
private void pausieren()
{
pauseApp();
//Freigabe der Ressourcen
notifyPaused(); //Mitteilung des Zustandswechsels an die AMS
}
Auch der Zustand zerstört kann durch das MIDlet oder die AMS initiiert werden. Ist sie
AMS initiiert, so wird nur die Methode destroyApp(boolean unconditional)
ausgeführt. Der Parameter unconditional gibt dabei an, wie in dem Fall verfahren
werden soll, dass wichtige Aktionen, wie eine nicht vollendete Datenübertragung, ablaufen.
Hat
unconditional
den
Wert
false,
so
wird
eine
11
Kapitel 4: GUI-Programmierung
MIDletStateChangeException zurückgegeben und der Zustand bleibt unverändert.
In allen anderen Fällen wird das MIDlet ohne Rücksicht auf laufende Prozesse beendet.
protected void destroyApp(boolean arg0)
throws MIDletStateChangeException {
synchronized (this) {
aktion=null;
}
gui.append("Adiöööö");
}
Veranlasst das MIDlet selbst den Zustandswechsel, so muss es selbst die „Aufräumarbeiten“ übernehmen und danach die Methode notifyDestroyed() aufrufen, um daraufhin in den Zustand zerstört zu wechseln. Im Beispiel-MIDlet wird dieses durch Aufruf der Methode schließen() nach einer Benutzerinteraktion veranlasst:
private void schließen()
{
try {
destroyApp(false); //Freigabe der Ressourcen
notifyDestroyed(); //Mitteilung des Zustandswechsels an die AMS
}
catch (MIDletStateChangeException e) {}
}
Es gibt noch eine weitere Methode, durch die das MIDlet in den eigenen Lebenszyklus
eingreifen kann. Befindet sich das MIDlet im Zustand pausierend, so kann es der AMS
durch den Aufruf von resumeRequest() mitteilen, dass es wieder in den aktiven Status wechseln will. Die AMS wartet nun so lange, bis eine Aktivierung des MIDlets in
Frage kommt und ruft daraufhin die Methode startApp() auf. Für das MIDlet gibt es,
abgesehen von dieser Methode, keine andere Möglichkeit, in den Zustand aktiv zu
wechseln.
Zu erwähnen bleibt, dass es sich bei den oben erwähnten Methoden notifyPaused(),
notifyDestroyed() und resumeRequest() um final Methoden handelt, die von
dem Programmierer nicht umgeschrieben werden können.
4 GUI-Programmierung
Mobile Informationssysteme unterscheiden sich von Desktopsystemen in vielerlei Hinsicht; besonders hervorzuheben ist die Interaktion mit dem Benutzer. Das Display eines
mobilen Endgerätes ist vergleichsweise klein. Oftmals ist nur eine Tastatur mit einem
Bruchteil der Tasten einer normalen Desktoptastatur vorhanden, ganz abgesehen von
einer fehlenden Maus, die allenfalls durch einen Stift ersetzt wird. Hinzu kommt noch,
dass die Bildschirme der mobilen Endgeräte oft unterschiedliche Größen haben.
12
Kapitel 4: GUI-Programmierung
Um diese Nachteile zu kompensieren ist das Package javax.microedition.lcdui
entwickelt worden. Es stellt dem Programmierer Schnittstellen bereit, Ausgaben auf
dem Bildschirm anzuzeigen und Eingaben über die Tastatur entgegen zu nehmen.
Das Package javax.microedition.lcdui umfasst zwei Ausprägungen der LCDUI:
das High-Level-API sowie das Low-Level-API.
Das High-Level-API ist für „business“-Anwendungen entwickelt worden. Portabilität
zwischen den Endgeräten ist für diese Anwendungen besonders wichtig. Um diese Portabilität gewährleisten zu können, stellt das High-Level-API dem Programmierer nur
wenig Kontrolle über das „look and feel“ seiner Anwendungen zur Verfügung. Eine
visuelle Gestaltung der Komponenten ist nicht möglich. Die Komponenten selbst besitzen schon vorgefertigte Fähigkeiten, wie Navigation oder Scrollen. Das Abfragen von
konkreten Tasten (Taste gedrückt bzw. losgelassen) ist ebenso wenig möglich wie das
pixelgenaue Setzen der Komponenten.
Das Low-Level-API hingegen ist für Anwendungen konzipiert, bei denen die Komponenten pixelgenau gesetzt werden müssen und Eingaben direkt entgegen genommen
werden sollen. Anwendungen müssen somit genauer auf das Endgerät abgestimmt werden und sind deshalb in der Portabilität eingeschränkt. Anwendung findet das LowLevel-API bei der Entwicklung von Spielen. Da diese Seminararbeit nur auf die HighLevel-API eingeht, und nur im begrenzten Umfang auf die GUI-Programmierung eingehen kann, wird für die Low-Level-API auf [Sc04] und [API06] verwiesen.
4.1 Die Klasse Display
Über die Klasse Display verwaltet das MIDlet das Display und die Eingabegeräte.
Hierzu wird jedem MIDlet genau ein Objekt der Klasse Display zugeordnet. Über die
Methode getDisplay() kann dieses Objekt für das MIDlet abgefragt werden. Als
Parameter wird das MIDlet selbst übergeben. Alle auf dem Display angezeigten Objekte
müssen vom Typ Displayable sein. Displayable ist die Superklasse der Klassen
Screen und Canvas.
13
Kapitel 4: GUI-Programmierung
Abbildung 2: Auszug aus dem Klassendiagramm des LCDUI (Quelle [To02])
Screen wiederum ist die Superklasse für alle Bildschirmobjekte der High-Level-APIs
und Canvas die Superklasse der Low-Level-APIs.
Um dem Display ein Objekt zu übergeben wird die Methode setCurrent() aufgerufen, der als Parameter ein Displayable Objekt übergeben wird.
Über die Methode setCurrent() können Objekte der Klasse Displayable auf dem
Bildschirm angezeigt werden. Objekte, die mit setCurrent() an die Display-Instanz
übergeben werden, zeigt der Bildschirm jedoch nur dann an, wenn das MIDlet, zu dem
die Display-Instanz gehört, auch im Vordergrund läuft.
Die Anzeige einer Textbox könnte wie folgt aufgerufen werden:
Display display;
protected void startApp() throws MIDletStateChangeException {
if(display==null) {
//erster Aufruf der startApp()
display=Display.getDisplay(this);
Displayable textbox=new TextBox("Hello World","Ich bin
eine TextBox mit 100 Zeichen",100,TextField.ANY);
display.setCurrent(textbox);
}
... }
Die Klasse Displayable ist die abstrakte Basisklasse für alle auf dem Bildschirm darstellbare Objekte und stellt die gemeinsamen Eigenschaften zusammen. So kann jedes
Objekt einen Titel, ein Laufband (engl. Ticker) sowie eine Vielzahl von Kommandos
haben.
14
Kapitel 4: GUI-Programmierung
4.2 Kommandos
Damit der Benutzer interaktiv Aktionen ausführen kann, werden den DisplayableKomponenten Command- und CommandListener-Objekte zugeordnet. Ein CommandObjekt beinhaltet dabei nur einen Namen, einen Typ sowie eine Priorität. Wird einer
Displayable-Instanz ein Kommando mit Hilfe der Methode addCommand() zuge-
ordnet, so werden bei der Anzeige dieser Displayable-Instanz die Softbuttons mit
den Kommandos belegt. Ist die Anzahl der Kommandos größer als die Anzahl der Softbuttons, so werden die Kommandos in einem Menü zusammengefasst.Die auszuführende Aktion ist im Command nicht enthalten. Sie wird in einem CommandListener gekapselt. Hierzu wird dem Displayable-Objekt der passende CommandListener mit
durch Methode setCommandListener() zugeordnet.
Die wichtigsten Command-Typen sind die folgenden:
Command-Typ
SCREEN
BACK
CANCEL
EXIT
ITEM
OK
STOP
Bedeutung
Bildschirmbezogenes Kommando, z.B. Laden, Speichern etc.
Rückkehr zum vorherigen Bildschirm
Dialog-Abbruch
Programm-Abbruch
Kommando für Elemente eines Bildschirms
Bestätigung durch den Benutzer
Abbruch der laufenden Operation
Tabelle 1: Command-Typen, vergl. [API06]
Ein Kommando zum Beenden eines Programms könnte dann wie folgt aussehen:
cmdExit=new Command("Beenden",Command.EXIT,1);
dispayable.addCommand(cmdExit);
dispayable.setCommandListener(new CommandListener(){
public void commandAction(Command c,Displayable d) {
if(c==cmdExit)
notifyDestroyed();
...
}});
Der letzte Parameter gibt die relative Priorität des Kommandos in Bezug auf die anderen auf dem Bildschirm angezeigten Kommandos an. Dabei stehen niedrige Werte für
eine hohe Priorität. Die Anordnung der Elemente auf dem Bildschirm kann durch den
Kommandotyp in Verbindung mit der Priorität beeinflusst werden. Die genaue Anordnung ist jedoch geräteabhängig.
15
Kapitel 4: GUI-Programmierung
4.3 High-Level-Display-Komponenten
Die High-Level-LCDUI stellt dem Programmierer vier Displayable-Klassen zur Verfügung:
•
Alert: weist den Benutzer auf besondere Umstände wie Fehler o. Ä. hin
•
List: bietet dem Benutzer eine Auswahlmöglichkeit
•
TextBox: ermöglicht dem Benutzer das Lesen und Editieren von Texten
•
Form: Beliebige Zusammenstellung von Bildern, Texteingabefeldern etc.
In der Anwendung SkiGuide aus dem Anhang B werden die nachfolgenden Erläuterungen anhand eines Beispiels verdeutlicht.
Das MIDlet bietet die Möglichkeit aus einem List-Objekt zwischen dem Anzeigen der
Ski-Regeln, dem Speichern der Urlaubsadresse oder dem Speichern von Aufgaben/
Hinweisen zu wählen. Dabei werden in den Untermenüs alle nachfolgenden Komponenten mindestens einmal verwendet um eine exemplarische Verwendung zu zeigen.
4.3.1 Alert
Um den Benutzer bestimmte Hinweise wie Fehler, Warnhinweise oder Bestätigungen
zu geben, stellt die High-Level-API die Klasse Alert bereit. Ein Alert beinhaltet einen
Titel, einen Hinweistext, möglicherweise ein Bild sowie einen der nachfolgenden AlertTypen. In Abhängigkeit vom Alert Typ kann das Verhalten und das Aussehen variieren.
Alert-Typ
ALARM
CONFIRMATION
ERROR
INFO
WARNING
Bedeutung
Benachrichtigung wie z. B. ein Wecker
Rückmeldung aufgrund einer vollendeten Aktion
Fehlermeldung
Informativer Hinweis
Warnhinweis
Tabelle 2: Alert-Typen, vergl. [API06]
Der Alert ist das einzige Objekt, dass nach der Anzeige selbstständig auf den nachfolgenden Bildschirm umschalten kann. Hierzu wird mit der Methode setTimeout() die
Anzahl
der
Millisekunden
als
Anzeigedauer
zugeordnet
und
der
Methode
display.setCurrent(alert, nextDisplayable) der nachfolgende Bildschirm
übergeben. Soll der Alert durch den Aufruf eines Kommandos beendet werden, so erhält die Methode setTimeout() den Parameter Alert.FOREVER.
16
Kapitel 4: GUI-Programmierung
Alert alert=new Alert("Hinweis","Das MIDlet ist das erste Mal
gestartet worden",null /*kein Image*/,AlertType.INFO);
alert.setTimeout(5000);
//Anzeigedauer des Alerts
display.setCurrent(alert,textbox); //zuerst
den
Alert
anzeigen,
Nachfolgebildschirm definieren
alert.getType().playSound(display); //Alerttyp-spezifischen
Sound
abspielen
Wird dem Befehl setCurrent() nur das Alert-Objekt übergeben, so erscheint anschließend der vorherige Bildschirm erneut angezeigt.
4.3.2 List
List repräsentiert eine Liste von Einträgen, die jeweils aus einem Text und einem opti-
onalen Image bestehen. Aus dieser Liste kann der Benutzer je nach Listentyp einen
oder mehrere Einträge selektieren. Man unterscheidet die Listentypen IMPLICIT,
MULTIPLE und EXCLUSIVE.
Mit dem Aufruf list=new List(„Titel“,List.IMPLICIT); wird eine Liste erzeugt, bei der die Selektion eines Listenelements eine frei programmierbare Aktion auslöst. Hierzu wird der Liste mit Hilfe der Methode setSelectCommand() ein
Command-Objekt zugeordnet. Im CommandListener wird nach der Selektion eines
Elements mit der Methode list.getSelectedIndex() der aktuell markierte Index
abgefragt und daraufhin die zugehörige Aktion ausgeführt.
Bei der MULTIPLE-List wird dem Benutzer ermöglicht, mehrere Elemente z. B. mit
Hilfe einer CheckBox zu selektieren. Die Abfrage der selektierten Elemente geschieht
hierbei über die Methode list.isSelected(index), wobei index den Index zwischen 0 und list.size()-1 definiert.
Die EXCLUSIVE-List ist ähnlich der MULTIPLE-List, erlaubt allerdings nicht die mehrfache Selektion innerhalb einer Liste. Dem Benutzer wird z. B. durch „Radio Buttons“
nur eine Selektion erlaubt. Um der Liste Elemente anzufügen wird die Methode
list.append() verwendet und als Parameter der Name sowie ein optionales Image-
Objekt übergeben:
list=new List("Lieblingseis",List.MULTIPLE); //erzeuge Liste
list.append("Zitroneneis", imgZitrone);
//füge Elemente an
list.append("Vanilleeis", null /*kein Bild*/);
list.append("Himbeereis", imgHimbeere);
list.setSelectedIndex(1, true);
display.setCurrent(list);
17
Kapitel 4: GUI-Programmierung
4.3.3 TextBox
Die Textbox ist ein Bildschirm, der dem Benutzer ermöglicht einen Text einzugeben
und/oder zu editieren. Der Text darf eine vorher maximal definierte Länge nicht überschreiten. Für den Fall, dass der Text nicht vollständig auf dem Bildschirm angezeigt
werden kann, wird die Textbox automatisch um einen Scrollbalken ergänzt.
Um syntaktische Fehleingaben möglichst gering zu halten, besteht die Möglichkeit, die
Eingabe durch Konstanten zu beschränken.
TextField-Konstante
ANY
EMAILADDR
NUMERIC
URL
PHONENUMBER
Bedeutung
keine Beschränkung
nur eine E-Mailadresse ist erlaubt
nur Ziffern und Minuszeichen sind erlaubt
ULR bzw. URI ist erlaubt
Ziffern und ggf. „+“ und „-“
Tabelle 3: TextField-Konstanten, vergl. [API06]
Die Textbox könnte wie folgt initialisiert werden:
TextBox
textBox=new
TextBox("Titel","Inhalt",100
/*maximale
Länge des Inhalts*/,TextField.ANY);
String neuerText="Dem Textfeld wird ein neuer Inhalt übergeben";
if(textBox.getMaxSize()>neuerText.length());
textBox.setString("neuer Text");
In diesem Beispiel wird der Inhalt der Textbox von dem Programm selbst geändert.
Hierbei ist zu beachten, dass die maximale Länge der Textbox nicht überschritten wird,
da es sonst zu einer IllegalArgumentException kommt.
4.3.4 Form
Form ist ein Bildschirm-Objekt mit dem mehrere Item-Objekte (Bilder, Texteingabefel-
der, Texte, Fortschrittsanzeigen…) gleichzeitig auf dem Bildschirm angezeigt werden
können. Falls der Bildschirm zu klein ist, wird das Form-Objekt automatisch um einen
Scrollbalken ergänzt.
Auf die Anordnung der Items innerhalb der Form hat der Programmierer nur geringen
Einfluss. Maßgeblich wird die Anordnung durch die Reihenfolge bestimmt, in der die
Items der Form zugewiesen werden. Die Items werden dann zeilenweise von links oben
nach rechts unten in die Form eingefügt. Jedoch besteht die Möglichkeit, den Abstand
der Items über ein Spacer-Objekt zu steuern. Dies wird durch den Konstruktoraufruf
Spacer(mindHöhe,mindBreite)
erzeugt und der Form mit dem Befehl
18
Kapitel 4: GUI-Programmierung
form.append(spacer) angefügt. mindHöhe bzw. minBreite definieren hierbei
den Mindestabstand in Pixel zum nachfolgenden Item-Element der Form.
StringItem
Der Zweck des StringItems liegt im Anfügen von String-Objekten an ein Form-Objekt.
Grundsätzlich besteht das StringItem aus einem Label und einem Text. Das Label ist
optional und dem Text vorangestellt. Um es vom Text abzusetzen, wird es vom WTKEmulator in Fett-Schrift dargestellt.
Erzeugt wird das StringItem durch den Konstruktoraufruf StringItem(label,
text). Ist kein Label erwünscht, so kann auch der Wert null übergeben werden. Die-
ses führt jedoch zu demselben Ergebnis wie der Aufruf der überladenen Methode
form.append(String).
TextField
Das TextField ist vergleichbar mit der TextBox und dient dem Benutzer als editierbares Textelement. Im Gegensatz zu TextBox wird es jedoch der Form angefügt. Als
Parameter erhält der Konstruktor einen Titel, einen anfänglichen Text oder null, eine
maximale Zeichenanzahl sowie einen Zeichen-Constraint:
textfield=new TextField("Titel","Inhalt",30,TextField.ANY);
form.append(textfield);
ImageItem
Die Programmierschnittstelle Image bietet die Möglichkeit, Grafiken im Portable Network Graphics – Format (PNG) zu repräsentieren. Dabei wird die Grafik mittels der
Methode Image.createImage(“\Skifahrer.png“) in das Programm geladen und
in einem Image-Objekt gespeichert. Ist das Image erstmal in das Programm geladen,
kann es als ImageItem der Form übergeben werden. Hierzu wird der Konstruktor
ImageItem(“Titel“, image, Item.LAYOUT_CENTER, “Alternativtext“)
aufgerufen und das resultierende Objekt an die Form übergeben.
ChoiceGroup
Die ChoiceGroup repräsentiert eine Gruppe von auswählbaren Einträgen. Die Funktionalität ist weitgehend mit der List identisch. Neben den von List bekannten Listenarten IMPLICIT, MULTIPLE und EXCLUSIVE bietet die ChoiceGroup mit POPUP noch
eine zusätzliche Variante. POPUP ist vergleichbar mit einer DropDown-Auswahl bei der
19
Kapitel 4: GUI-Programmierung
nur das aktuell ausgewählte Element angezeigt wird. Die weiteren Elemente sind jedoch
über ein Popup-Menü auswählbar.
Erzeugt
wird
die
POPUP-ChoicGroup
über
den
den
Konstruktor:
ChoiceGroup(„Titel“, ChoiceGroup.POPUP). Das Hinzufügen weiterer Ele-
mente wird über die Methode append(“Name“,image) realisiert, wobei image ein
optionaler Parameter ist.
20
Kapitel 5: Zusammenfassung
5 Zusammenfassung
In den vorangegangenen Kapiteln sind die wesentlichen Grundlagen der Programmierung mit JavaME erläutert worden. Zu Beginn wurden die zur Erstellung von MIDlets
notwendigen Umgebungen (JDK, MIDP-RI) und hilfreichen Tools (WTK, EclipseMEPlugin) kurz erläutert sowie die möglichen Wege zur Übertragung und Installation auf
dem mobilen Endgerät aufgezeigt.
Ein wichtiger Unterschied zu den aus dem JDK bekannten Objekten ist der besondere
Lebenszyklus von MIDlets, der sich aus den drei Zuständen pausierend, aktiv und zerstört ergibt. Auf diese Besonderheit konzentriert sich das Kapitel Grundlagen der MIDlet-Programmierung. Es wurde auf die für den Zustandswechsel wichtigen Methoden
eingegangen und erklärt, worauf bei der Implementierung zu achten ist. Verdeutlicht
werden diese Methoden an dem Beispiel ErstesMIDlet.
Bedingt durch die begrenzten Ressourcen der mobilen Endgeräte, sowohl der knappe
Speicher und die geringe Prozessorleistung, als auch die kleine Anzeigeeinheit und das
fehlende Zeigergerät unterscheiden sich die GUI-Objekte in ihrer Verwendung und ihrem Funktionsumfang von den GUI-Objekten, wie sie aus dem JDK bekannt sind. Aus
diesem Grund wurden in dem Kapitel GUI-Programmierung die High-Level-DisplayKomponenten als eine Möglichkeit der GUI-Erzeugung, vorgestellt. Die Verwendung
der Komponenten wird in dem MIDlet SkiGuide dargestellt.
Da es im Rahmen einer Seminararbeit nicht möglich ist, alle Einzelheiten zum Thema
MIDlet-Programmierung zu erwähnen, und sie vielmehr nur einen Überblick geben soll,
sei für weitere Informationen insbesondere auf die MID Profile Specification API sowie
auf die weiteren Quellen im Literaturverzeichnis verwiesen.
21
Anhang A: MIDlet ErstesMIDlet
A MIDlet ErstesMIDlet
import
import
import
import
import
import
javax.microedition.midlet.MIDlet;
javax.microedition.midlet.MIDletStateChangeException;
javax.microedition.lcdui.*;
java.util.Timer;
java.util.TimerTask;
java.util.Date;
public class ErstesMIDlet extends MIDlet {
private
private
private
private
Form gui;
int wartezeit;
//Wiederaufwachintervall
Aktion aktion;
//Aktions-Thread zur Ausgabe der Zeit
boolean erstmalsGestartet=true;
public ErstesMIDlet() {
//Einlesen und Speichern der Zeit bis zum resumeRequest
String wartezeit=getAppProperty("Wartezeit");
this.wartezeit=Integer.parseInt(wartezeit)*1000;
//Anlegen einer Bildschirmausgabe mit Kommando Ende und Pause
GUIerstellen();
}
private class Aktion extends Thread
{
public void run()
{
long startzeit=new Date().getTime();
while(aktion==this)
{
print("aktive
Zeit:
"+
(new
Date().getTime()startzeit)/1000+"s");
try {sleep(1000);} catch (Exception e) {}
}
}
}
protected void startApp() throws MIDletStateChangeException {
if (erstmalsGestartet) {
//erster Start des MIDlets
erstmalsGestartet=false;
print("1. Start des MIDlets");
}
else
{
//erneuter Start des MIDlets
gui.deleteAll();
print("MIDlet aufgewacht");
}
Display.getDisplay(this).setCurrent(gui);//Ressourcen anfordern
synchronized (this)
//Programmthread starten
{
aktion=new Aktion();
aktion.start();
}
}
protected void pauseApp() {
synchronized (this)
{ aktion=null; } //Löschen des Threads
new
Timer().schedule(new
TimerTask()
{public
void
run()
{resumeRequest();}}, wartezeit);
//Versuch
das
MIDlet
nach
"wartezeit" Sekunden wieder neu zu starten
}
private void pausieren() {
pauseApp();
//Freigabe der Ressourcen
notifyPaused(); //Mitteilung des Zustandswechsels an die AMS
}
Anhang A: MIDlet ErstesMIDlet
protected
void
destroyApp(boolean
MIDletStateChangeException {
synchronized (this)
{
aktion=null;
gui.append("Adiöööö");
}
arg0)
throws
}
private void schließen()
{
try {
destroyApp(false); //Freigabe der Ressourcen
notifyDestroyed(); //Mitteilung des Zustandswechsels an die
AMS
} catch (MIDletStateChangeException e) {}
}
private void GUIerstellen()
{
gui = new Form("Das erste MIDlet!");
gui.addCommand(new Command("ENDE", Command.EXIT,1));
gui.addCommand(new Command("Pause", Command.STOP ,1));
gui.setCommandListener(new KommandoListener());
gui.setTicker(new Ticker("ErstesMIDlet"));
}
// Funktion zur Ausgabe des String text auf der GUI
private void print(String text)
{
gui.append(text+"\n");
System.out.println(text);
}
// CommandListener der das Pausieren und Schließen behandelt
private class KommandoListener implements CommandListener
{
public void commandAction(Command c,Displayable d)
{
if(c.getCommandType()==Command.EXIT)
schließen();
else{
pausieren();
}
}
}
}
Anhang B: MIDlet SkiGuide
B MIDlet SkiGuide
import
import
import
import
import
java.io.InputStreamReader;
javax.microedition.midlet.MIDlet;
javax.microedition.midlet.MIDletStateChangeException;
javax.microedition.rms.RecordStore;
javax.microedition.lcdui.*;
public class SkiGuide extends MIDlet implements CommandListener{
Display display;
//Bildschirmobjekt
Image skifahrer=null;
//Exit-Command;
Command cmd_exit;
Command cmd_safe;
//Save-Command;
Command cmd_ok;
Command cmd_back;
Alert ale_welcome;
//Willkommensbildschirm
List lis_hauptmenü; //Hauptmenü
Form for_Adresse;
//Adresseingabebildschirm
StringItem si_info; //Komponenten des Adresseingabebildschirms
TextField tf_Name;
TextField tf_Adresse;
TextField tf_Ort;
TextField tf_TelNr;
ChoiceGroup cg_Unterkunftsart;
List lis_pistenregeln;
//1.Dimension Regelname, 2.Dimension Regeldetails
String[][] regeln={
{"Rücksicht auf die anderen Skifahrer","Jeder
Skifahrer muss sich so verhalten, dass er keinen anderen gefährdet oder schädigt."},
{"Beherrschung der Geschwindigkeit und der Fahrweise","Jeder Skifahrer
muss auf Sicht fahren. Er muss seine Geschwindigkeit und seine Fahrweise seinem Können und den Gelände-, Schnee- und Witterungsverhältnissen sowie der Verkehrsdichte anpassen."},
{"Wahl der Fahrspur","Der von hinten kommende Skifahrer muss seine
Fahrspur so wählen, dass er vor ihm fahrende Skifahrer nicht gefährdet."},
{"Überholen","Ueberholt werden darf von oben oder unten, von rechts
oder links, aber immer nur mit einem Abstand, der dem überholten Skifahrer für alle seine Bewegungen genügend Raum lässt."},
{"Einfahren und Anfahren", "Jeder Skifahrer, der in eine Skiabfahrt
einfahren oder nach einem Halt wieder anfahren will, muss sich nach
oben und unten vergewissern, dass er dies ohne Gefahr für sich und andere tun kann."},
{"Anhalten","Jeder Skifahrer muss es vermeiden, sich ohne Not an engen
oder unübersichtlichen Stellen einer Abfahrt aufzuhalten. Ein gestürzter Skifahrer muss eine solche Stelle so schnell wie möglich freimachen."},
{"Aufstieg und Abfahrt","Ein Skifahrer, der aufsteigt oder zu Fuß absteigt, muss den Rand der Abfahrt benutzen."},
{"Beachten der Zeichen","Jeder Skifahrer muss die Markierung und die
Signalisation beachten."},
{"Hilfeleistung","Bei Unfällen ist jeder Skifahrer zur Hilfeleistung
verpflichtet."},
Anhang B: MIDlet SkiGuide
{"Ausweispflicht","Jeder Skifahrer, ob Zeuge oder Beteiligter, ob verantwortlich oder nicht, muss im Falle eines Unfalles seine Personalien
angeben."}};
Form for_Regel; //Anzeige für Regeldetails
TextBox tb_sonstiges;
RecordStore rs=null;
public SkiGuide() {
cmd_back=new Command("Zurück","zum letzten Bild//Initialisierung der Kommandos
schirm",Command.BACK,2);
cmd_exit=new Command("Beenden","Programm beenden",Command.EXIT,1);
cmd_ok=new Command("OK","Auswahl",Command.OK,1);
cmd_safe=new Command("Speichern","Speichert die Eingabe",Command.OK,2);
try { //lese Image aus dem Stammordner /res ein.
skifahrer=Image.createImage("/Skifahrer.png");
} catch (Exception e) {
skifahrer=null; }
ale_welcome=new Alert("Skiguide","\n This is a skiers guide\n ->>
D o n ' t
P a n i c <<-",skifahrer,AlertType.INFO);
ale_welcome.setTimeout(3000); //setze Anzeigedauer des Alerts
}
protected void startApp() throws MIDletStateChangeException {
if(display==null)
{ display=Display.getDisplay(this); //Initialisierung
Displayobjekt
initialisieren();
//Initialisieren
der einzelnen Fenster
display.setCurrent(ale_welcome, lis_hauptmenü);
}
try{ //RecordStore Objekt erstellen/laden
rs=RecordStore.openRecordStore("SkiGuideDB",true);
} catch (Exception e) {}
laden(rs);
//Laden der Benutzerdaten, falls welche eingegeben
wurden
}
protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
//Ressourcen freigeben
try {
rs.closeRecordStore();
} catch (Exception e) {}
}
protected void pauseApp() {
//Ressourcen freigeben und ein Wiederaufnahmehinweis laden
try {
rs.closeRecordStore();
} catch (Exception e) {}
Alert pause=new Alert("Wilkommen zurück"," ->>W E I T E R
G E H
T S<<- ",skifahrer,AlertType.INFO);
pause.setTimeout(3000);
display.setCurrent(pause);
}
private void initialisieren() {
lis_hauptmenü=new List("Hauptmenü",List.IMPLICIT);
//Hauptmenü
initialisieren
Anhang B: MIDlet SkiGuide
lis_hauptmenü.setCommandListener(this);
//CommandListener für das Hauptmenü definieren
lis_hauptmenü.addCommand(cmd_exit);
//Kommandos hinzufügen
lis_hauptmenü.addCommand(cmd_ok);
lis_hauptmenü.setSelectCommand(cmd_ok);
lis_hauptmenü.append("Adresse der Unterkunft", null);
//Listenelemente anfügen
lis_hauptmenü.append("Pistenregeln", null);
lis_hauptmenü.append("Noch zu merken", null);
//Initialisierung der Form Adresse mit allen Komponenten
for_Adresse=new Form("Urlaubsadresse");
for_Adresse.setCommandListener(this);
for_Adresse.addCommand(cmd_back);
for_Adresse.addCommand(cmd_safe);
si_info=new StringItem(null/*kein Label*/,"Speichern Sie hier die
Adresse Ihrer Unterkunft ab, um für den Fall der Fälle vorbereitet zu
sein.");
for_Adresse.append(si_info);
for_Adresse.append(new Spacer(5,5));
cg_Unterkunftsart=new ChoiceGroup("Unterkunftsart:",ChoiceGroup.POPUP);
cg_Unterkunftsart.append("Hotel", null /*kein Image*/);
cg_Unterkunftsart.append("Ferienwohnung", null);
for_Adresse.append(new Spacer(5,5));
for_Adresse.append(cg_Unterkunftsart);
tf_Name=new TextField("Name:
","Unterkunftsname",30,TextField.ANY);
for_Adresse.append(tf_Name);
tf_Adresse=new TextField("Adresse: ","Straße,
Nr.",30,TextField.ANY);
for_Adresse.append(tf_Adresse);
tf_Ort=new TextField("PLZ, Ort:","12345 Musterstadt",40,TextField.ANY);
for_Adresse.append(tf_Ort);
tf_TelNr=new TextField("TelefonNr.:","0",20,TextField.PHONENUMBER);
for_Adresse.append(tf_TelNr);
//Initialisierung der Übersicht über die Pistenregeln
lis_pistenregeln=new List("Pistenregeln",List.IMPLICIT);
lis_pistenregeln.setCommandListener(this);
lis_pistenregeln.addCommand(cmd_back);
lis_pistenregeln.addCommand(cmd_ok);
lis_pistenregeln.setSelectCommand(cmd_ok);
for(int i=0;i<regeln.length;i++)
{ lis_pistenregeln.append(regeln[i][0], null);
}
//Fenster für die Erläuterung der Regel
for_Regel=new Form("1. Regel");
for_Regel.setCommandListener(this);
for_Regel.addCommand(cmd_back);
for_Regel.setTicker(new Ticker("Regeltext"));
for_Regel.append(new Spacer(for_Regel.getWidth(),5));
for_Regel.append(new ImageItem("",skifahrer,ImageItem.LAYOUT_CENTER,""));
Anhang B: MIDlet SkiGuide
for_Regel.append(new Spacer(0,for_Regel.getHeight()/2-5//Die detailierte Regel soll in der
skifahrer.getHeight()));
unteren Hälfte stehen
for_Regel.append(new StringItem(null,"Details"));
//Fenster für sonstige Informationen
tb_sonstiges=new TextBox("Nicht vergessen:","In diesem Textfeld
koennen Sie wichtige Informationen speichern!",300,TextField.ANY);
tb_sonstiges.setCommandListener(this);
tb_sonstiges.addCommand(cmd_back);
tb_sonstiges.addCommand(cmd_safe);
}
private void shutdown() throws MIDletStateChangeException
{ destroyApp(false);
notifyDestroyed();
}
public void commandAction(Command c, Displayable d) {
if (c==cmd_exit)
try {
shutdown();
} catch (MIDletStateChangeException e) {}
//Hauptmenuebefehle
else if (d==lis_hauptmenü&&(c==cmd_ok))
{
if(lis_hauptmenü.getString(lis_hauptmenü.getSelectedIndex()).equals(
"Adresse der Unterkunft"))
display.setCurrent(for_Adresse);
else if(lis_hauptmenü.getSelectedIndex()==1) //Skiregeln
sollen angezeigt werden
display.setCurrent(lis_pistenregeln);
else
display.setCurrent(tb_sonstiges);
}
//Adresseingabe-Befehle
else if(d==for_Adresse)
{ if(c==cmd_back)
{ display.setCurrent(lis_hauptmenü);
laden(rs);}
else if(c==cmd_safe)
{ speichern(rs);
commandAction(cmd_back, d);
}
}
//lis_pistenregeln-Befehle
else if(d==lis_pistenregeln)
{ if(c==cmd_back)
display.setCurrent(lis_hauptmenü);
else if(c==cmd_ok)
{ int ausgewählt=lis_pistenregeln.getSelectedIndex();
for_Regel.setTicker(new Ticker(regeln[ausgewählt][0]));
//Ticker setzen
for_Regel.setTitle(ausgewählt+1+". Regel");
((StringItem)for_Regel.get(3)).setText(regeln[ausgewählt][1]); //Text in der
Form setzen
display.setCurrent(for_Regel);
} }
Anhang B: MIDlet SkiGuide
//Befehl des Fensters für Regeldetails
else if(d==for_Regel)
display.setCurrent(lis_pistenregeln);
//Befehle für das Fenster "Noch zu merken"
else if(d==tb_sonstiges)
{ if(c==cmd_back)
{ display.setCurrent(lis_hauptmenü);
laden(rs);
}
else if(c==cmd_safe)
{ speichern(rs);
commandAction(cmd_back, d);
} } }
/**
* Die Funktion läd Daten aus der Datenbank.
*/
public void laden(RecordStore rs)
{ try
{
//ließt Daten aus der Datenbank
cg_Unterkunftsart.setSelectedIndex(Integer.parseInt(bytetoString(rs.
getRecord(1))), true);
tf_Name.setString(bytetoString(rs.getRecord(2)));
tf_Adresse.setString(bytetoString(rs.getRecord(3)));
tf_Ort.setString(bytetoString(rs.getRecord(4)));
tf_TelNr.setString(bytetoString(rs.getRecord(5)));
tb_sonstiges.setString(bytetoString(rs.getRecord(6)));
} catch(Exception e){}
}
/**
* Funktion castet jedes Byte eins byte-Array zu einem Character und
fügt es dem return String an.
* @param a Byte-Array, welches umgewandelt werden soll
* @return String der gecasteten Byteelemente
*/
public String bytetoString(byte[]a)
{ String b="";
for(int i=0;i<a.length;i++)
b+=(char)a[i];
return b;
}
public void speichern(RecordStore rs)
{ try
{ byte[] a;
if(rs.getNumRecords()==0) //falls noch kein Record gespeichert
wurde, erzeuge leere Records
for(int i=0;i<6;i++)
rs.addRecord(null,0,0);
a=(cg_Unterkunftsart.getSelectedIndex()+"").getBytes();
rs.setRecord(1,a , 0, a.length);
a=tf_Name.getString().getBytes();
rs.setRecord(2,a , 0, a.length);
a=tf_Adresse.getString().getBytes();
rs.setRecord(3,a , 0, a.length);
a=tf_Ort.getString().getBytes();
rs.setRecord(4,a , 0, a.length);
a=tf_TelNr.getString().getBytes();
rs.setRecord(5,a , 0, a.length);
a=tb_sonstiges.getString().getBytes();
rs.setRecord(6,a , 0, a.length);
Anhang B: MIDlet SkiGuide
}catch (Exception e) {}
}
/**
* Die Methode einlesen ließt von der im Parameter übergebenen Datei
ein und speichert die Datei Zeilenweise in Arrayelementen
* @param datei Dateiname als String
* @return Eingelesener Text. wobei alle ungeraden Zeilen in [x][0]
gespeichert werden und alle gerade in [x][1]
*/
private String[][] einlesen(String datei)
{ InputStreamReader is =new InputStreamReader(getClass().getResourceAsStream(datei));
String[][] a=null;
try
{ int Anzahl=0;
while(is.ready())
//Ließ die Anzahl der Zeilen ein
{ int c=is.read();
if(c==13)
{ is.read();
Anzahl++;
} }
Anzahl=(Anzahl+1)/2;
is.reset();
a=new String[Anzahl][2];
for(int g=0;g<a.length;g++) //Initialisiere das Array mit dem
leeren String
a[g][0]=a[g][1]="";
int i=0, j=0;
while(is.ready())
{ int c=is.read();
if(c==13)
//kommt ein Zeilenumbruch gehe zum nächsten
Arrayelement;
{ is.read();
i+=(++j/2);
j%=2;
}
//ansonsten füge das nächste Zeichen an das aktuelle
else
ArrayElement an
a[i][j]+=(char)c+"";
}
} catch(Exception e){e.printStackTrace();}
return a;
}
}
Anhang B: MIDlet SkiGuide
Literaturverzeichnis
[Sc04]
Klaus-Dieter Schmatz: Java 2 Micro Edition, 1. Aufl., dpunkt.verlag, 2004.
[To02]
Kim Topley: J2ME in a Nutshell, 1st. ed., O’Reilly, 2002.
[BM06]
Ulrich Breymann, Heiko Mosemann: Java ME - Anwendungsentwicklung
für Handys, PDA und Co., 2. erweiterte Aufl., Hanser, 2006
[UG04]
User’s Guide: J2ME Wireless Toolkit 2.2, Sun Microsystems, 2004
[API06]
MID Profile Specification API, Version 2.0, Sun Microsystems, 2006
[JDK06]
Sun Microsystems, Inc.: Java SE Downloads - Previous Release - JDK 5
http://java.sun.com/j2se/1.5.0/download.jsp, Abrufdatum: 18.10.2006
[MR06]
Sun Microsystems, Inc.: Mobile Information Device Profile
http://java.sun.com/products/midp/, Abrufdatum: 18.10.2006
[Ec06a]
Eclipse Foundation Web Site: Eclipse downloads
http://www.eclipse.org/downloads/, Abrufdatum: 20.10.2006
[Ec06b]
Craig Setera: EclipseME - Installation
http://eclipseme.org/docs/installation.html, Abrufdatum: 20.10.2006
Herunterladen