Posiadaczom komórek technologia Java kojarzy się głównie z grami oraz aplikacjami służącymi przede wszystkim rozrywce. Warto jednak wspomnieć o możliwości zastosowania telefonów komórkowych do bardziej przydatnych zadań, które wymagają od nas mobilności i ciągłego dostępu do informacji.Posiadaczom komórek technologia Java kojarzy się głównie z grami oraz aplikacjami służącymi przede wszystkim rozrywce. Warto jednak wspomnieć o możliwości zastosowania telefonów komórkowych do bardziej przydatnych zadań, które wymagają od nas mobilności i ciągłego dostępu do informacji.
Dzięki opracowaniu technologii J2ME programiści uzyskali
dostęp do szybkiego i prostego tworzenia aplikacji wykorzystujących
funkcjonalność urządzeń mobilnych. Artykuł ten ma na
celu zaprezentowanie możliwości technologii J2ME w oparciu
o prostą aplikację, umożliwiającą otrzymywanie oraz wysyłanie
zapytań do serwera WWW.
Co to jest J2ME?
Ze względu na stosunkowo niewielkie zasoby
telefonów komórkowych oraz innych urządzeń
przenośnych (tj. dużo wolniejsze procesory,
niewielką ilość pamięci, mały wyświetlacz – lub
też jego brak) firma Sun Microsystems opracowała
specyfikację określoną mianem J2ME (czyli Java 2
Micro Edition).
Jest to nic innego, jak nowy standard wykorzystujący
najważniejsze cechy technologii J2SE (Java
2 Standard Edition) oraz wprowadzający zbiór dodatkowych
klas charakterystycznych tylko dla urządzeń
mobilnych. Klasy te nazywane są najczęściej
konfiguracjami (ang. configuration) określającymi
wymogi techniczne telefonu komórkowego.
Uzupełnieniem całego standardu są profile
(ang. profile) oraz pakiety opcjonalne (ang. Optional
Packages), które dodają funkcje dostępne tylko
w konkretnych modelach urządzeń.
Najczęstszym profilem stosowanym w telefonach
komórkowych jest MIDP (Mobile Information
Device Profile), który tak naprawdę uzupełnia
zestaw klas oferowanych przez CLDC. Programy
napisane za pomocą MIDP noszą nazwę Midletów
i uruchamiane są w środowisku KVM (Kilobyte
Virtual Machine).
Rozróżniamy dwie wersje MIDP stosowane
w obecnych telefonach komórkowych:
- MIDP 1.0
- MIDP 2.0
Różnią się one przede wszystkim wymaganiami
nałożonymi na telefon komórkowy oraz możliwościami,
jakie dają. Większość nowych telefonów
wspiera standard MIDP 2.0, dlatego też aplikacja,
jaką stworzymy, zostanie oparta na nowszej wersji
ww. profilu.
Przygotowanie do pracy
Istnieje wiele środowisk programistycznych,
w których można tworzyć aplikacje Java przeznaczone
dla telefonów komórkowych. Różnią się
one wielkością oraz ilością dostępnych funkcji.
Teoretycznie nie są potrzebne żadne wyszukane
narzędzia i dla prostych projektów wystarczy
z pewnością zwykły Notatnik oraz J2ME Wireless
Toolkit.
My posuniemy się jednak trochę dalej i wykorzystamy
znakomite środowisko do tworzenia
aplikacji Java, jakim jest NetBeans 5.5. Oprócz
standardowych funkcji, takich jak kolorowanie
składni i wyświetlanie podpowiedzi, oferuje także
zaawansowane metody wykrywania błędów już na
poziomie pisania kodu. Ponadto istnieje możliwość
rozbudowania środowiska za pomocą dość pokaźnego
zestawu wtyczek.
Żeby rozpocząć tworzenie aplikacji w J2ME,
trzeba zainstalować wymagane narzędzia:
- Instalacja emulatora J2ME. Wejdź na stronę
firmy Sun Microsystems: http://java.sun.
com/products/sjwtoolkit/, a następnie pobierz
i zainstaluj paczkę o nazwie J2ME Wireless Toolkit
2.2 (w momencie pisania artykułu dostępna
jest wersja 2.5 Beta. Nie jest ona jednak w pełni
kompatybilna ze środowiskiem NetBeans). - Instalacja środowiska IDE wraz z dodatkiem do
obsługi J2ME. Ze strony: http://www.netbeans.
org/ pobierz i zainstaluj następujące archiwa:
NetBeans 5.5 IDE oraz Mobility Pack for CLDC/
MIDP.
Po zakończeniu wymienionych czynności warto
uruchomić środowisko NetBeans, aby sprawdzić, czy
wszystkie elementy zainstalowały się poprawnie.
Kilka słów teorii
Midlet to nic innego jak aplikacja Java napisana
w oparciu o profil MIDP. Każdy midlet, jaki stworzysz,
musi dziedziczyć z klasy MIDlet, która z kolei
wywodzi się bezpośrednio z object.
Dzięki takiemu podejściu masz dostęp do
deskryptora aplikacji i możliwość wykrywania
różnych zdarzeń zewnętrznych, takich jak przejście
w stan pauzy, start lub zakończenie działania.
Aby obsłużyć jedną z wyżej wymienionych
sytuacji wystarczy przeciążyć wybraną metodę:
- {stala}startApp(){/stala} – sygnalizuje, że Midlet przeszedł
w stan \”Active\” (pierwsze uruchomienie
lub powrót ze stanu pauzy) - {stala}pauseApp(){/stala} – sygnalizuje, że Midlet
przeszedł w stan \”Paused\” (tzw. pauza lub stan
pasywny). Sytuacja ta występuje najczęściej,
gdy ktoś dzwoni na telefon komórkowy podczas
działania aplikacji. - {stala}destroyApp(boolean unconditional){/stala}
– sygnalizuje żądanie zakończenia aplikacji
i próbę przejścia do stanu \”Destroyed\”. Argument
jest wartością logiczną określającą bezwarunkowe
lub warunkowe zakończenie aplikacji. - {stala}notifyDestroyed(){/stala} – oznacza przejście
do stanu \”Destroyed\”. W praktyce kończy to
działanie aplikacji.
Poniższy listing przedstawia strukturę prostego
midletu. Zwróć uwagę na konieczność importowania
dodatkowych pakietów, a także na fakt,
że stworzona przez nas klasa PrzykladowyMidlet
wywodzi się bezpośrednio z klasy MIDlet.
Struktura prostego midletu:
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class PrzykladowyMidlet extends MIDlet {
public void startApp() {
}
public void pauseApp() {
}
public void destroyApp(boolean unconditional) {
}
}
Projekt aplikacji
Naszym zadaniem jest opracowanie prostego
systemu łączności pomiędzy telefonem komórkowym
a skryptem PHP (lub innym), działającym po
stronie serwera.
W tym celu jako przykład stworzona zostanie
prosta aplikacja J2ME umożliwiająca wypełnienie
formularza w telefonie komórkowym, a następnie
wysłanie go na serwer za pomocą protokołu HTTP
i metody POST. Ponadto aplikacja posiadać będzie
funkcję pobierania ostatniego wpisu z serwera
oraz wyświetlania go w telefonie.
Zauważ, że stanem początkowym jest menu.
W zależności od decyzji użytkownika istnieje
możliwość wyboru jednej z trzech opcji: dodania nowego wpisu, pobrania ostatniego rekordu lub
zakończenia aplikacji. W przypadku wyboru pierwszej
opcji istnieje również możliwość powrotu do
menu za pomocą przycisku \”Wstecz\”.
Wiadomo już, w jaki sposób działać będzie
nasz midlet. Przyszedł więc czas, aby omówić
strukturę klas oraz zależności pomiędzy nimi. Ułatwi
nam to prosty sposób przejść od fazy projektu
do implementacji.
Główną klasą projektu jest TaskMidlet, dziedzicząca
z MIDlet i przeciążająca takie metody jak
{stala}startApp(){/stala}. To właśnie tutaj zagnieżdżone są
wszystkie napisane przez nas klasy:
- {stala}MainMenuDisplayable{/stala} – klasa odpowiedzialna
za wyświetlenie Menu, dziedzicząca
z klasy List i implementująca interfejs CommandListener,
umożliwiający przechwytywanie
zdarzeń wywołanych przez użytkownika. - {stala}AddTaskDisplayable{/stala} – klasa odpowiedzialna
za wyświetlenie formularza, dziedzicząca
z klasy Form i implementująca interfejs
CommandListener. - {stala}ModalAlert{/stala} – klasa rozszerzająca funkcjonalność
okienka z komunikatem (\”Alert\”)
o możliwość dodania przycisku \”OK\”. - {stala}OkButtonAlert{/stala} – właściwa klasa rozszerzająca
ModalAlert, przechwytująca zdarzenie
przyciśnięcia \”OK\”. - {stala}DisplayableManager{/stala} – odpowiedzialna
za zarządzanie obiektami wywodzącymi się
z klasy Displayable. Innymi słowy, jest to klasa
nadzorująca wyświetlanie obiektów na ekranie
telefonu komórkowego – określająca ich kolejność
i relacje panujące między nimi. - {stala}PostQuery{/stala} – odpowiedzialna za połączenie
za pomocą protokołu HTTP i wysłanie zapytań
metodą POST.
Warto dodać, że wszystkie klasy zawierające
w nazwie przyrostek \”Displayable\” będą wyświetlone
na ekranie telefonu.
Zależności pomiędzy klasami zostały przedstawione
na poniższym diagramie klas. Dla przypomnienia
dodam, że zgodnie ze standardem UML
2.0, strzałki z ciągłą linią oznaczają dziedziczenie
z obiektu wskazywanego przez grot. Przerywana
linia reprezentuje natomiast implementację wskazywanego
interfejsu.
Jak łatwo zauważyć, w projekcie rozszerzamy
funkcjonalność elementów GUI (wszystkie
pochodne klasy Displayable) w taki sposób, aby
zdefiniować ich wygląd oraz umożliwić komunikację
z użytkownikiem. W tym celu klasy te
implementują metodę commandAction zdefiniowaną
w interfejsie CommandListener – odpowiedzialnym
za nasłuchiwanie i obsługę przychodzących
zdarzeń.
Warto również zapoznać się z bardziej szczegółową
wersją diagramu UML przedstawiającą dość
dokładną strukturę klas, zamieszczonego na CD.
Z pewnością ułatwi ona zrozumienie kodu, który
przedstawiony zostanie na kolejnych stronach tego
artykułu.
Zarządzanie elementami GUI – klasa DisplayableManager
Profil MIDP 2.0 udostępnia wiele klas umożliwiających
tworzenie interfejsów graficznych,
przeznaczonych dla wyświetlaczy telefonów
komórkowych. Większość z nich dostępna jest
w pakiecie javax.microedition.lcdui, który oferuje
zarówno możliwość tworzenia GUI wysokiego, jak
i niskiego poziomu.
Ten pierwszy posiada zbiór
zdefiniowanych kontrolek, które tworzy się w prosty
sposób, lecz ich implementacja i wygląd zależą
od modelu telefonu. Interfejs niskiego poziomu
daje bezpośredni dostęp do ekranu komórki oraz
możliwość rysowania na płótnie (ang. canvas).
Kosztem tego jest niestety bardziej skomplikowany
kod oraz dłuższy proces tworzenia aplikacji.
W naszym midlecie wykorzystamy GUI wysokiego
poziomu, gdyż jest ono proste w zastosowaniu
i w łatwy sposób wyjaśnia podstawowe
techniki programowania w J2ME.
Każdy obiekt wyświetlany na ekranie telefonu
musi dziedziczyć z klasy Displayable. Za sam proces
wyświetlenia obiektu Displayable odpowiada
warstwa abstrakcyjna w postaci klasy Display.
W praktyce umożliwia to dostęp do podstawowych
własności ekranu (takich jak rozmiar) oraz ustawienie
danego obiektu typu Displayable jako aktualnie
wyświetlanego.
Najważniejsze metody klasy Display to:
- {stala}getDisplay(MIDlet midlet){/stala}
– zwraca referencje do aktualnej instancji ekranu
związanej z midletem - {stala}setCurrent(Displayable d){/stala}
– wyświetla obiekt typu Displayable na ekranie
telefonu - {stala}getCurrent(){/stala} – pobiera aktualnie wyświetlany
obiekt Displayable
Warto teraz zastanowić się, jak wygląda proces
nawigacji w aplikacjach J2ME. Ponieważ rozmiar
wyświetlacza jest ograniczony, to w danej chwili
możliwe jest wyświetlenie tylko jednego obiektu
typu Displayable.
W związku z tym warto jest opracować
mechanizm umożliwiający szybkie przełączanie
się pomiędzy ekranami. Wykorzysta on stos,
czyli dobrze znaną strukturę danych typu LIFO (Last
In First Out). Na szczycie stosu będzie się zawsze
znajdować aktualnie widoczny ekran.
Dodanie
nowego elementu możliwe jest dzięki metodzie
pushDisplayable, która automatycznie zmienia
zawartość wyświetlacza. Jeżeli zajdzie potrzeba
powrotu do poprzedniego ekranu, wystarczy
wykonać metodę popDisplayable().
Na początku definiujemy nazwę klasy oraz atrybuty.
Wykorzystujemy tutaj gotową implementację
klasy Stack dostępną w pakiecie java.util.Stack.
Atrybut current wskazuje na aktualnie wyświetlany
obiekt, parent odnosi się do klasy rodzica.
public class DisplayableManager
{
private Stack displayableStack;
private Displayable current;
private TaskMidlet parent;
Tworzymy konstruktor w którym dokonujemy
inicjalizacji stosu oraz innych atrybutów.
public DisplayableManager(TaskMidletparent) {
current = null;
this.parent = parent;
displayableStack = new Stack();
}
Kolejny krok to stworzenie metody push
Displayable odpowiedzialnej za dodanie
obiektu Displayable na szczyt stosu oraz aktualizację
zawartości ekranu telefonu.
public void pushDisplayable(Displayabled) {
displayableStack.push(d);
current = (Displayable) displayableStack.peek();
Display.getDisplay(parent).set-Current(current);
}
Warto również pomyśleć o możliwości powrotu
do poprzedniej zawartości ekranu. W tym celu
stworzymy metodę popDisplayable(), która usunie
najwyższy element ze stosu i wyświetli element,
który znajdował się pod nim
public Displayable popDisplayable()
{
Displayable popped = (Displayable) displayableStack.pop();
if ( displayableStack.size() !=0 ) {
current = (Displayable) displayableStack.peek();
Display.getDisplay(parent).set-current(current);
} else {
parent.destroyApp(false);
}
return popped;
}
Kontrola akcji użytkownika – tworzymy menu
Klasa MainMenuDisplayable
Kręgosłupem naszego midletu jest menu,
czyli miejsce, z którego możliwy jest dostęp do
pozostałych funkcji aplikacji. Menu to nic innego,
jak obiekt dziedziczący z klasy List, która wywodzi
się także z Displayable.
Elementy listy dodawane są za pomocą metody
append, która przyjmuje za argument nazwę oraz
ewentualną referencję do obrazka (w naszym wypadku
jest to null). Aktualnie zaznaczona pozycja listy zwracana
jest przez metodę getSelectedIndex().
Przechwytywanie zdarzeń zewnętrznych
wymaga stworzenia obiektu typu Command, który
w uproszczeniu jest przyciskiem \”Wybierz\”, pozwalającym
reagować na akcje użytkownika:
cSelect = new Command(NAZWA, TYP, KOLEJNOŚĆ);
Gdzie:
- NAZWA – określa napis wyświetlany na przycisku
- TYP – to rodzaj przycisku zdefiniowany przez
stałą dostępną z poziomu klasy Command (np.
{stala}Command.OK{/stala}., {stala}Command.BACK{/stala}) - ORDER – kolejność wyświetlania przycisków
(jeżeli jest ich kilka) - cPrzyklad – referencja do nowo powstałego
obiektu
Pamiętaj, że każde zdarzenie określone przez
Command musi zostać związane z obiektem nadrzędnym.
Można to zrobić w następujący sposób:
{stala}this.addCommand(cSelect){/stala};
Na sam koniec warto dodać, że każda klasa obsługująca
pewnego rodzaju zdarzenia musi implementować
interfejs CommandListener. W praktyce
sprowadza się to do odpowiedniego przeciążenia
metody {stala}commandAction(){/stala} oraz wykonania
{stala}setCommandListener(this){/stala} w konstruktorze.
W ten sposób \”prosimy\” o nasłuchiwanie
zdarzeń pochodzących tylko ze stworzonego przez
nas obiektu:
this.setCommandListener(this);
Wewnątrz metody {stala}commandAction(){/stala} umieszczona jest instrukcja switch, wykonująca odpowiedni
kod w zależności od wartości zwróconej
przez {stala}getSelectedIndex(){/stala}. Innymi słowy, w momencie naciśnięcia przycisku \”Wybierz\”
wykonana zostanie funkcja zależna od zaznaczonej
pozycji na liście menu.
Struktura commandAction dla klasy MainMenu-
Displayable:
public void commandAction(Command c, Displayable s) {
if ( c == cSelect) {
switch( this.getSelectedIndex() )
{
Dodaj zadanie
case 0:
Tworzy obiekt z Formularzem (opisany później)
addTaskDisplayable = new AddTaskDisplayable(\"Dodaj zadanie\", parent);
Wyświetla obiekt i wrzuca go na stos
onDisplay.pushDisplayable(add-TaskDisplayable);
break;
Pobierz zadanie
case 1:
Zakończ działanie aplikacji
case 2:
parent.destroyApp(true);
break;
}
}
}
Dodawanie nowego wpisu – obsługa formularza
Klasa AddTaskDisplayable
Kolejnym etapem rozbudowy naszego midletu
jest udostępnienie interfejsu umożliwiającego
wprowadzenie danych wejściowych. W tym celu
najlepiej rozszerzyć funkcjonalność klasy Form i dodać
do niej wymagane przez nas pola formularza.
Podobnie jak w przypadku stron internetowych
J2ME oferuje kilka rodzajów pól wejściowych.
Wszystkie reprezentowane są za pomocą obiektów
wywodzących się z klasy Item. W naszej aplikacji
wykorzystane zostaną tylko pola edycji typu
TextField. Umożliwiają one wprowadzenie przez
użytkownika wybranego ciągu znaków o określonej
maksymalnej długości.
Konstruktor obiektu klasy TextField posiada
następującą składnię:
TextField nameField = new TextField(NAZWA, WARTOŚĆ, MAX, TYP);
Gdzie:
- NAZWA – określa nazwę pola tekstowego – czyli
napis wyświetlający się po lewej stronie pola - WARTOŚĆ – pozwala predefiniować wartość
wyświetlającą się wewnątrz pola - MAX – określa maksymalną długość pola
- TYP – wprowadza ograniczenia dla pola
takie jak: tylko numer telefonu, cyfry lub znaki
alfabetu. Wartości tego argumentu zostały zdefiniowane
jako stałe w klasie TextField (np. {stala}TextField.NUMERIC{/stala} lub {stala}TextField.PHONENUMBER{/stala})
Stworzone w powyższy sposób pole należy
dodać do formularza za pomocą metody append(r),
gdzie \”r\” jest referencją do pola (w naszym przypadku
nameField). Warto wiedzieć, że do formularza
można dodawać jedynie obiekty dziedziczące
z klasy Item. W przeciwnym wypadku będziemy
mieć konflikt typów.
Jeżeli w przyszłości zajdzie potrzeba odczytania
wartości pola nameField wewnątrz innej metody,
można to zrobić w następujący sposób:
String wartość = nameField.get-String();
Niestety metoda {stala}getString(){/stala} zwraca
tylko łańcuch znaków. W przypadku gdy chodzi
o wartość innego typu, należy ją samemu przekonwertować.
Poniższy kod pokazuje, w jaki sposób
należy dokonać konwersji z typu string na short.
short priority= Short.parseShort((priorityField.getString());
Posiadając tę wiedzę, można w łatwy sposób dodać następujące pola formularza:
- {stala}nameField{/stala} – określa nazwę zadania, które wprowadzamy do bazy
- {stala}phoneField{/stala} – numer telefonu zleceniodawcy
- {stala}descField{/stala} – krótki opis
- {stala}addressField{/stala} – adres zleceniodawcy
- {stala}priorityField{/stala} – priorytet zadania
Pola tekstowe zostały dodane. Na sam koniec
pozostało stworzenie dwóch obiektów Command
pozwalających obsłużyć okno z formularzem. Jeden
przycisk odpowiadać będzie za wysłanie formularza,
drugi za powrót do menu głównego:
Dodawanie zdarzeń:
cAdd = new Command(\"Dodaj\", Command.OK, 1);
cBack = new Command(\"Wstecz\", Command.BACK,2);
this.addCommand(cAdd);
this.addCommand(cBack);
this.setCommandListener(this);
Przykładowa implementacja metody command-Action może wyglądać w następujący sposób:
public void commandAction(Command c,Displayable s) {
// Obsługa przycisku cAdd
if ( c == cAdd) {
KOD odpowiedzialny za wysłanie formularza
zostanie dodany w dalszej części artykułu.
Obsługa przycisku cBack
} else if ( c == cBack ) {
wykonuje metodę POP na obiekcie typu Display-
Manager.
Powraca do Menu Głównego
parent.onDisplay.popDisplayable();
}
}
Wyświetlanie komunikatów
W technologii J2ME proste komunikaty lub
ostrzeżenia przekazywane są za pomocą obiektów
typu Alert (dziedziczących z Displayable). Pozwalają
one na wyświetlenie krótkiego tekstu, ikony oraz
zasygnalizowanie zdarzenia dźwiękiem. Zastosowanie
alertów w midlecie TaskMidlet sprowadza
się do wyświetlenia komunikatu o błędzie oraz
ostatnio dodanym zadaniu.
Wyróżniamy dwa rodzaje alertów:
- zwyczajne – wyświetlane tylko przez czas określony
za pomocą setTimeout() - modalne – wyświetlane zawsze, aż do momentu
ich zamknięcia przez użytkownika (setTimeout(
Alert.FOREVER)
Naszym zadaniem jest stworzenie okna
komunikatu, reagującego tylko na akcje użytkownika.
W tym celu stworzona zostanie nowa klasa
ModalAlert, dziedzicząca z Alert. Konstruktor obiektu
ModalAlert przyjmuje dwa parametry: nazwę
okna oraz jego treść. Pozostałe wartości wymagane
przez superklasę zdefiniowane są statycznie.
W tym przypadku null oznacza brak dodatkowego
obrazka wyświetlanego przy komunikacje, a Alert-
Type.INFO określa rodzaj komunikatu.
Przykładowy kod klasy może wyglądać tak jak
poniżej:
ModalAlert public class ModalAlert extends Alert {
protected Command cOk;
public ModalAlert(String title, String content ) {
super(title,content, null, AlertType.INFO);
cOk = new Command(\"Ok\",Command.OK, 1);
this.addCommand(cOk);
this.setTimeout(Alert.FOREVER);
}
}
Klasa ModalAlert ma na celu stworzenie uniwersalnego
okna komunikatu (typu modal).
Tak naprawdę udostępnia ona jeden przycisk
oraz blokuje możliwość samoczynnego zakończenia
alertu. Rozszerzenie funkcjonalności klasy
polega na zaimplementowaniu znanego już interfejsu
CommandListener i oprogramowaniu akcji
odpowiedzialnej za zamknięcie okna.
public class OkButtonAlert extends ModalAlert implements CommandListener
{
private TaskMidlet parent;
public OkButtonAlert(String t,String content,TaskMidlet p ) {
super(t, content);
this.parent = p;
setCommandListener(this);
}
public void commandAction(Command c, Displayable s) {
if ( c == cOk ) {
parent.onDisplay.popDisplayable();
}
}
}
Komunikacja z serwerem – wstęp
Największym atutem technologii J2ME jest
możliwość szybkiego i łatwego nawiązywania
połączeń sieciowych za pomocą takich standardów
jak Bluetooth, GPRS oraz Infrared (podczerwień).
W praktyce oznacza to duże możliwości w zakresie
tworzenia aplikacji klient-serwer.
Wszystko to
za sprawą konfiguracji CLDC (Connected Limited
Device Configuration), która oferuje zestaw klas
odpowiedzialnych za połączenia sieciowe. Zbiór
ten nazwano Generic Connection Framework
i w rzeczywistości jest to niezależna od platformy
struktura oferująca hierarchię interfejsów sieciowych,
za których implementację odpowiedzialne są
profile (takie jak MIDP).
Profil MIDP rozszerza funkcjonalność CLDC
o strukturę HttpConnection, która, jak można
się domyślić, wspiera standard HTTP. Wszystkie
implementacje MIDP muszą implementować HTTP
bez względu na to, czy jest to wersja obsługująca
protokół WAP, czy TCP/IP.
Cechą wspólną Generic Connection Framework
jest sposób rozpoczynania połączeń. Można to zrobić
za pomocą metody open() klasy Connector.
W przypadku nawiązania połączenia zwrócony
jest obiekt, który implementuje jeden z interfejsów
GCF. Dla przykładu poniższy kod otworzy połączenie
HTTP ze wskazanym URL:
String url = \"http://www.server.com/script.jsp\";
HttpConnection hc = null;
hc = (HttpConnection)Connector.open(url);
Do procesu komunikacji potrzebna jest również
wiadomości, która zostanie przesłana do skryptu.
W naszym wypadku można ją zdefiniować tak jak
to widać poniżej:
message = \"op=add&name=\" + name + \"&desc=\" + desc + \"&phone=\" + phone + \"&address=\" + address + \"&priority=\" + String.valueOf(priority);
Powyższy kod tworzy łańcuch znaków zawierający
nazwy pól oraz ich wartości. Zadaniem skryptu
działającego na serwerze jest interpretacja tych danych.
Przykładowo może ona polegać na dodaniu
przesłanego wpisu do bazy danych.
Kiedy połączenie zostanie nawiązane, możliwe
będzie ustawienie podstawowych parametrów
określających między innymi metody przesyłania
danych:
hc.setRequestMethod(HttpConnection.POST);
hc.setRequestProperty(\"Content-Type\", \"application/x-www-form-urlencoded\");
hc.setRequestProperty(\"Content-Length\", len);
Ostatnia linia określa rozmiar przesyłanej
wiadomości. Jeżeli jest ona zapisana w zmiennej
message, to długość obliczamy w następujący
sposób:
len = Integer.toString( message.length() );
Wysłanie wiadomości do serwera odbywa się
w dwóch etapach. Najpierw otwieramy strumień
wyjściowy:
OutputStream out = hc.openOutput-Stream();
Następnie zamieniamy wiadomość na format
bajtowy i zapisujemy go do łańcucha wyjściowego
za pomocą metody write:
out.write(message.getBytes());
Odpowiedź serwera można odebrać podobną
techniką. Zamiast strumienia wyjściowego, należy
jednak użyć strumienia wejściowego. Otrzymane
dane zostają zapisane w tablicy bajtów za
pomocą metody read.
Jej rozmiar zostaje określony
dynamicznie przy użyciu funkcji getLength().
Konwersja do łańcucha znaków odbywa się dzięki
konstruktorowi klasy String.
InputStream in = hc.openInputStream();
int length = (int)hc.getLength();
byte[] data = new byte[length];
in.read(data);
String response = new String(data);
Istnieje możliwość, że w czasie połączenia
zostanie wyrzucony wyjątek IOException. Najlepiej
jest więc umieścić cały kod w bloku try … catch
i w przypadku błędu wyświetlić stosowny komunikat.
Komunikacja z serwerem – klasa PostQuery
Zdobytą w poprzedniej sekcji wiedzę teoretyczną
najlepiej jest sprawdzić, tworząc klasę PostQuery
odpowiedzialną za połączenie za pomocą
protokołu HTTP przy użyciu metody POST.
Klasa PostQuery implementując interfejs
Runnable tworzy nowy wątek odpowiedzialny za
wysłanie zapytania do serwera. Dzięki takiemu
rozwiązaniu, w łatwy sposób uniezależnia się obsługę
połączenia od interfejsu graficznego. W ten
sposób unikniemy niepotrzebnego blokowania lub
opóźnień w reakcji elementów GUI.
Konstruktor klasy PostQuery przyjmuje dwa
argumenty: adres URL skryptu oraz referencję do
obiektu TaskMidlet. Lokalizację pliku na serwerze
najlepiej zapisać w deskryptorze (JAD). Umożliwi
to szybką zmianę adresu URL bez konieczności
rekompilowania kodu. Do wartości zawartych
w deskryptorze można odwołać się w następujący
sposób:
parent.getAppProperty(\"Script-URL\")
gdzie parent jest referencją do obiektu klasy
TaskMidlet.
Klasa PostQuery została wyposażona również
w dwie metody odpowiadające za sformułowanie
wiadomości przesyłanej do serwera, rozpoczęcie
wątku wysyłającego wiadomość oraz odebranie
odpowiedzi i wyświetlenie komunikatu za pomocą
stworzonego wcześniej obiektu klasy OkButton-
Alert.
Użycie obiektu typu PostQuery sprowadza się
jedynie do stworzenia go za pomocą konstruktora
i wywołania jednej z metod:
- {stala}sendAddDataQuery(String name,
String desc, String phone, String
address, short priority{/stala}) - {stala}sendGetDataQuery();{/stala}
Pierwsza metoda przesyła do serwera zapytanie
odpowiadające dodaniu wpisu do bazy danych
(opcja \”Dodaj\” z Menu). Druga wysyła zapytanie
z prośbą o udostępnienie ostatnio dodanego wpisu
(opcja \”Pobierz\”). W odpowiedzi otrzymujemy ciąg
znaków reprezentujący ostatni rekord.
Skrypt serwera
Na sam koniec warto wspomnieć o skrypcie
działającym po stronie serwera. Z pewnością powinien
on obsługiwać dwa rodzaje zapytań. Pierwsze
z nich przyjmuje dane w postaci:
nazwa_skryptu?op=add&name=Nazwa&desc=Opis&phone=Telefon&address=Adres&priority=Priorytet
a następnie dodaje do bazy rekord zawierający
wartości przesłane przez midlet.
Drugie zapytanie wysyła łańcuch znaków reprezentujący
ostatnio dodany wpis i ma następującą
postać:
nazwa_skryptu?op=get
Warto zwrócić uwagę na format, w jakim
przesyłane są dane. Najlepiej oddzielić nazwy pól
separatorem (na przykład znakiem #), tak aby midlet
mógł w łatwy sposób wyodrębnić odpowiednie
wartości z łańcucha znaków.
Język programowania, w jakim zostanie napisany
skrypt, nie ma w tym wypadku większego
znaczenia. Ważne jest, żeby potrafił przyjmować
parametry w opisanej tutaj postaci.