22
Kurs Java ME – II część
Dodano [Listopad 22, 2011]Witajcie!
„Wytrwałość drogą do sukcesu” – taka myśl mi przyszła do głowy siadając do pisania kolejnej części naszego kursu. Niezmiernie się cieszę że tu jesteście (mam nadzieję że nie „z przypadku”) i że chcecie rozwijać swoje zdolności w oparciu o nowe technologie (czy może być coś piękniejszego?). Nie rozwódźmy się już dalej (hehe – czy pasję programowania można porównać do małżeństwa?) tylko wskoczmy od razu w nasz cyfrowy świat.
Na poprzedniej lekcji dowiedzieliście się czym (ogólnie) jest Java ME, skąd ją można pobrać, jak skonfigurować środowisko pracy a także poznaliście sposób tworzenia najprostszej aplikacji na urządzenia mobilne. Trochę się nawet z nią podroczyliśmy aby następnie ujrzeć efekt naszej pracy na cudownym, kilkucalowym ekraniku…emulatora (na razie).
Dziś „wejdziemy” trochę głębiej, a mianowicie zobaczymy co tak naprawdę znajduje się pod „maską” naszej aplikacji, poznamy kod źródłowy „szkieletu” programu, który będziemy wykorzystywać przez cały kurs, dowiecie się jak wygląda cykl życia aplikacji mobilnej, a także wprowadzimy pierwsze kontrolki „wysokiego poziomu” (jeśli nie pamiętacie o co chodzi, przeczytajcie jeszcze raz poprzednią lekcję).
Dużo tego. Nie przerażajcie się – będzie jeszcze więcej
Ale czyż nie dlatego tu jesteśmy?
Zaczynamy.
Rozdział drugi – „Co diabeł skrywa pod spódnicą”
Zanim rozpoczniemy tworzenie nowego projektu, proszę Was byście jeszcze raz spojrzeli na ten, nad którym pracowaliśmy ostatnio (jeśli nie macie go u siebie, możecie ściągnąć go z mojej strony – link jest na końcu pierwszej części kursu). Dla przypomnienia, zamieszczę tutaj kod źródłowy (odpowiednio sformatowany), który wygenerował nam NetBeans – jeśli chcecie sami go zobaczyć w Waszym IDE, kliknijcie dwa razy na nazwę Waszego projektu w okienku Projects, potem na Source Packages, następnie hello a potem HelloMIDlet.java (gdy zamiast kodu pojawiło się okienku przebiegu działania programu lub budowania GUI, wybierzcie Source z menu View → Editors:
-
-
import javax.microedition.midlet.*;
-
-
import javax.microedition.lcdui.*;
-
-
public class HelloMIDlet extends MIDlet implements CommandListener
-
-
{
-
-
private boolean midletPaused = false;
-
-
private Command exitCommand;
-
-
private Form form;
-
-
private StringItem stringItem;
-
-
/**
-
-
* The HelloMIDlet constructor.
-
-
*/
-
-
public HelloMIDlet ()
-
-
{
-
-
}
-
-
/**
-
-
* Initilizes the application.
-
-
* It is called only once when the MIDlet is started. The method is called before the <code>startMIDlet</code> method.
-
-
*/
-
-
private void initialize() {
-
-
// write pre-initialize user code here
-
-
// write post-initialize user code here
-
-
}
-
-
/**
-
-
* Performs an action assigned to the Mobile Device – MIDlet Started point.
-
-
*/
-
-
public void startMIDlet() {
-
-
// write pre-action user code here
-
-
switchDisplayable(null, getForm());
-
-
// write post-action user code here
-
-
}
-
-
/**
-
-
* Performs an action assigned to the Mobile Device – MIDlet Resumed point.
-
-
*/
-
-
public void resumeMIDlet() {
-
-
// write pre-action user code here
-
-
// write post-action user code here
-
-
}
-
-
/**
-
-
* Switches a current displayable in a display. The display instance is taken from getDisplay method. This method is used by all actions in the design for switching displayable.
-
-
* @param alert the Alert which is temporarily set to the display; if null, then nextDisplayable is set immediately
-
-
* @param nextDisplayable the Displayable to be set
-
-
*/
-
-
public void switchDisplayable(Alert alert, Displayable nextDisplayable) {
-
-
// write pre-switch user code here
-
-
Display display = getDisplay();
-
-
if (alert == null) {
-
-
display.setCurrent(nextDisplayable);
-
-
} else {
-
-
display.setCurrent(alert, nextDisplayable);
-
-
}
-
-
// write post-switch user code here
-
-
}
-
-
/**
-
-
* Called by a system to indicated that a command has been invoked on a particular displayable.
-
-
* @param command the Command that was invoked
-
-
* @param displayable the Displayable where the command was invoked
-
-
*/
-
-
public void commandAction(Command command, Displayable displayable) {
-
-
// write pre-action user code here
-
-
if (displayable == form) {
-
-
if (command == exitCommand) {
-
-
// write pre-action user code here
-
-
exitMIDlet();
-
-
// write post-action user code here
-
-
}
-
-
}
-
-
// write post-action user code here
-
-
}
-
-
/**
-
-
* Returns an initiliazed instance of exitCommand component.
-
-
* @return the initialized component instance
-
-
*/
-
-
public Command getExitCommand() {
-
-
if (exitCommand == null) {
-
-
// write pre-init user code here
-
-
exitCommand = new Command("Exit", Command.EXIT, 0);
-
-
// write post-init user code here
-
-
}
-
-
return exitCommand;
-
-
}
-
-
/**
-
-
* Returns an initiliazed instance of form component.
-
-
* @return the initialized component instance
-
-
*/
-
-
public Form getForm() {
-
-
if (form == null) {
-
-
// write pre-init user code here
-
-
form = new Form("Welcome", new Item[] { getStringItem() });
-
-
form.addCommand(getExitCommand());
-
-
form.setCommandListener(this);
-
-
// write post-init user code here
-
-
}
-
-
return form;
-
-
}
-
-
/**
-
-
* Returns an initiliazed instance of stringItem component.
-
-
* @return the initialized component instance
-
-
*/
-
-
public StringItem getStringItem() {
-
-
if (stringItem == null) {
-
-
// write pre-init user code here
-
-
stringItem = new StringItem("Hello", "Hello, World!");
-
-
// write post-init user code here
-
-
}
-
-
return stringItem;
-
-
}
-
-
/**
-
-
* Returns a display instance.
-
-
* @return the display instance.
-
-
*/
-
-
public Display getDisplay ()
-
-
{
-
-
return Display.getDisplay (this);
-
-
}
-
-
/**
-
-
* Exits MIDlet.
-
-
*/
-
-
public void exitMIDlet ()
-
-
{
-
-
switchDisplayable (null, null);
-
-
destroyApp (true);
-
-
notifyDestroyed ();
-
-
}
-
-
/**
-
-
* Called when MIDlet is started.
-
-
* Checks whether the MIDlet have been already started and initialize/starts or resumes the MIDlet.
-
-
*/
-
-
public void startApp ()
-
-
{
-
-
if (midletPaused)
-
-
{
-
-
resumeMIDlet ();
-
-
} else
-
-
{
-
-
initialize ();
-
-
startMIDlet ();
-
-
}
-
-
midletPaused = false;
-
-
}
-
-
/**
-
-
* Called when MIDlet is paused.
-
-
*/
-
-
public void pauseApp ()
-
-
{
-
-
midletPaused = true;
-
-
}
-
-
/**
-
-
* Called to signal the MIDlet to terminate.
-
-
* @param unconditional if true, then the MIDlet has to be unconditionally terminated and all resources has to be released.
-
-
*/
-
-
public void destroyApp (boolean unconditional)
-
-
{
-
-
}
-
-
}
Trochę długi ten kod, czyż nie? Nie martwcie się, tak naprawdę aplikację na telefon można zmieścić w kilku linijkach, o czym niedługo się przekonacie. Jednak tutaj – mamy jeszcze wiele dodatkowych rzeczy, dzięki czemu korzystanie z aplikacji jest wygodniejsze. Pewnie wielokrotnie obiło się Wam o uszy słowo „MIDlet” – i zastanawiacie się czym on tak naprawdę jest i dlaczego o nim wspominamy podczas tego kursu.
„Na początku, Bóg stworzył…”
MIDlet – to kolaż dwóch słów: „MID” + „let”. Pierwsze z nich odnosi się do pojęć które przyswaliśmy sobie w poprzedniej lekcji: Mobile Information Device Profile – czyli zestaw wymagań jakie musi spełniać dane urządzenie mobilne, by mogło obsłużyć naszą aplikację. Drugie, to skrót od słowa „applet” – znają je wszyscy, którzy dłużej programują w Javie, i dotyczy aplikacji uruchamianych w przeglądarce internetowej na wirtualnej maszynie Javy.
Konkluzja – MIDlet to aplikacja przeznaczona na urządzenie mobilne. Z czego się składa MIDlet? Tworzą go zazwczyaj dwa pliki – ich nazwa zawiera w sobie nazwę naszej aplikacji, natomiast jeden z nich ma rozszerzenie JAR a drugi JAD. Czym są te pliki?
- plik JAR (Java Archive) – to najogólniej mówiąc paczka przechowująca kod wykonywalny naszej aplikacji wraz z wszystkimi niezbędnymi do jej działania danymi (zasobami). Znajdziecie więc w niej skompilowane pliki CLASS (o nich później), wszelkie grafiki, dźwięki i inne dane z których korzysta nasza aplikacja oraz plik manifestu o którym opowiem za chwilę. Co ciekawe – pliki JAR możecie otwierać każdym narzędziem służącym do dekompresji danych (jak np. 7-Zip) gdyż do ich stworzenia wykorzystuje się właśnie algorytm ZIP. Dzięki temu możecie podejrzeć, co twórca aplikacji umieścił w jej wnętrzu.
- plik JAD (Java Archive Descriptor) to zwykły plik tekstowy opisujący naszą aplikację, tak, by wirtualna maszyna która ją uruchamia (czyli nasz ukochany telefon) wiedziała jak ma postąpić podczas ładowania pliku JAR. Plików JAD nie będziemy zwykle edytować, gdyż całkiem dobrze robi to za nas nasze IDE (NetBeans bądź Eclipse).
Zastanawiacie się pewnie – skoro w poprzedniej lekcji utworzyliśmy aplikację Java ME, sprawdziliśmy w emulatorze jak ona działa – to by znaczyło, że gdzieś te pliki musiały zostać wygenerowane. Nie mylicie się
Niektórzy z Was pewnie już odkryli miejsce, gdzie kompilator zapisał sobie nasz plik wykonywalny: przy otwartym katalogu z projektem znajduje się katalog dist – wejdźcie w niego, a Waszym oczom ukaże się taki oto widok:
Katalog lib możecie zignorować. Jak widzicie – mamy dwa, omówione powyżej pliki. Oczywiście nie można ich tak prosto uruchomić, klikając dwukrotnie na Projekt1.jar gdyż są one przeznaczone do uruchomienia w emulatorze (czyli np. w naszym IDE). Jednak zdradzę Wam jeden sekret który niewąptliwie przyczyni się do naszej dominacji nad ludzkoś….eeee… – do uszczęśliwienia naszej twarzy
Te dwa pliki…to tak naprawdę WSZYSTKO czego potrzebujecie by uruchomić Waszą aplikację na telefonie! Nie więcej, nie mniej – wystarczy je tylko skopiować na swój telefon i zainstalować (nie podam metody instalacji, gdyż każdy producent urządzenia mobilnego ustanowił swoje standardy; jeśli nie wiecie jak to zrobić poczytajcie w dokumentacji lub Internecie pod hasłem „Instalowanie aplikacji Java” dla swojego telefonu). Kurtyna wreszcie opadła – teraz już możemy tworzyć pełnowymiarowe aplikacje podbijające masowe rynki gdyż wiemy co należy zrobić, by skłonić naszego wibrującego przyjaciela (bez skojarzeń proszę
) do współpracy.
O czym my tu ostatnio… A tak, MIDlety
Programowanie urządzeń mobilnych polega właśnie na pisaniu MIDletów – bez względu na to, czy będzie to kalendarz biznesowy, aplikacja do sprawdzania pogody czy własna implementacja gry Kółko i Krzyżyk – wszystkie z nich zachowują ten sam, podstawowy szkielet. Przyjżyjmy się mu z bliska.
Każdy MIDlet jest klasą (Javy, dla ścisłości.
) Żeby jednak nasz telefon wiedział że ma do czynienia z aplikacją mobilną – nasza klasa musi dziedziczyć po – uwaga, będzie niespodzianka – klasie „MIDlet”
To nie wszystko. Nasz program musi posiadać również zdefiniowane trzy metody:
-
startApp()
-
destroyApp()
-
pauseApp()
które to obsługują poszczególne stany MIDletu. Czym jest stan? Najogólniej mówiąc – stan definiuje „osobowość” w jakiej znajduje się lub może się znaleźć nasza aplikacja. MIDlety posiadają trzy główne stany:
- aktywny
- spauzowany
- zniszczony
a naszym zadaniem jest ich poprawne obsłużenie. Cykl pracy aplikacji mobilnej przedstawia następujący wykres:
MIDlet rozpoczyna pracę po utworzeniu instancji jego obiektu (a więc przy użyciu new) – ważne jest więc, by klasa dziedzicząca po MIDlet posiadała publiczny, domyślny konstruktor! Wywoływana jest wtedy metoda startApp, której celem jest przygotwanie aplikacji. Umieszcza się tutaj zwyczajowo kod ładujący potrzebne zasoby, tworzący obiekty pomocnicze, ekrany (o ekranach było w poprzedniej lekcji), itp. . Jeśli w trakcie tworzenia obiektu, MIDlet wywoła wyjątek – jest on niszczony. Jak widzicie na wykresie, MIDlet tylko chwilowo znajduje się w stanie Paused, tuż po jego utworzeniu – zaraz potem przechodzi w stan Active (aktywny), tuż po wykonaniu kodu znajdującego się w metodzie o sygnaturze:
-
protected void startApp( ) throws MIDletStateChangeException;
Jeżeli nie chcemy, by nasza aplikacja uruchomiła się natychmiast, w trakcie wywoływania tej metody rzucamy wyjątek klasy MIDletStateChangeException co spowoduje, że aplikacja znajdzie się w stanie Paused i będziemy ją mogli uruchomić później. Jeżeli jednak wystąpi błąd, który ma spowodować że aplikacja nie będzie działała poprawnie (np. nie może odnaleźć pliku z zasobami jak grafika, dźwięki) – powinniśmy wywołać metodę notifyDestroyed (o niej później). Ostatecznie, gdy wyrzucimy jakikolwiek inny wyjątek, znaczyć to będzie, iż wystąpił tzw. błąd fatalny, aplikacji nie da się już uratować i trzeba ją zamknąć przy wykorzystaniu metody destroyApp. Po wykonaniu kodu inicjalizycyjnego zawartego w metodzie startApp…MIDlet kończy swoją pracę
Tak jest, nie ma tu jakichś ukrytych sztuczek, nie ma tajemniczo wygenerowanej pętli przyjmującej polecenia użytkownika – to od nas w 100% zależy jak ma wyglądać interakcja z MIDletem. Zwykle, zaraz po zainicjowaniu wszystkich niezbędnych obiektów tworzymy interfejs użytkownika (GUI) i pozwalamy użytkownikowi na wprowadzanie komunikatów (przycisków). Można również uruchomić osobny wątek w którym to będzie wykonywana logika aplikacji, bądź też dodać zaplanowane zadania, które MIDlet będzie wykonywał w ustalonym czasie. Gdy MIDlet jest aktywny może przyjąć jeszcze dwa inne stany: zapauzowany oraz zniszczony. Kiedy MIDlet jest zapauzowany? Nie mamy sami na to wpływu – przejście MIDletu w ten stan dokonuje sama maszyna wirtualna Javy ME, kiedy wykryje jakąś akcję zewnętrzną, jak np. nadchodzące połączenie telefoniczne. Wywoływana jest wtedy właśnie metoda:
-
protected abstract void pauseApp( );
a sam MIDlet staje się zadaniem w tle (drugorzędnym). Dlaczego tak ważne jest obsłużenie tej metody? Ano – w czasie gdy MIDlet jest zapauzowany TRACI dostęp do ekranu naszego telefonu! Nie ma sensu zatem ciągła aktualizacja jego logiki, gdyż zużywa się wtedy tylko niepotrzebnie i tak już ograniczone zasoby naszego urządzenia. Zwykle, w stanie zapauzowania wstrzymuje się wykonywanie kodu odpowiedzialnego za odświeżanie ekranu, można też np. wyłączyć obsługę dźwięków w naszej aplikacji, zablokować timery, zamknąć połączenia sieciowe itp. – robić wszystko co niezbędne by aplikację „podtrzymać na życiu” ale nie pozwolić jej rozgrzewać niepotrzebnie naszego urządzonko
. Zauważcie, że gdy skończy się połączenie telefoniczne, i platforma Javy wykryje, że można wznowić działanie MIDletu, PONOWNIE jest wywoływana metoda startApp! Pojawiło się zatem interesujące pytanie: przecież w metodzie tej umieszczony był cały kod inicjalizacyjny! Czy zatem wszystkie obiekty tam powstałe będą utworzone od nowa? Odpowiedź brzmi: niesetety tak. Dlatego też – bardzo ważny jest rozsądek przy projektowaniu aplikacji – by uniknąć niepotrzebnych i nieporządanych efektów jakie mogą powstać przy wielokrotnym alokowaniu tych samych obiektów. Czy jest na to jakieś rozwiązanie? Oczywiście
Przecież mamy jeszcze konstruktor MIDlet’u! Zasada jest taka – w metodzie startApp umiesczamy inicjalizację tych wszystkich obiektów, które będą zwalniane w metodzie pauseApp, do konstruktora MIDlet’u ładujemy natomiast cały pozostały kod – proste, czyż nie?
Może się jednak zdarzyć, iż nasz MIDlet nie będzie chciał wrócić „do życia”, np. gdy skończy się połączenie telefoniczne, z jakichś nieznanych powodów nie będzie możliwe powrócenie ze stanu „zapauzowany” na stan „aktywny” (gdyż np. zostanie rzucony wyjątek typu: MIDletStateChangeException). Co się dzieje wtedy? Nasza aplikacja przechodzi w trzeci stan: „stan zniszczenia”. Jak nazwa nam podpowiada – to miejsce, z którego nie możemy powrócić do „normalnego” funkcjonowania MIDletu ponieważ za chwilę zostanie on usunięty z pamięci. W metodzie tej oprogramowujemy całą logikę odpowiedzialną za zwalnianie zasobów, które wcześniej zaalokowaliśmy, zapisujemy stan aplikacji do późniejszego uruchomienia, wyłączamy dźwięki, zamykamy połączenia sieciowe, zabijamy wszystkie pracujące wątki, itd.. Deklaracja metody którą musimy oprogramować wygląda następująco:
-
public abstract void destroyApp(boolean unconditional) throws MIDletStateChangeException;
Jak widzicie, metoda ta przyjmuje parametr – unconditional, z którego zrozumieniem może być troche problemów. Generalnie – parametr ten przeważnie ma wartość „true” – co oznacza, że MIDlet nie może wpływać na przebieg procesu jego zamykania. Tak więc gdy gdzieś w kodzie znajdzie się fragment:
-
…
-
destroyApp (true);
-
notifyDestroyed ();
-
…
-
…
wirtualna maszyna Javy wykona kod znajdujący się w tej metodzie (zwolnienie zasobów) a następnie wywoła metodę notifyDestroyed (o której za chwilę). Gdy jednak do tej metody przekażemy argrument „false” musimy pamiętać by umieścić ten kod wywołujący w bloku „try-catch”. Teraz, gdy maszyna Javy wykona kod znajdujący się w metodzie destroyApp, nie przejdzie do dalszego wykonywania instrukcji (a więc metoda notifyDestroyed jako kolejna się nie wykona) – tylko wyrzuci wyjątek typu MIDletStateChangeException, którego blok obsługi powinien pozostać raczej pusty. Tak więc, poprawny kod dla tego przypadku wygląda następująco:
-
…
-
try
-
{
-
destroyApp (true);
-
notifyDestroyed ();
-
}
-
catch (MIDletStateChangeException ex)
-
{
-
}
-
…
-
…
Do czego służy, o tajemniczo brzmiącej nazwie metoda notifyDestroyed? Ma ona za zadanie poinformować wirtualną maszynę Javy: tak, skończyłem już swoją pracę, możesz mnie usunąć z pamięci
Jakie to proste! Ważne dla nas jest zapamiętać następujące fakty:
- gdy maszyna Javy sama chce zakończyć naszą aplikację (bo np. brakuje pamięci lub wystąpił wyjątek), wywoła ona metodę destroyApp z parametrem „true” – co spowoduje wywołanie kodu w niej się znajdującego i bezwarunkowe wyjście z aplikacji. Metoda notifyDestroyed NIE JEST już wtedy wywoływana,
- kiedy sami chcemy zakończyć pracę MIDletu (skończył obliczenia, użytkownik wybrał z menu opcję „Wyjście”, etc) – musimy ręcznie wywołać metodę notifyDestroyed, a za kilka chwil maszyna Javy usunie nasz program z pamięci. Uwaga! W tym przypadku NIE JEST również wywoływana „magicznie” metoda destroyApp, tuż przed wyjściem z aplikacji ponieważ maszyna Javy zakłada, iż aplikacja już zwolniła swoje zasoby jeśli chce być zamknięta. Dlatego – zaleca się taki schemat postępowania jak przedstawiłem powyżej – najpierw wywołanie metody: destroyApp, następnie notifyDestroyed. Kropka.
Aha – jeżeli myślicie o poprawnym zamykaniu MIDletu za pomocą takich „sztuczek” jak System.exit () – nie uda się Wam to, gdyż maszyna Javy rzuci wtedy wyjątkiem typu SecurityException. Tak więc używanie wspomnianych wyżej funkcji jest „jedyną słuszną” metodą na zamknięcie naszej aplikacji.
Co ciekawe, MIDlet może jeszcze wywołać dwie metody, które wpływają na jego stan. Pierwsza z nich:
-
public final void notifyPaused( );
służy do poinformowania systemu, iż MIDlet ma się znaleźć w stanie „zapauzowania”. Występuje tu jednak ta sama zależność jak przy stanie „zniszczenia” – a więc, gdy ręcznie wywołamy tę metodę, MIDlet NIE WYKONA kodu znajdującego się w metodzie pauseApp, gdyż zakłada iż metoda ta została już przetworzona (dzieje się tak automatycznie, gdy aplikacja zostanie zapauzowana przez samą maszynę Javy). Musimy wobec tego stosować się do tego samego wzoru jak opisałem powyżej.
Ostatnia z metod do obsługi stanu aplikacji:
-
public final void resumeRequest( );
jest „przeciwieństwem” metody notifyPaused. Informuje ona platformę Javy, że ze stanu „zapauzowania” chcemy z powrotem przejść w stan „aktywny”. Po kilku chwilach aplikacja może zostać wznowiona poprzez ponowne wywołanie metody startApp. Omawiana metoda zwykle jest wywoływana przez zewnętrze wątki aplikacji, bądź timer’y które zostały uaktywnione gdy aplikacja została zawieszona.
Uff! Dużo tego, na razie tyle teorii nam wystarczy – czas zastosować ją w praktyce. Przewińcie stronę kilkadziesiąt centymetrów wyżej – zamieściłem tam kod projektu na którym teraz ćwiczymy. Przeglądając go na pewno, w jego dolnej części zauważycie trzy najważniejsze metody każdego MIDletu – a więc: startApp, pauseApp i destroyApp. Pozostałym kodem nie przejmujcie się – został on wygenerowany przez NetBeans’a i służy (jak się wczytacie) do wygodnego korzystania z często powtarzających się zadań przy programowaniu MIDletu. Na chwilę obecną możemy jednak o wszystkim poza tymi trzema metodami – zapomnieć.
Naszą zabawę rozpoczniemy z pierwszą z nich: startApp. Usuńcie kod w niej się znajdujący i zastąpcie go tak, by funkcja wyglądała w następujący sposób:
-
public void startApp ()
-
{
-
Display display = getDisplay();
-
Form form = new Form ("Java ME – czesc II");
-
StringItem stringItem = new StringItem ("Moja druga aplikacja JavaME\n", ":)");
-
form.append (stringItem);
-
display.setCurrent(form);
-
}
Resztę kodu pozostawcie bez zmian. Po skompilowaniu i uruchomieniu Twoim oczom ukaże się piękny widok:
Gratuluję! Udało Ci się stworzyć aplikację dla JavyME edytując kod źródłowy
. To dopiero początek, a możliwości są niemałe. Zanim przejdziemy dalej – kilka słów wyjaśnienia dla kodu jaki dodaliśmy. Zaczęliśmy od:
-
Display display = getDisplay();
Dzięki tej linijce, do zmiennej display możemy pobrać referencję na aktualny „ekran” (o ekranach mówiłem na poprzedniej lekcji) – dzięki czemu będziemy mogli na nim rysować. Zauważcie, że metody getDisplay nigdzie w kodzie nie ma zdefiniowanej a mimo to ją wywołujemy. Jak to możliwe? Nasza klasa dziedziczy przecież po klasie MIDlet więc możemy podejrzewać, że to w niej jest ta „magia” zdefiniowana (podejrzenia całkiem słuszne
) – i jak się później okaże dzięki metodom tej klasy zrobimy jeszcze kilka pożytecznych rzeczy. Mając referencje na ekran tworzymy obiekt klasy Form:
-
Form form = new Form ("Java ME – czesc II");
Klasa Form służy do definiowana obiektów które będą zajmować wirtualnie nasz cały ekran i będzie możliwe umieszczanie w nich tzw. kontrolek. O co chodzi? Występuje tu ta sama analogia co w programowaniu komputerów. Klasa Display to odpowiednik naszego komputerowego ekranu, pulpitu. Natomiast klasa Form – to nic innego jak okienko aplikacji. Kontrolka natomiast to wszystko to, co występuje w oknie, a więc przyciski, listy wyboru, przyciski wyboru (tzw. checkbox, radiobutton) oraz etykiety (czyli pola wypełnione tekstem którego nie możemy edytować). Kontrolek jest wystarczająco dużo by poświęcić im tylko jeden rozdział, dlatego też w kolejnych lekcjach będę je sukcesywnie omawiał. Na razie poznaliśmy najważniejszy element „budulcowy” aplikacji – a więc formę (będę używał bardziej swojsko brzmiącego określenia – formatkę) na której umieszczamy pozostałe elementy. Zauważcie, że sama formatka nie ma reprezentacji wizualnej, czyli nie ma ramki, przycisków maksymalizacji, minimalizacji, ikonki, itd. – czyli tego wszystkiego co występuje w systemach operacyjnych na naszych komputerach. Wszystko to dlatego, by nie marnowała niepotrzebnie miejsca na ograniczonym ekraniku naszego telefonu, a dodatkowo – w danej chwili, na ekranie może być widoczne TYLKO jedna formatka, tak więc nie ma możliwości zmiany ich rozmiaru (choć jest możliwość przełączania się między nimi). Formatka posiada za to tytuł, który jest widoczny w jej górnej części i który – jak się domyślacie przekazany został w jej konstruktorze. Następnie tworzymy naszą pierwszą kontrolkę:
-
StringItem stringItem = new StringItem ("Moja druga aplikacja JavaME\n", ":)");
Klasa StringItem, dziedzicząca po Item reprezentuje zwykłe, statyczne pole tekstowe (nie możemy go edytować z poziomu telefonu), które może służyć, np. jako pole informacyjne. Przyjmuje ona dwa parametry: pierwszy z nich określa etykietę czyli dodatkowy opis do naszego tekstu, nastomiast drugie – tekst właściwy. Po pomyślnym stowrzeniu tej kontrolki NIC na ekranie nie zobaczycie
Dzieje się tak dlatego – iż mimo że rezyduje ona już w pamięci, to nie jest skojarzona z żadną formatką (a jak pamiętacie – formatka jest miejscem do przechowywania kontrolek właśnie). Aby związać ze sobą te dwa obiekty potrzebna jest linijka:
-
form.append (stringItem);
która spowoduje dodanie naszej kontrolki do listy kontrolek kontrolowanych przez formatkę. Teraz – jakiekolwiek działania na formatce będą wpływały również na naszą kontrolkę (co umożliwi np. jej rysowanie). Mimo to – na ekranie i tak jeszcze nic nie zobaczycie
Dopiero ostatnia linijka:
-
display.setCurrent(form);
spowoduje przypisanie naszej formatki do aktualnego ekranu naszej aplikacji – co spowoduje jej wyświetlenie. Metoda setCurrent klasy Display przyjmuje argument typu Displayable jako argument – a skoro każda klasa typu Form implementuje ten interfejs – może zostać ustawiona jako aktualny ekran – ot, i cała magia!
Czas na obslugę metody pauseApp. Tak samo jak poprzednio, umieśćcie w niej następujący kod:
-
public void pauseApp ()
-
{
-
licznik ++;
-
}
Na samym dole aplikacji dodajcie następujące pole:
-
private int licznik = 0;
A do metody startApp dopiszcie na końcu następujące linijki:
-
pauseApp ();
-
notifyPaused ();
-
resumeRequest ();
(będą one symulować przychodzące połączenie do Waszego telefonu – a więc umożliwią wprowadzanie aplikacji w stan „zapauzowania”). Po skompilowaniu i uruchomieniu – zauważycie że metody startApp oraz pauseApp wywoływane są naprzemiennie co powoduje wyświetlanie zmieniającego się licznika naszej aplikacji:
Super! Widzimy że wszystko działa jak należy. Pamiętajcie jednak że metoda ta nie powinna być wywoływana z wnętrza naszej aplikacji – tym zajmuje się automatycznie maszyna Javy – naszym zadaniem jest tylko poprawne jej obsłużenie – czyli zatrzymanie aktualizacji przebiegu wszystkich wątków naszej aplikacji (spokojnie, aplikacje wielowątkowe również będziemy pisać).
Czas na ostatnią metodę – kończenie pracy naszego MIDletu. Jak pamiętacie – możemy to wymusić programowo dzięki wywołaniu metody notifyDestroyed. Zedytujmy więc kod naszej metody startApp tak, aby wyglądała następująco:
-
public void startApp ()
-
{
-
Display display = getDisplay();
-
Form form = new Form ("Java ME – czesc II");
-
stringItem = new StringItem ("Licznik: ", Integer.toString (licznik));
-
form.append (stringItem);
-
-
display.setCurrent (form);
-
-
pauseApp ();
-
notifyPaused ();
-
resumeRequest ();
-
-
if (licznik > 20)
-
{
-
try
-
{
-
notifyDestroyed ();
-
}
-
catch (InterruptedException ex)
-
{
-
ex.printStackTrace ();
-
}
-
}
-
}
Interesujące dla nas w tym przypadku są linijki zaczynające się od warunku:
-
if (licznik > 20)
Jeśli licznik ma określoną wartość, wymuszamy na aplikacji jej zamknięcie poprzez wysłanie do maszyny Javy zapytania w metodzie notifyDestroyed. Ponieważ maszyna Javy nie widzi ku temu przeciwwskazań (a czemu miałaby?) zamyka posłusznie naszą aplikację. Zagadka na przypomnienie – czy wywołana zostanie metoda destroyApp? Aby to sprawdzić: do metody startApp zaraz poniżej wywołania metody:
-
notifyDestroyed ();
dodajmy linijkę:
-
Thread.sleep (4000);
co spowoduje uśpienie wykonania naszej aplikacji na 4 sekundy tuż przed jej zakończeniem, natomiast do sama metoda destroyApp powinna wyglądać następująco:
-
public void destroyApp (boolean unconditional)
-
{
-
stringItem.setText ("Konczymy…");
-
}
co powinno spowodować przypisanie nowego napisu do naszej kontrolki. Gdy uruchomimy aplikację, okazuje się, że…tekst na kontrolce w ogóle się nie zmienia – a znaczy to, że zgodnie z przewidywaniami – nasza implementacja metody destroyApp nie została uruchomiona. Aby to zrobić, musimy ją jawni wywołać zaraz przed wywołaniem metody notifyDestroyed w metodzie startApp, która ostatecznie powinna wyglądać następująco:
-
public void startApp ()
-
{
-
Display display = getDisplay();
-
Form form = new Form ("Java ME – czesc II");
-
stringItem = new StringItem ("Licznik: ", Integer.toString (licznik));
-
form.append (stringItem);
-
-
display.setCurrent (form);
-
-
pauseApp ();
-
notifyPaused ();
-
resumeRequest ();
-
-
if (licznik > 20)
-
{
-
try
-
{
-
destroyApp (true);
-
notifyDestroyed ();
-
-
Thread.sleep (2000);
-
}
-
catch (InterruptedException ex)
-
{
-
ex.printStackTrace ();
-
}
-
}
-
}
Dopiero teraz naszym oczom ukaże się oczekiwany rezultat:
Na tym dziś skończymy. Z wydawałoby się prostego tematu jakim jest wprowadzenie do MIDletów, powstała dość rozbudowana lekcja, która mam nadzieję – nie zamieszała Wam zbytnio w głowach
Dzisiaj dowiedzieliście się:
- czym jest MIDlet, jakie są jego składowe i jaka jest jego fizyczna reprezentacja na dysku twardym,
- jakie stany występują w MIDletach, jak się pomiędzy nimi przełączać, czego ustrzegać się podczas ich zarządzania,
- jak wygląda podstawowy szkielet aplikacji JavaME,
- kilku zdań wprowadzenia o budowaniu interfejsów użytkownika na urządzenia mobilne z wykorzystaniem API „wysokiego” poziomu.
Na kolejnej lekcji poznamy sposób obsługi komunikatów w MIDletach, dzięki czemu będziemy mogli odpowiadać w naszej aplikacji na reakcje użytkownika (naciśnięcia klawiszy).
Dziękuję Wam wszystkim za uwagę ![]()
Do usłyszenia!


























