Masarykova univerzita, Fakulta informatiky
Knihovna pro tvorbu GUI v mobilních zařízeních Vojtěch Štursa
Diplomová práce
Brno 2008
Prohlášení: Prohlašuji, že tato práce je mým původním autorským dílem, které jsem vypracoval samostatně. Všechny zdroje, prameny a literaturu, které jsem při vypracování používal nebo z nich čerpal, v práci řádně cituji s uvedením úplného odkazu na příslušný zdroj.
Děkuji Mgr. Pavlu Drášilovi za odbornou pomoc a vedení při vytváření této diplomové práce
Shrnutí: Cílem práce je prozkoumat současné knihovny pro tvorbu uživatelských rozhraní pro mobilní zařízení. Součástí práce je vytvoření takové knihovny pro platformu J2ME a ukázková aplikace.
Klíčová slova: J2ME, CLDC, grafické uživatelské rozhraní, MIDP, XML
Obsah 1. Úvod..........................................................................................................................5 1.1 Rozdělení mobilních telefonů do kategorií:.......................................................5 1.2 Omezující faktory při vývoji programů pro mobilní telefony...........................6 1.3 Optimalizace......................................................................................................6 1.4 Přehled programového vybavení........................................................................7 2. J2ME.......................................................................................................................10 2.1 Architektura......................................................................................................10 2.2 Konfigurace......................................................................................................11 2.3 Profily...............................................................................................................12 2.4 Vývoj J2ME aplikací........................................................................................12 2.4.1 Další optimalizace....................................................................................13 2.4.2 Struktura programu J2ME........................................................................13 2.5 MIDP API.........................................................................................................17 2.5.1 High-Level API........................................................................................18 2.5.2 Low-level API..........................................................................................23 2.5.3 Ostatní API...............................................................................................24 2.6 Grafická uživatelská rozhraní pro vývoj mobilních aplikací...........................25 2.6.1 J2ME Polish..............................................................................................25 2.6.3 Light-weight visual component library....................................................27 2.6.4 MicroEWT................................................................................................28 2.6.5 Paxmodept GUI........................................................................................29 2.6.6 Shrnutí......................................................................................................29 3. Vlastní knihovna pro tvorbu GUI na mobilních telefonech....................................31 3.1 Model knihovny...............................................................................................31 3.2 Implementace modelu .....................................................................................35 3.2.1 Sada komponent.......................................................................................35 4. Ukázková aplikace – Mapa Brna............................................................................37 4.1 Příprava dat......................................................................................................37 4.1.1 Data assembler..........................................................................................39 4.1.2 Tile create.................................................................................................40 4.1.3 Tile merge.................................................................................................41 4.2 Vzhled a ovládání aplikace..............................................................................41 4.3 Struktura aplikace.............................................................................................48 5. Literatura.................................................................................................................53
1. Úvod Současné mobilní telefony již nejsou pouhým komunikačním zařízením, ale implementují i řadu dalších, zejména multimediálních funkcí. Přehrávají hudbu, umí pořizovat digitální fotografie a prakticky všechny novější mobilní telefony obsahují podporu pro další programové vybavení. Každoročně se prodá okolo jedné miliardy mobilních telefonů a očekává se, že toto množství se bude dále zvětšovat. Mobilní telefon se tedy již stal běžnou součástí lidského života, životního stylu a zdrojem zábavy. Přestože jsou mobilní telefony tak rozšířené, věřím, že jejich potenciál ještě není plně využit, protože kromě samotné komunikace se totiž nabízí možnost spouštět na nich programy třetích stran. Na telefonech bez vlastního operačního systému jsou však těmito programy stále převážně hry. Těchto zařízení je nejvíce a ačkoli jsou relativně levné, poskytují dostatečný výkon pro řadu zajímavých aplikací.
1.1 Rozdělení mobilních telefonů do kategorií: Podle schopností mobilních telefonů bych je rozdělil do následujících kategorií: Telefony bez možnosti spouštět programy. Jedná se o nejjednodušší typy, umožňující pouze telefonovat, posílat textové zprávy a používat napevno implementované funkce. Zpravidla mají malý displej, často pouze monochromatický. ● Telefony s podporou Java2ME. Jsou to zařízení nižší a střední třídy, nemají vlastní operační systém (tedy ne takový, který by byl společný pro více druhů zařízení), přesto umožňují spouštět programy v jazyce Java. Tato zařízení mají barevný displej, odpovídající procesor a řadu komponent, které může aplikační programátor využít - například fotoaparát, videokameru, některé typy mají i akcelerátor 3D grafiky, infraport, bluetooth modul či polyfonní syntezátor MIDI. Dále programátor může použít funkce pro odesílání textových zpráv SMS, MMS, případně funkce pro komunikaci přes internet. ● Telefony s vlastním operačním systémem. Tyto telefony, označované též jako smartphones, umožňují totéž, co druhá kategorie, ale mají ještě rychlejší procesor, velký, většinou dotykový displej a poskytují ještě větší možnosti programování, především v jazyce C. Příkladem operačního systému je například Symbian OS, Windows Mobile a začíná se uplatňovat i Linux. ●
5
1.2 Omezující faktory při vývoji programů pro mobilní telefony Mobilní telefony jsou navržené tak, aby byly snadno přenositelné. To znamená co nejmenší rozměry, váhu a spotřebu. Nejvýznamnějším faktorem je právě spotřeba a té se podřizuje vše ostatní. Výrobci musí volit jistý kompromis, protože uživatelé chtějí být schopni komunikovat co nejdéle, ale zároveň nechtějí v kapse nosit nic těžkého. Zároveň si řada uživatelů potrpí na vzhled a eleganci svého telefonu, proto vzniká řada variant, úsporných režimů apod. Z pohledu programátora jsou zajímavé následující parametry: ● maximální velikost programu, který je možné spustit (zde může jít o velikosti od 64 kB do desítek MB) ● maximální velikost paměti, kterou je možné alokovat (maximálně jednotky MB) ● možnost pracovat s externími soubory, uložené například na kartě flash (tyto karty jsou dnes schopny pojmout až 4 GB dat) ● možnost uchovávat konfigurační data aplikace, velikost této paměti a počet konfiguračních záznamů (řádově jednotky kB)
1.3 Optimalizace Optimální využití paměti je náročný úkol a někdy je nutné používat různé programátorské triky a optimalizace, obzvlášť v případě programů pro nejstarší typy telefonů. Například mobilní telefon může mít určenou část paměti, která je rychlá, ale může obsahovat pouze obrazová data. Může tedy záležet třeba i na pořadí načítaní programových objektů. Pokud chceme snížit velikost obrazových dat (zejména obrázky ve formátu PNG), můžeme optimalizovat například počet barev, použít lepší kompresi, spojit obrázky do jednoho (čím víc souborů, tím je potřeba více záznamů o nich) a dokonce se někdy odstraňují i datové hlavičky formátu PNG, pokud jsou u více obrázků totožné. Dále se při tvorbě programů používají tzv. obfuskátory, což jsou programy, s jejichž pomocí jsme schopni z binárních programových souborů odstranit nadbytečné informace. Je tak možné např. odstranit nepoužité atributy tříd, nahradit set/get metody přímým přístupem k proměnné, vyřadit z kódu nepoužité metody apod. Existuje řada optimalizačních postupů, jak z hlediska rychlosti, tak výsledné velikosti programu. Pokud jde o optimalizaci samotného algoritmu, vždy platí zásada, že optimalizujeme ne to, co si myslíme, že je pomalé, ale to, co zjistíme programem označovaným jako profiler. Je to nástroj, který je schopen v programu nalézt často volané funkce a poskytnout o nich tabulkový přehled. Urychlíme-li totiž část, která běží 6
většinu času o několik procent, bude program rychlejší, než když výrazně urychlíme část, která je v programu prakticky nepoužitá. Nehledě na čas, který na to potřebujeme. Dalším užitečným postupem je v případě vykreslování grafických objektů na displej vykreslovat jen to, co je nutné. Tedy jen to, co se od posledního kreslení na displeji změnilo. Příkladem buď například u her hráčovo skóre, umísťované do rohu displeje. To zabírá třeba 10 % displeje, ale prakticky se nemění. Optimalizace může jít až tak daleko, že je při aktualizaci skóre možné měnit jen změněné číslice. Na druhou stranu při takovýchto optimalizacích narůstá velikost kódu a tedy musíme volit jistý kompromis.
1.4 Přehled programového vybavení Pro vývoj programů pro mobilní telefony potřebujeme několik nástrojů. Kromě textového editoru pro psaní kódu je nutný příslušný kompilátor jazyka java, případně C/C++. Velmi vhodný je i emulátor mobilního telefonu pro PC, který nás částečně zbaví nutnosti testovat náš program ve fyzickém zařízení. Zároveň nám emulátor umožní nahlédnout, jak se program bude chovat na zařízeních, které nemáme k dispozici. Nelze však na něj úplně spoléhat, protože ne vždy je možné emulovat všechny funkce na počítači a také mezi jednotlivými verzemi firmware mobilních telefonů bývají drobné rozdíly. Problémové je také emulování některých pokročilých funkcí mobilních zařízení, například ovládání videokamery, připojování externích zařízení přes rozhraní bluetooth či používání infraportu. Ačkoli se výrobci snaží tyto problémy řešit, testování na fyzickém zařízení se nevyhneme. Nejčastěji pro vývoj programů použijeme některé z dostupných IDE (Integrated development environment), které v sobě obsahuje potřebné programy a nastavení. Uveďme přehled nejpoužívanějších IDE: ● ● ● ● ● ● ● ●
Netbeans IDE 6.x + Mobility packs (J2ME, CLDC i CDC) Eclipse + Mobile tools (J2ME, CLDC i CDC) Nokia Carbide.c++ (C++, Symbian OS) Nokia Carbide.vs (plugin pro MS Visual studio, Symbian OS) Nokia Developer's Suite for J2ME Borland JBuilder X Mobile Edition (J2ME) Visual C++ (C++, Windows Mobile) Sun Java Studio Mobility (J2ME)
Součástí většiny programových balíků týkajících se javy pro mobilní telefony je J2ME Wireless Toolkit od firmy Sun. Jedná se o nástroj, obsahující potřebné programy pro sestavení a emulaci J2ME programů, včetně příkladů a dokumentace. 7
Dostupné emulátory: Prakticky všichni významní výrobci poskytují podporu a dokumentaci ke svým výrobkům, označovanou jako SDK. Příklad: ● ● ● ●
Sony Ericsson SDK (modelová řada W, P, Z, ...) Nokia SDK (serie 40, 60, 80) Motorola SDK (řada A, C, E, V, RAZR, ...) dále pak Alcatel, Sharp, Samsung, LG, ...
Součástí emulátorů je několik užitečných nástrojů. Prvním je monitor využité paměti, který nám umožňuje zjistit vývoj paměťových nároků aplikace v čase a také velikost paměti, kterou potřebují jednotlivé objekty aplikace. Druhým nástrojem je již zmíněný profiler. Zobrazuje stromovou strukturu s metodami objektů, přičemž pro každý uzel udává v procentech čas, po který aplikace danou metodu využívala. Dále můžeme pomocí profileru zjistit, kolikrát byla metoda volána a počet provedených instrukcí.
Obrázek 1: Netbeans IDE 6.0
8
Obrázek 2: Emulátor, Sony Ericsson SDK
Obrázek 3: Memory monitor
Obrázek 4: Profiler
9
2. J2ME 2.1 Architektura J2ME je běhové prostředí pro programy napsané v jazyce java. Pomocí tzv. konfigurací a profilů je možné toto prostředí uzpůsobit pro řadu zařízení. Používá se v mobilních telefonech, pagerech, navigačních systémech, set-top boxech a dalších zařízeních. Velkou výhodou J2ME je to, že jeden program je teoreticky schopen běžet na více různých zařízeních, protože jazyk je pořad jeden a tentýž. Jednotlivé typy zařízení se však mohou lišit rozsahem implementovaných knihovních funkcí a dalšími parametry. Normou je dána minimální množina funkcí, která je pro všechna zařízení společná. Konfigurací rozumíme množinu základních tříd (core classes) a parametry JVM (java virtual machine). Profil pak k základní množině tříd přidává další funkcionalitu podle toho, jaké další funkce zařízení poskytuje. Zařízení nemusí obsahovat úplnou JVM, ale pouze její podmnožinu (KVM, CVM).
J2ME App.
J2ME App.
MIDP
Foundation profile
CLDC
CDC
KVM
CVM
J2ME App.
J2SE
JVM
Operační systém zařízení Obrázek 5: Struktura běhového prostředí, od jednoduchých zařízení po složité
10
2.2 Konfigurace V současné době jsou definovány pouze dvě konfigurace J2ME – CLDC a CDC. V budoucnosti však mohou vzniknout i další. Connected Limited Device Configuration (CLDC) je určena pro 16 a 32 bitová zařízení s omezeným množstvím paměti (minimálně 192 kB). Tato konfigurace a KVM (virtuální stroj) se používá pro malé aplikace, zpravidla napájené z baterie. Programátor musí respektovat paměťová a výkonová omezení těchto zařízení. Cíle konfigurace CLDC: ●
●
●
Nízké nároky na zdroje. Mobilní telefony a podobná zařízení jsou vyráběna ve velkém množství (desítky až stovky miliónů kusů ročně). Chce-li výrobce zvýšit svůj zisk, musí hledat cesty, jak minimalizovat cenu za jeden kus. Proto jsou výkonnější procesory a větší paměť instalovány teprve tehdy, až má výrobce jistotu, že budou tyto zdroje smysluplně využity a zákazníci za ně zaplatí. Zaměřit se nikoli na systémové, ale na aplikační programy. CLDC je zamýšlena především jako platforma pro vývoj aplikací. To znamená, že by měla obsahovat především knihovny vyšší úrovně a zároveň by měla být dostatečně obecná a portabilní. Podpořit vývoj aplikací třetích stran. Dříve bylo běžné, že zařízení byla napevno naprogramována z výroby a nebylo možné reagovat na potřeby zákazníků. CLDC se snaží podpořit vznik nových, rozšiřitelných zařízení. Součástí je možnost stahování aplikací bezpečným způsobem přes internetovou síť.
Connected Device Configuration (CDC) je určena pro 32 bitová zařízení, obsahující alespoň 2 MB paměti. Příkladem těchto zařízení jsou například navigační systémy do automobilů apod. Jako virtuální stroj je použito CVM (C virtual machine) Cílem konfigurace CDC je podpořit vznik sofistikovanějších aplikací. Díky většímu displeji, výkon, paměťovým možnostem a dostupným knihovnám je možné vytvářet i složité aplikace.
11
2.3 Profily Profil definuje typ zařízení a přidává další funkcionalitu. Je postaven nad konfigurací. Pro CLDC jsou definovány dva profily: ● Kjava ● Mobile Information Device Profile (MIDP) Pro CDC jsou definovány profily tři: ● Foundation Profile je určen pro zařízení, která potřebují částečnou, případně kompletní podporu pro J2SE ● Personal Basis Profile obsahuje totéž co Foundation Profile, navíc přidává část knihovny AWT (Abstract Window Toolkit) ● Personal Profile pak navíc obsahuje náročnější komponenty AWT a vylepšenou JVM
2.4 Vývoj J2ME aplikací Při návrhu aplikace bychom měli zvážit několik věcí: ● Jednoduchost. Aplikace na malém zařízení by měla umět jen to, co je pro uživatele nezbytné. Můžeme zvážit, zda větší aplikaci nerozdělíme do několika menších. ● Menší je lepší. Menší aplikace pochopitelně zabírá menší množství paměti a rychleji se spouští. Můžeme zvážit, zda použijeme kompresi. ● Minimalizace paměti při běhu. Pro menší paměťové nároky můžeme použít skalární proměnné místo objektů. Dále můžeme pomoci správě paměti přiřazením hodnoty null objektům, které již nebudeme dále potřebovat. Můžeme použít i tzv. línou instanciaci objektů – vytváříme je jen tehdy, až když jsou potřeba ● Co všechno může zpracovat server. Při vývoji mobilních aplikací můžeme část práce přenechat vzdálenému serveru a ke zpracovaným informacím přistupovat přes síť. ● Volba jazyka. Některá mobilní zařízení jsou schopna spouštět programy pro J2ME i programy napsané v jazyce C/C++. Můžeme tedy volit podle situace. Programy napsané v C/C++ mají zpravidla přístup k funkcím nižší úrovně. Pokud zařízení umožňuje běh více aplikací současně, je možné aplikaci rozdělit do více menších aplikací. Každá pak může být napsána v jiném jazyce.
12
2.4.1 Další optimalizace Uveďme příklad optimalizací používaných v J2ME: ● Lokální proměnné. Je rychlejší přistupovat k lokální proměnné, než k atributu třídy. Proto pokud často přistupujeme k nějakému atributu třídy (například v cyklu), měli bychom si tuto hodnotu napřed uložit v lokální proměnné. ● Spojování řetězců. Spojování řetězců vede ke zpomalení aplikace a také ke zvyšování paměťových nároků, zejména pokud jsou řetězce dlouhé. ● Použití vláken. Pokud nějaká akce aplikace trvá déle než 100 ms, je vhodné ji spustit v samostatném vlákně. Uživatel by měl mít vždy dojem, že aplikace běží a vždy by měl mít možnost prováděnou akci ukončit či přerušit. To platí zejména pro navazování komunikaci přes internetové spojení apod. ● Použití MVC modelu. Je vhodné rozdělit aplikaci na model, zobrazení a ovládání. Usnadníme si tak modifikaci aplikace v budoucnu. V každém případě bychom neměli slučovat model (data a jejich správu) se zobrazováním. 2.4.2 Struktura programu J2ME V této sekci si popíšeme, jaké třídy potřebujeme pro vývoj aplikace a ukážeme kostru aplikace. Konfigurace CLDC obsahuje následující balíky tříd: ●
java.io – vstup a výstup pomocí datových proudů
●
java.lang – základní třídy jazyka java (Integer apod.)
●
java.util – kolekce a funkce pro čas a datum
●
javax.microedition.io – obecné funkce pro konektivitu
Sami vidíme, že pro psaní použitelných programů, zejména takových, které by komunikovali s uživatelem, nám samotné CLDC nestačí. Další funkcionalitu nám poskytuje profil MIDP. Balíky tříd MIDP 2.0: ●
javax.microedition.lcdui - uživatelský interface
●
javax.microedition.rms - persistence dat –
●
javax.microedition.midlet - životní cyklus aplikace
●
javax.microedition.io - síťové funkce
●
javax.microedition.media - audio
●
javax.microedition.pki - autentizace
13
Základní programovou jednotkou v J2ME je tzv. midlet. Pomocí midletu jsme schopni řídit životní cyklus aplikace. Aplikaci vytváříme tak, že nejprve vytvoříme potomka třídy MIDlet a předefinujeme některé jeho metody. Prázdný midlet by mohl vypadat takto: import javax.microedition.midlet.*; public class MainMidlet extends MIDlet { public void startApp() { } public void pauseApp() { } public void destroyApp(boolean unconditional) { } }
Midlet se může nacházet v několika stavech, přičemž programátor může na každý stav reagovat. Midlet mezi těmito stavy přechází buď následkem vnější události (uživatel přerušil běh aplikace apod.), nebo na žádost programu. Stavy v životním cyklu midletu: ● Paused. Midlet je inicializován a neběží. V tomto stavu by neměl držet žádné zdroje ● Active. Midlet běží normálním způsobem. ● Destroyed. Midlet uvolnil držené prostředky a neběží. Do tohoto stavu se lze dostat pouze jednou. pauseApp
midlet by měl uvolnit všechny zdroje
startApp
midlet by měl alokovat zdroje a pokračovat v běhu
destroyApp
midlet by měl uložit svůj stav a uvolnit alokované zdroje
notifyDestroyed
touto metodou dává midlet upozornění řídícímu software, že zdroje uvolnil a je připraven ukončit činnost
notifyPaused
midlet dává upozornění řídícímu software, že přešel do stavu Paused
resumeRequest
midlet žádá řídící software o opětovné spuštění
getAppProperty
dotaz na vlastnosti běhového prostředí
Tabulka 1: MIDlet interface
14
new( )
destroyApp( ) Paused
pauseApp( )
startApp( )
Destroyed
Active destroyApp( ) Obrázek 6: Životní cyklus midletu 2.4.3 Sestavení aplikace K sestavení programu je potřeba několik kroků: Přeložení zdrojového kódu – kompilátor používáme stejný jako pro J2SE programy. Rozdíl je pouze v tom, že musíme zadat cestu ke knihovnám zvolené platformy. Výsledkem jsou class soubory. javac -bootclasspath \midp\classes MainMidlet.java
Preverifikace – oproti J2SE je potřeba jeden krok navíc. Verifikace je proces, při kterém se ověřuje korektnost a správné chování class souborů vůči JVM. Problém je v tom, že tento proces je náročný na paměť, které se mobilním zařízením nedostává. CLDC definuje dvoufázový proces, který tento nedostatek odstraní: 1. Class soubory jsou preverifikovány mimo zařízení. Je provedeno několik kontrol a do class souboru se přidá informace, která umožní rychlé ověření tohoto kroku 2. Na mobilním zařízení se při načítání class souboru verifikace dokončí. Pokud class soubor neprojde kontrolou, potom je zamítnut.
15
Sestavení jar archivu – jar archiv kromě zmíněných class souborů může obsahovat i další soubory. Aby byla aplikace funkční, vyžaduje navíc dva speciální textové soubory - manifest a deskriptor. Manifest je soubor META-INF/manifest.mf a je přibalený k aplikaci v jar archivu. Deskriptor je soubor pojmenovaný jako jar archiv, jen s tím rozdílem, že má příponu jad. V obou souborech jsou uloženy informace o aplikaci, autorovi a parametry nutné pro její běh.
Název atributu
Popis
MIDlet-Name
název midletu
MIDlet-Version
verze
MIDlet-Vendor
dodavatel aplikace
MIDlet-Icon
cesta k ikoně midletu
MIDlet-Description
popis aplikace
MIDlet-Info-URL
url s dalšími informacemi
MIDlet-
čárkami oddělený seznam midletů v aplikaci
MIDlet-Jar-URL
url pro stažení jar souboru
MIDlet-Jar-Size
velikost jar souboru
MIDlet-Data-Size
kolik paměti aplikace potřebuje k uložení své konfigurace
MicroEdition-Profile
požadovaný profil (MIDP 2.0)
MicroEdition-Configuration
požadovaná konfigurace (CLDC 1.1)
Tabulka 2: Přehled povinných a volitelných parametrů Dále aplikace může zahrnovat multimediální data, tedy její součástí mohou být obrázky, zvuky, písničky apod. Tyto soubory jsou stejně jako class soubory součástí výsledného jar archivu. K vytvoření jar archivu použijeme program jar: jar cvmf manifest jarfile [ -C dir ] inputfiles [ -Joption ]
Volitelné kroky při sestavení aplikace: V MIDP 2.0 byla zavedena možnost digitálního podpisu aplikace. Podle toho, zda aplikace podpis obsahuje či neobsahuje, rozlišujeme aplikace na důvěryhodné a 16
nedůvěryhodné. S aplikacemi používající MIDP 1.0 se nakládá jako s nedůvěryhodnými aplikacemi. Rozdíl mezi nimi je v tom, že nedůvěryhodné aplikace mají omezen přístup k některým funkcím, případně vyžadují k některým akcím explicitní souhlas uživatele. Jedná se o funkce pro vytváření komunikačních spojení (což je zpravidla placená funkce), zápis do souboru a další. Jak přesně a přísně je možné tato omezení aplikaci nastavit záleží do jisté míry na výrobci zařízení. Pro zmenšení velikosti výsledného jar archivu můžeme použít obfuskátor a různé optimalizační a kompresní programy.
2.5 MIDP API Nyní se již můžeme pustit do tvorby samotné aplikace. První věcí, kterou bychom si měli ujasnit, je co všechno by naše aplikace měla umět. Pokud toto máme, musíme se rozhodnout, jakého typu naše aplikace bude. Existují dvě základní možnosti: ● Aplikace většinu času čeká na vstup uživatele. V takovém případě zpravidla použijeme pro uživatelské rozhraní funkce vyšší úrovně (high-level API). Máme na výběr komponenty typu text, seznam, formulář, příkazy v menu a několik dalších. Protože nemáme kontrolu nad tím, jak komponenty vypadají, výrobce má možnost aplikovat různá uživatelská schémata – barvy, okraje apod. Všechny aplikace napsané s použitím high-level API pak vypadají na jednom mobilním zařízení stejně. ● Aplikace dynamicky reaguje na akce uživatele. Takové aplikace neustále mění obsah displeje (např. hry) a při jejich tvorbě často používáme funkce nižší úrovně. Jsou to funkce například pro kreslení grafických primitiv na displej, testování stavu klávesy a další.
Obrázek 7: Vztah mezi komponentami MIDP
17
2.5.1 High-Level API Při spouštění midletu je třeba vytvořit hlavní obrazovku. Docílíme toho tak, že vytvoříme instanci některého potomka třídy Screen, nastavíme jeho vlastnosti a takto získaný objekt nastavíme jako aktuální obrazovku naší aplikace: public void startApp() { List list = new List(...) Display.getDisplay(this).setCurrent(list); }
Při tvorbě aplikace pomocí high-level API používáme sadu komponent. Pojďme se na tyto komponenty podívat podrobněji: ● Alert je obrazovka, jejímž účelem je informovat uživatele o nějaké události (chyba či jiná významná událost) Může obsahovat text, nadpis a volitelně i obrázek. Po zobrazení alertu se čeká zadanou dobu a poté se zobrazí zadaný objekt typu Displayable. Alert může být modální či nemodální a také je schopen zobrazovat zvolený indikátor průběhu. Alert také může upozornit uživatele zvukovým efektem. ● List je obrazovka, která umožňuje vybírat položku z několika variant. Varianta EXCLUSIVE dovolí vybrat právě jednu položku, varianta MULTIPLE několik. ● TextBox je obrazovka umožňující uživateli zadat textový řetězec. Jeho délka je omezená a na řetězec mohou být kladeny speciální omezení, jako například možnost vkládat pouze číslice, URL apod. ● Form je obrazovka sdružující několik dalších komponent. S její pomocí můžeme vytvářet formuláře pro zobrazování a zadávání údajů. Můžeme použít následující formulářové komponenty: • ChoiceGroup – položka, jejímž účelem je vybírat jednu nebo několik možností ze seznamu, podobně jako u obrazovky List. • DateField – slouží k zadávání datumu, času, případně pro obojí. • Gauge – zobrazuje sloupec pro zadání celočíselné hodnoty. • ImageItem – zobrazuje obrázek, může sloužit i jako odkaz. Můžeme zvolit zarovnání obrázku vzhledem k textu, případně vynutit zalomení textu před a za obrázkem. • StringItem – zobrazuje textový řetězec, případně tlačítko. Obsahuje nadpis a může fungovat jako odkaz. • TextField – zobrazuje řádek pro editaci textu. Podobně jako u obrazovky TextBox můžeme zadat omezení na zadávaný text.
18
ANY
libovolný text nepřesahující maximální délku
EMAILADDR
platná emailová adresa
NUMERIC
celočíselná hodnota
PASSWORD
zadání tajného kódu, zobrazuje hvězdičky místo znaků
PHONENUMBER
telefonní číslo v platném formátu
URL
korektní URL
Tabulka 3: Omezení na zadávaný text Všechny obrazovky mohou obsahovat příkazy (commands). Existuje jich několik typů a je na výrobci zařízení, jak zajistí jejich zobrazení. Mobilní zařízení může mít různá speciální tlačítka, např. pro akci „Zpět“ a „Konec“. Proto jsou tyto věci ponechány na zvážení výrobci. Programátor musí jen každé akci přiřadit její typ. Máme opět několik možností: ● Command.BACK – příkaz určený pro návrat na předchozí obrazovku ● Command.CANCEL – negativní odpověď na dotaz ● Command.EXIT – ukončení aplikace ● Command.HELP – akce zobrazující nápovědu ● Command.ITEM – položka zařazená k explicitní položce ● Command.OK – potvrzující odpověď ● Command.SCREEN – akce specifická pro obrazovku ● Command.STOP – akce přerušující probíhající proces Pro ilustraci použití MIDP jsem vytvořil ukázkovou aplikaci MidletTest.Tato aplikace zobrazuje většinu komponent MIDP, včetně jejich různého nastavení.
19
Následující zdrojový kód ilustruje vytvoření základní obrazovky aplikace: /* * NavigationScreen.java */ package com.xstu.midlettest; import import import import import import
javax.microedition.lcdui.Command; javax.microedition.lcdui.CommandListener; javax.microedition.lcdui.Display; javax.microedition.lcdui.Displayable; javax.microedition.lcdui.List; javax.microedition.midlet.MIDlet;
/** * * @author xstursa */ public class Navigation extends List implements CommandListener { private MIDlet midlet = null; private Command cmdExit = null; private Command cmdSelect = null; /** Creates a new instance of NavigationScreen */ public Navigation(MIDlet midlet) { super("Menu", List.IMPLICIT); this.midlet = midlet; // init commands cmdExit = new Command("Exit", Command.EXIT, 1); cmdSelect = new Command("Select", Command.ITEM, 1); addCommand(cmdExit); setSelectCommand(cmdSelect); setCommandListener(this); // create items append("Alerts", null); append("TextBox", null); append("TextFields", null); append("ChoiceGroups", null); append("List - exclusive", null); append("List - multiple", null); append("Miscellaneous", null); append("Properties", null); } public void commandAction(Command command, Displayable displayable) { if(command == cmdExit) { midlet.notifyDestroyed(); return; } if(command == cmdSelect) { int index = getSelectedIndex(); switch(index) { case 0:
20
Display.getDisplay(midlet).setCurrent( new AlertTest(midlet, this)); break; case 1: Display.getDisplay(midlet).setCurrent( new TextBoxTest(midlet, this)); break; case 2: Display.getDisplay(midlet).setCurrent( new TextFieldsTest(midlet, this)); break; case 3: Display.getDisplay(midlet).setCurrent( new ChoiceGroupTest(midlet, this)); break; case 4: Display.getDisplay(midlet).setCurrent( new ListTest(midlet, this, List.EXCLUSIVE)); break; case 5: Display.getDisplay(midlet).setCurrent( new ListTest(midlet, this, List.MULTIPLE)); break; case 6: Display.getDisplay(midlet).setCurrent( new MiscTest(midlet, this)); break; case 7: Display.getDisplay(midlet).setCurrent( new Info(midlet, this)); break; } } } }
V kódu vytvářím nejprve dvě akce – cmdSelect a cmdExit. Poté, co tyto akce přiřadím k obrazovce (potomek třídy List), vytvořím několik položek a výběr položky svážu s akcí cmdSelect. Pokud uživatel zvolí akce cmdExit, dojde ukončení aplikace, pokud zvolí některou položku, vytvoří se nová obrazovky a nastaví se jako aktuální. Součástí programu MidletTest je i výpis všech podstatných vlastností (properties) běhového prostředí. Programátor pomocí nich může zjistit například typ telefonu, kódování textu, informace o podporovaných multimediálních souborech a mnohé další, viz příloha 1.
21
Ukázka MIDP komponent, jak jsou zobrazeny v emulátoru:
22
2.5.2 Low-level API Na rozdíl od high-level API máme plnou kontrolu nad tím, co se vykresluje na displej. Základními třídami jsou Canvas a Graphics. import javax.microedition.lcdui.*; class ExampleCanvas extends Canvas { public void paint (Graphics g) { g.setColor (255, 0, 0); g.drawLine (0, 0, 50, 200); g.setColor(0, 0, 255); g.fillRect (20, 10, 50, 30); } }
Vytvoříme-li instanci ExampleCanvas a přiřadíme-li ji pomocí Display.getDisplay(this).setCurrent(ecanvas);
na obrazovku, nakreslí se nám červená čára a modrý obdélník. Samozřejmě se musíme postarat i o to, co je nakresleno na pozadí. Metody třídy Graphics nám umožňují vykreslit: ● obrázek ● obdélník vyplněný barvou nebo jen okraj ● obdélník s kulatými rohy vyplněný barvou nebo jen okraj ● úsečku ● kruhovou výseč ● text Dále máme k dispozici funkce pro ořez, translaci a několik dalších.
23
2.5.3 Ostatní API Kromě tříd MIDP profilu může aplikace používat funkce, které jsou součástí volitelných balíků. Jejich přítomnost či nepřítomnost je vhodné určit pomocí properties a při startu aplikace informovat uživatele, pokud nebylo nalezeno to, co aplikace očekává. Volitelné balíky: ● J2ME XML/RPC ● Java Card RMI API ● File Connection API ● Java API for Bluetooth Connection ● MMAPI ● Mobile 3D Graphics Optional Package ● SVG API a mnohé další, specifické například pro telefony značky Nokia.
24
2.6 Grafická uživatelská rozhraní pro vývoj mobilních aplikací Vytváření aplikací pomocí high-level API MIDP profilu má několik nevýhod. Tou první je skutečnost, že aplikace mohou na různých zařízeních vypadat odlišně. Další problém je to, že nemůžeme přidávat svoje specifické komponenty. Proto se v poslední době začínají uplatňovat knihovny pro tvorbu uživatelského rozhraní, které jsou postaveny z velké části na low-level API. Mobilní zařízení jsou dnes již tak výkonná, že rozdíl například v rychlosti je velmi malý. Uživatel tak může používat aplikace s líbivým vzhledem. Protože se jedná o relativně novou oblast, většina těchto knihoven není úplně vyzrálá. Přesto existují i dobře použitelné knihovny, bohužel jsou většinou docela drahé. Knihovny se liší též požadavky na verzi MIDP a CLDC. Novější knihovny vyžadují alespoň MIDP 2.0. Představme si některé z nich. 2.6.1 J2ME Polish J2ME Polish je ucelený programový balík pro tvorbu mobilních aplikací. Odděluje model aplikace od designu a pro nastavení vzhledu používá klasické CSS soubory. Programátor při tvorbě aplikace používá převážně high-level API, ale může použít i nové komponenty, jako například TabbedForm nebo TreeItem. Pomocí direktivy #style ve zdrojovém kódu se propojí kód s designem a následně se vzhled aplikace specifikuje pomocí CSS souboru. V CSS souboru můžeme definovat vzhled komponent, například barvu pozadí položek seznamu apod. Příklad definice stylu: .myStyle { font-color: white; font-style: bold; font-size: large; font-face: proportional; background-color: black; }
Tímto stylem jsme nastavili barvu, pozadí a písmo komponenty. Kromě direktivy #style ve zdrojovém kódu můžeme použít v CSS obecné identifikátory, např. „p“ pro textové komponenty, „a“ pro odkazy a další. J2ME Polish podporuje CSS box model, tedy můžeme nastavovat okraje, zarovnání, odsazení a další parametry (margin, border, padding). Styly se dále mohou vytvářet hierarchie a dědit své vlastnosti. K tomu slouží klíčové slovo extends, např.: .baseStyle { font-color: black; }
25
.myStyle extends baseStyle { background-color: red; font-color: white; }
Pomocí CSS můžeme komponentám nastavit celou řadu vlastností. Můžeme je rozdělit do skupin podle účelu: ● barvy ● písma ● pozadí ● okraje ● zbytek Kromě GUI nám J2ME Polish nabízí i další, pomocné funkce. Jedná se například o funkce pro ukládání dat do RMS (konfigurační paměť aplikace), funkce usnadňující lokalizaci aplikace do více jazyků a funkce pro vzdálené volání procedur. Sestavení aplikace je řízeno pomocí nástroje Ant, souboru build.xml a má několik kroků: ● výběr zařízení, pro která budeme aplikaci sestavovat ● zpracování zdrojů ● preprocesing zdrojového kódu a optimalizace pro dané zařízení ● kompilace ● optimalizace pomocí obfuskátoru ● preverifikace ● vytvoření souborů jar a jad J2ME Polish má dva druhy licence. Pro nekomerční použití se jedná o GPL, pro komeční využití má svoji vlastní. Licence pro jednu komerční aplikaci stojí kolem jednoho tisíce euro. Chystá se však možnost použít tuto knihovnu za určitých okolností zdarma. Obrázky výsledné aplikace J2ME Polish, lišící se pouze v CSS stylu a ikonách:
26
2.6.3 Light-weight visual component library LwVCL je zajímavý projekt, který zahrnuje několik programovacích jazyků a platforem. Tato GUI knihovna se snaží být řešením pro tvorbu uživatelských rozhraní pro J2SE, SWT, Microsoft .NET, je vypracován koncept i pro J2ME a dá se portovat např. do jazyka Python. Je to možné díky použití velké míry abstrakce při návrhu knihovny. Vlastnosti knihovny LwVCL: ● Vrstvená architektura. Komponenty uživatelského rozhraní mají slabé vazby na konkrétní platformu, což usnadňuje portování knihovny na další platformy. ● Malá velikost. Knihovna je velká přibližně 200 kB, což ji činí vhodnou pro zařízení PDA (CDC + Personal Profile, Windows Mobile) ● Přes třicet komponent uživatelského rozhraní. Knihovna poskytuje jednoduché i komplexní prvky UI, jako například tabulky, stromové struktury a mnohé další. ● Optimalizace paměti a nároků na procesor. ● MVC přístup. Knihovna je navržena s ohledem na koncept modelview-controller. Odděluje od sebe tedy design, model a ovládání. ● Rozšiřitelnost. Je možné přidávat další komponenty. Autor knihovny vytvořil i variantu pro J2ME MIDP profil. Některé komponenty bylo nutné kvůli možnostem zařízení upravit a bohužel se nejspíš dále rozvíjet na této platformě nebude. Důvodem je nízký zájem uživatelů a určitá omezení MIDP API. Přesto je k dispozici zdrojový kód pro libovolné účely. Obrázky aplikace vytvořené v LwVCL pro MIDP:
Obrázek 9: J2SE varianta stromu
Obrázek 8: J2ME varianta ukázkového programu
27
2.6.4 MicroEWT MicroEWT je nová knihovna pro tvorbu uživatelských rozhraní, stále označovaná jako beta verze. Pro běh aplikace vyžaduje MIDP 2.0 a CLDC 1.1 a je distribuována pod licencí GPL. Pro komerční účely existuje speciální licence. Vlastnosti knihovny: ●
Zachovává podobné rysy jako známé knihovny AWT nebo Swing.
Vzhled je oddělen od komponent UI, umožňuje definovat vlastní vzhled aplikace, nebo dokonce jen její části. ●
●
Obsahuje funkce pro kreslení bitmapových fontů.
●
Podpora pro lokalizaci aplikace.
●
Respektuje omezení typických J2ME zařízení.
●
Událostmi řízené interakce.
●
Podpora pro volně umístěná okna.
Možnost použít celou obrazovku (fullscreen) nebo jen část (menu se pak vykreslí pomocí komponent high-level API). ●
Knihovna MicroEWT mne zaujala především tím, že umožňuje používat okna. Tato okna je možné zavírat, případně maximalizovat, podobně jako u aplikací na PC. I když si nemyslím, že to je vždy vhodný způsob na zařízení s tak malým displejem, možnost je to zajímavá. Knihovna též obsahuje velmi zajímavý systém rozvržení komponent (layouting). Obrázky:
28
2.6.5 Paxmodept GUI Jako posledního zástupce knihovny pro tvorbu uživatelských rozhraní jsem si vybral velice zajímavou knihovnu od firmy Paxmodept. Stejně jako mnoho jiných vývojářů nebyli autoři této knihovny spokojeni se současným stavem v oblasti GUI pro J2ME a rozhodli se udělat knihovnu vlastní. Jejich cílem bylo vytvořit příjemně vypadající rozhraní se sladěnými barvami, přičemž se inspirovali, tak jako většina, knihovnami Swing a AWT. Výsledkem je použitelně vypadající produkt. Zatím byla vydána ukázková verze, na knihovně se stále ještě pracuje. Několik obrázků:
2.6.6 Shrnutí Se vznikem a rozšířením nových mobilních telefonů vzniká potřeba vytvářet aplikace příjemné na pohled. Jak jde kupředu vývoj v oblasti hardware, možnosti mobilních zařízení se zvětšují a objevují se nové možnosti, jak řešit interakci mezi člověkem a aplikací na mobilním telefonu. V oblasti J2ME zařízení se kromě tradičního rozhraní objevují specializované knihovny, snažící se řešit jeho nedostatky. Především omezené množství komponent a archaický vzhled. 29
Tyto knihovny mají řadu společných rysů. Všechny nějakým způsobem vychází z uživatelského rozhraní na PC, obsahují tedy podobné komponenty (tlačítko, zatržítko, panel, apod.). Dále se musí vyrovnat s velikostí displeje přenosných zařízení, s možnostmi ovládání (tlačítka, případně pero) a s výkonovými možnostmi zařízení. Cílem pochopitelně je, aby daná knihovna byla použitelná pro co nejvíce uživatelů. Protože je jedním z cílů příjemný vzhled, knihovny do různé míry umožňují měnit svůj styl a nastavení. Knihovny se liší některými speciálními funkcemi, jako například možnost otočit obraz o 90 stupňů, možnostmi rozvržení komponent a jejich propracovaností.
30
3. Vlastní knihovna pro tvorbu GUI na mobilních telefonech Přestože vzniká vícero knihoven pro tvorbu GUI, žádná mi úplně nevyhovuje. Proto jsem se rozhodl, že vytvořím knihovnu vlastní. Vlastnosti: ● Určena pro profil MIDP 2.0 a konfiguraci CLDC 1.1. MIDP 2.0 má oproti MIDP 1.0 navíc zejména lepší podporu pro zobrazování obrázků, kreslení grafických primitiv a umožňuje zobrazit aplikaci přes celý displej zařízení. Těchto vlastností jsem se rozhodl využít. ● Základní sada komponent pro tvorbu aplikace. Snažil jsem se o minimalizaci počtu ovládacích prvků. Pokud jich bude v budoucnu potřeba více, bude je možné doplnit. Základní sada je ovšem rozšiřitelná o uživatelské komponenty. ● Událostmi řízená interakce s uživatelem. Každá komponenta může reagovat na několik akcí vyvolaných uživatelem. Akce se vyvolá například pokud komponenta získá zaměření, změní svůj stav apod. ● Rozmístění komponent. Snažil jsem se o vhodný layoutovací mechanismus, implementoval jsem několik variant. ● Alerty. Pro pozornění uživatele na událost jsem implementoval několik druhů výstražných/informačních oken.
3.1 Model knihovny Knihovna se skládá z několika tříd. Podstatné jsou následující: Constants.java – několik konstant identifikující akce. Frame.java – základní třída, aplikace vytváří potomka této třídy. Jejím účelem je sdružovat panely aplikace a uchovávat informace o akutálním stavu. Panely mohou ležet ve třech vrstvách. Třída poskytuje funkce pro vkládání panelů do vrstev a jejich vykreslování. Další metody slouží k inicializaci aktivního ovládacíh prvku na všech panelech aplikace a pro layouting. Frame obsahuje atribut paintMask, kterým můžeme zakázat vykreslování některých vrstev. Atribut shadeEnabled pak říká, jestli mezi vrstvami kreslit poloprůhlednou vrstvu. FrameBasicControl.java – tato třída řídí umísťování panelů do vrstev. Předpokládá se, že v aplikaci můžeme zobrazit zároveň maximálně tři vrstvy - hlavní panel, levé menu, pravé menu nebo pop-up panel a alert.Tato třída si uchovává informaci o tom, které vrstvy jsou obsazené kterým panelem. InputHandler.java – účelem této třídy je zpracovávat tlačítka, převádět je na akce a o vzniklé akci informovat aktivní komponentu.
31
public void handleKey(int keyCode) { int key = keyCode; int action = frame.getGameAction(keyCode); if(action == frame.UP) key = frame.KEY_NUM2; else if(action == frame.DOWN) key = frame.KEY_NUM8; else if(action == frame.LEFT) key = frame.KEY_NUM4; else if(action == frame.RIGHT) key = frame.KEY_NUM6; else if(action == frame.FIRE) key = frame.KEY_NUM5; else if(keyCode == Constants.ACTIONKEY_LEFT) key = frame.KEY_NUM1; else if(keyCode == Constants.ACTIONKEY_RIGHT) key = frame.KEY_NUM3; if(!frame.handleGlobalKey(key)) return; if(key == frame.KEY_NUM4) { frame.getNavigator().selectNext(true); } else if(key == frame.KEY_NUM6) { frame.getNavigator().selectNext(false); } else if(key == frame.KEY_NUM2) { Widget aw = frame.getActiveWidget(); if(aw != null && (aw instanceof ActionListener)) { ((ActionListener) aw).actionPerformed( null, Constants.ACTION_UP); } } else if(key == frame.KEY_NUM8) { Widget aw = frame.getActiveWidget(); if(aw != null && (aw instanceof ActionListener)) ((ActionListener) aw).actionPerformed( null, Constants.ACTION_DOWN); } else if(key == frame.KEY_NUM5) { Widget aw = frame.getActiveWidget(); if(aw != null && (aw instanceof ActionListener)) ((ActionListener) aw).actionPerformed( null, Constants.ACTION_FIRE); } else { AbstractPanel ap = frame.getActivePanel(); if(ap != null && ap instanceof KeyListener) ((KeyListener) ap).keyPressed(null, key); } }
Pokud uživatel stiskne směrovou klávesu, převedeme ji na kód klávesy 0 – 9. Díky tomu se vyhneme některým problémům s kompatibilitou. Dále pak už můžeme pracovat jen s kódy kláves, které jsou vždy definovány (0-9, hvězdička, křížek). Pokud uživatel stiskne 2 nebo 6, znamená to, že chce vybrat komponentu před nebo za současně aktivní komponentou. Pomocí objektu třídy Navigator příslušnou komponentu najdeme a vybereme ji. Pokud uživatel stiskne 8, 5 nebo 2, vyvoláme v aktivní komponentě akci ACTION_UP, ACTION_DOWN nebo ACTION_FIRE.
32
MIDPTextBoxInput.java – pokud některá komponenta knihovny vyžaduje textový vstup, je vhodné tento text nejprve přijmout komponentou MIDP TextBox a pa k tento text v komponentě vykreslit. Navigator.java – tato třída řeší vyhledávání komponent v hierarchii panelů Style.java – abstraktní třída, definuje vlastnosti stylu aplikace public abstract Font getFont(); public abstract Font getBoldFont(); public abstract int getButtonBorderWidth(); public abstract int getPanelBorderWidth(); public abstract Image getImgInfo(); public abstract Image getImgQuestion(); public abstract Image getImgExclamation(); public abstract Image getImgYes(); public abstract Image getImgNo(); public abstract int [] getColorsScrollLine(); public abstract int [] getColorsScrollBar(); public abstract Image getImgItemShade(); public abstract Image getImgGroupColapsed(); public abstract Image getImgGroupUncolapsed(); public abstract Image getImgGroupEmpty(); public abstract Image getImgProgress(); public abstract Image getImgUp(); public abstract Image getImgDown(); public abstract int getColor(int id); public abstract void drawButtonBorder(Graphics g, int x, int y, int w, int h, boolean active); public abstract void drawButtonBackground(Graphics g, int x, int y, int w, int h, boolean active); public abstract void drawPanelBackground(Graphics g, int x, int y, int w, int h); public abstract void drawPanelBorder(Graphics g, int x, int y, int w, int h); }
33
Kromě metod, které vrací obrázky, písma, rozměry a barvy deklaruje metody pro vykreslení okraje a pozadí panelu a tlačítka. Potomek této třídy může dle uvážení použít různě složité metody vykreslování, od jednoduché čáry až po bitmapové výplně. Protože všechny komponenty pro své pozadí a okraj používají jednu z těchto dvou variant, změna v těchto metodách ovlivní celkový vzhled.
Canvas FrameBasic Control Frame
Hierarchy Navigator
InputHandler AbstractPanel
Component
SpacerPanel
Widget
LabelSimple
Button
ChoiceList a další
34
Panel
3.2 Implementace modelu Přestože existují knihovny pro parsování XML pro J2ME, mají docela vysoké nároky na paměť. Proto jsem vzhledem k možnostem zařízení zvolil efektivnější způsob, a to binární formát. Atributy komponent je možné načítat pomocí třídy DataInputStream. Příklad: ImageBox.java – komponenta zobrazuje obrázek, má atributy img, tiled, offsetX a offsetY. Načtení z binárního souboru lze provést metodou: public void initFromStream(DataInputStream dis) throws IOException { String path = dis.readUTF(); this.img = Utils.loadImage(path); this.tiled = dis.readBoolean(); this.offsetX = dis.readInt(); this.offsetY = dis.readInt(); }
3.2.1 Sada komponent Implementoval jsem následující komponenty:
Obrázek 10: Příklad aplikace Button – můžeme nastavit ikonu a zarovnání textu. Checkbox – klasické zatržítko ImageBoxScaled – zobrazí obrázek, možnost zarovnání svisle i vodorovně, je možné obrázkem vyplnit plochu 35
ImageBoxScaled – vybere největší obrázek, který se vejde do prostoru, a zobrazí jej. ImageBoxAnim – jednoduchá animace. ScrollBar – vertikální a horizontální posuvník Choice – výběr z možností List – výběr z možností, víceřádková komponenta TextArea – pro výpis a zadávání textu TextField – textové pole Tree – pokročilejší komponenta pro zobrazení hierarchie LabelSimple - popisek LabelFull – popisek s ikonou, barvami apod. Alerty – slouží k upozornění uživatele na událost. Několik typů: INFO – pouze informuje WARNING – varování EXCLAMATION – vykřičník PROGRESS a PROGRESS_WITH_CANCEL – zobrazují průběh Alerty také přehrávají příslušný zvuk (závislé na zařízení)
36
4. Ukázková aplikace – Mapa Brna Jako ukázkovou aplikaci pro použití mojí knihovny jsem zvolil offline mapu Brna. Myslím, že to je užitečná aplikace a není mi známo, že by podobná aplikace pro J2ME existovala. Samozřejmě existují různé online aplikace, jako například Google Maps, ty však vyžadují připojení k internetu. Vlastnosti aplikace: ● Všechna data jsou součástí jar archivu. Aplikace nevyžaduje internetové připojení. ● Aplikace zobrazuje mapu uloženou jako dlaždice ve formátu PNG. Zdálo se mi vhodné použít tento způsob, než například používat vektorový formát. Mapa primárně není určena k navigaci po silnici, proto jsem dal přednost tomuto. ● Snadné vyhledávání brněnské ulice. Aplikace je schopna vyhledat brněnskou ulici podle názvu. ● Ukládání význačných bodů. Uživatel může uložit několik bodů na mapě do konfigurační paměti. ● Měření vzdálenosti a úhlů. Aplikace je schopna změřit vzdálenost vyznačené trasy a zobrazit azimut mezi jednotlivými úseky. ● Ukládání tras. Kromě bodů je možné ukládat do konfigurační paměti také trasy. ● Body zájmu. Aplikace obsahuje několik kategorií význačných bodů (hotely, bankomaty apod.) ● GPS. Je možné zobrazit přibližnou polohu v souřadnicích GPS. Aplikace je připravena na přidání podpory pro připojení GPS modulu (přes rozhraní Bluetooth). ● Slunce. Známe-li polohu slunce (azimut), můžeme mobilní telefon správně natočit na sever. Tato funkce není použitelná, pokud je například zamračeno nebo tma. ● Mapové podklady jsou použity se svolením DPA s.r.o.
4.1 Příprava dat Aplikace používá několik datových zdrojů, které musíme správně sestavit. Za tímto účelem bylo nutné vytvořit několik doplňkových programů. Prvním typem dat jsou mapové dlaždice. Jsou to obrázky typu PNG, jejichž výška i šířka je 256 pixelů. Aplikace umožňuje přibližování a oddalování mapy, proto je nutné mít dlaždice připravené pro několik přiblížení. Tyto dlaždice jsou umístěny ve složkách z0, z1 a z2. Máme tedy 3 úrovně.
37
Obrázek 11: Pokrytí oblasti dlaždicemi
Všechny dlaždice mají stejnou šířku a jsou pojmenovány číslem. Máme-li v nejvyšší úrovni (z0) dlaždici s názvem 78_57.png, po přiblížení budeme chtít zobrazit některé ze 4 dlaždic z úrovně z1. Tyto dlaždice budou mít název odvozený z dvojnásobku číselného označení v úrovni z0, tedy 156_114.png, 157_114.png, 156_115.png a 157_115.png. Informace o ulicích je uložena v souboru index.dat. Je to klasický příklad indexového souboru. Protože vyhledáváme podle prvních dvou písmen, obsah tohoto souboru vypadá takto: A B C D ... první písmeno
A B C D ...
název, souřadnice název, souřadnice název, souřadnice …
druhé písmeno
bod na mapě a jeho souřadnice Posledním typem použitého datového souboru je informace o bodech zájmu. Ke každé dlaždici potřebujeme mít seznam bodů, které v oblasti dané dlaždice leží. Aby tento způsob byl efektivní, těchto bodů v dlaždici musí být vhodný počet. Jestliže je bodů v oblasti dlaždice málo, znamená to, že dlaždice je příliš malá. Pokud je jich hodně, je příliš velká. Musíme zvolit kompromis mezi počtem dlaždic a velikostí jejich oblasti. Je to proto, že pro prohledávání okolí musíme projít všechny body v dlaždici, což pro více než 50 bodů zájmu na dlaždici velmi zpomaluje odezvu programu.
38
V aplikaci používám dva druhy souřadnic. Prvním druhem je pozice v pixelech. Pokud znám úroveň přiblížení, mohu dopočítat názvy obrázků, které potřebuji zobrazit na displej. Druhým typem je souřadnice v metrech od nějakého místa na mapě. Pro převod mezi nimi používám lineární interpolaci. Konstanty pro tento převod a několik dalších informací uchovávám v souboru config.dat. 4.1.1 Data assembler Tento program vytvoří všechny potřebné soubory, vybere z úložiště obrázky pro zvolenou oblast a počet úrovní přiblížení, vytvoří seznam ulic, které se ve zvolené oblasti nacházejí a vytvoří soubor s indexem. Dále vytvoří seznam bodů zájmu v této oblasti a vytvoří příslušné datové soubory. Než spustíme proces vytváření datových souborů, musíme provést určitá nastavení, viz soubor Main.java v projektu dataAssembler. Nejprve zadáme cesty k souborům s body zájmu. Tyto textové soubory obsahují řádky ve tvaru Název;souřadniceX;souřadniceY. String poiBrnoPaths[] = new String[7]; poiBrnoPaths[0]="/seznamy/old1000/bankomatyBrno/result2"; … // Jak se budou jmenovat datové soubory s body zájmu String poiBrnoNames [] = {"bankomaty", "cerpaci", "hotely", "mhd", "nakup", "pohostinstvi", "ulice"}; // Název a verze Configuration config3 = new Configuration("mapaBrno3", 1001); // Konstanty pro převod pixelů do globálních souřadnic. int [] coordMap3 = {-60426311, -59300200, 61, 2882, -115535411, -116407900, 466, 2653, 492376472, 491701639, 165169167, 166832778 }; // Kolik máme úrovní (počátek a počet dlaždic)
přiblížení,
jakou
oblast
budeme
zpracovávat
config3.setLevels(3, 78, 57, 3, 3); config3.setCoordMap(coordMap3); // Cesty k dlaždicím String [] levelImgPaths3 = new String[3]; levelImgPaths3[0] = "/home/xstursa/projects/mapy/res/podklady/brno16"; levelImgPaths3[1] = "/home/xstursa/projects/mapy/res/podklady/brno8"; levelImgPaths3[2] = "/home/xstursa/projects/mapy/res/podklady/brno4"; // Cesta k náhledovému obrázku String globalThumbPath3 = "globalThumb-brnoCrop.png"; // Optimalizace počtu bodů zájmu na dlaždici. int poiBrnoFactors3 [] = {1, 2, 1, 1, 1, -1, -1}; Assembler ass3 = new Assembler("mapy/res/assembled/", config3, levelImgPaths3, globalThumbPath3, cityListPath3, poiBrnoPaths, poiBrnoNames, poiBrnoFactors3);
39
// Spuštění ass3.process();
Vytvoří se složka mapaBrno3 a v ní soubory: z0/, z1/, z2/
složky s dlaždicemi pro jednotlivé úrovně přiblížení:
config.dat
informace o verzi, názvu, konstanty pro převod souřadnic a další parametry
index.dat
ulice a index podle počátečních dvou písmen
bankomaty.dat, hotely.dat, mhd.dat, soubory s body zájmu pohostinstvi.dat, cerpaci.dat, nakup.dat, ulice.dat globalThumb.png
náhledový soubor mapy
4.1.2 Tile create Tato jednoduchá aplikace slouží k rozřezání velkého obrázku na dlaždice o velikosti 256 pixelů public static void main(String[] args) throws IOException { String path = "brno4half.png"; String out = "brno4half/"; BufferedImage inImg = ImageIO.read(new File(path)); int w = inImg.getWidth() / 256; int h = inImg.getWidth() / 256; Graphics g = inImg.getGraphics(); for(int i = 0; i < w; i++) { for(int j = 0; j < h; j++) { BufferedImage subImg = inImg.getSubimage( i*256, j*256, 256, 256); ImageIO.write(subImg, "png", new File(out + i + "_" + j + ".png")); } System.out.println(i); } }
40
4.1.3 Tile merge Opačný proces, dlaždice v daném rozsahu spojí v jeden soubor public static void main(String[] args) throws IOException { int fromX = 312/4; int toX = 324/4-1; int fromY = 228/4; int toY = 240/4-1; String path = "brno16/"; String out = "brno16crop.png"; // --------int w = (toX - fromX + 1) * 256; int h = (toY - fromY + 1) * 256; BufferedImage outImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); Graphics g = outImage.getGraphics(); for(int i = fromX; i <= toX; i++) { for(int j = fromY; j <= toY; j++) { System.out.println(i + " " + j); BufferedImage bi = ImageIO.read( new File(path + i + "_" + j + ".png")); g.drawImage(bi, (i-fromX)*256, (j-fromY)*256, null); } System.out.println(i); } ImageIO.write(outImage, "png", new File(out)); }
4.2 Vzhled a ovládání aplikace Po spuštění aplikace se zobrazí hlavní obrazovka. V pravém horním rohu vidíme tři tečky. Je to ukazatel velikosti kroku, s jakým se pohybujeme do stran. Ve spodním pravém rohu je údaj o aktuálním přiblížení. Ovládání aplikace: Směrové šipky a tlačítka 4,2,6,8 – pohyb po mapě 7 – přiblížení 9 – oddálení * – velikost kroku při pohybu 0 – náhled mapy Levá klávesa (soft-key) – vstup do menu
41
Obrázek 12: Úvodní obrazovka
Obrázek 13: Menu
mapa – DPA s.r.o. Pohyb v menu provádíme buď směrovými šipkami nebo tlačítky 2 a 4. Volbu potvrzujeme tlačítkem 5 nebo tlačítkem Fire. Volby menu: Domů – skočí na první uloženou pozici (pokud existuje) Exit – ukončí aplikaci Další volby a funkce viz následující tabulka:
42
Celý pohled – zobrazí náhled mapy, vyznačí malým červeným křížkem uložené pozice. V tomto pohledu se můžeme rychle pohybovat po celé mapě. Stiskem tlačítka fire (případně 5) skočíme na zvolené místo.
Obrázek 14: Celý pohled na mapu Najít ulici – zobrazí formulář s výpisem brněnských ulic. Pohyb po prvcích provádíme klávesami 4 a 6 (doleva/doprava). Stisknutím tlačítka 5 (fire) přejdeme na hledanou ulici.
Obrázek 15: Najít ulici Oblíbené – formulář pro ukládání bodů a cest. Umožňuje nám přidávat a mazat body a cesty. Stiskem klávesy fire na položce zobrazíme daný bod. V případě cesty se zobrazí uložená cesta. První uložený bod v seznamu se považuje jako výchozí (viz volba Domů)
Obrázek 16: Oblíbené
43
Body zájmu – v této sekci můžeme nastavit zobrazování bodů zájmu. Na výběr máme následující: Bankomaty, čerpací stanice, hotely, MHD, obchodní domy pohostinská zařízení a ulice.
Obrázek 17: Volba zobrazení bodů zájmu
44
Hledej. Pokud máme vybrán některý druh zájmových bodů a zvolíme tlačítko „Hledej“, prohledá se okolí aktuálního místa (sřed displeje) a vrátí se maximálně 50 bodů, setříděných podle vzdálenosti. Stisknutím tlačítka fire (5) na daný objekt přejdeme.
Obrázek18: Prohledávání okolí Slunce – vyznačí na displeji azimut ke slunci. Podle polohy slunce na obloze můžeme displej natočit na sever. (protože byl obrázek pořízen v jednu hodinu po půlnoci, slunce je v severovýchodním směru)
Obrázek 19: Ukazatel slunce Zobrazení bodů zájmu na mapě. Modré tečky znázorňují body zájmů. Typ a název zjistíme ve spodní části obrazovky. Orientační vzdálenost od středu displeje vidíme v horním levém rohu. Všimněme si též červené čáry, která nás vede k nejbližšímu bodu (užitečné, pokud je bod mimo obrazovku)
Obrázek 20: Body zájmu
45
Aplikace může ve spodní části obrazovky zobrazovat orientační souřadnice GPS.
Obrázek 21: GPS Měření cesty a azimutu. Kilometrová hodnota se sčítá. Cestu též můžeme uložit mezi oblíbené. Můžeme si též všimnout, že popisky k jednotlivým bodům se snaží rozmístit tak, aby nezavazely v cestě.Je to uděláno tak, že čára vedoucí k popisku půlí tupý úhel mezi segmenty.
Obrázek 22: Měření vzdáleností Součástí aplikace je stručná nápověda
Obrázek 23: Nápověda
46
Pokud mažeme uložený bod nebo cestu, aplikace se nás zeptá pomocí alertu.
Obrázek 24: Příklad použítí alertu
47
4.3 Struktura aplikace Aplikace je složena z několika tříd. Snažil jsem se oddělit vzhled od modelu. Přehled balíků a jejich tříd je uveden v následjící tabulce. com.xstu.mobile.mapcr ● AppFrame.java – základní třída aplikace, která sdružuje všechny panely, vytváří jejich instance a řeší reakce na stisknutá tlačítka. ● Config.java – sdružuje konfiguraci a aktuální stav aplikace. Obsahuje všechny textové řetězce a poskytuje metody pro dotazování na aktuální stav (například zda má být zobrazena cesta. Body zájmu apod.) Dále obsahuje pole s ikonami, které se vytváří při startu aplikace a také metody pro přístup k uloženým bodům a cestám. ● MapCR.java – jediný midlet aplikace. Jeho úkolem je zobrazit načítací a pak úvodní obrazovku ● WaitScreen.java – načítací obrazovka, která zobrazuje obrázek a progress bar. com.xstu.mobile.mapcr.map ● Cache.java – protože je načítání obrázků dlaždic z paměťové karty pomalé, vytvořil jsem cache paměť. Uchovává v sobě několik obrázků a urychluje tak vykreslování mapy. public Image getImage(int zoom, int x, int y) { Image ret = null; int index = search(zoom, x, y); if(index == -1) { if(itemsUsed < items.length) { CacheNode n = new CacheNode( zoom, x, y, mf.getImage(zoom, x, y)); items[itemsUsed] = n; itemsUsed++; pointer++; ret = n.img; } else { pointer = (pointer + 1) % itemsUsed; CacheNode n = items[pointer]; n.img = null; n.set(zoom, x, y, mf.getImage(zoom, x, y)); ret = n.img; } } else { return items[index].img; } return ret; } private int search(int zoom, int x, int y) { CacheNode n = null;
48
for(int i = 0; i < itemsUsed; i++) { n = items[i]; if(n.x == x && n.y == y && n.zoom == zoom) return i; } return -1; }
Pokud nenalezneme obrázek v seznamu a máme ještě volné místo, načteme obrázek a uložíme do cache. Pokud volné místo není, přepíšeme nejstarší záznam v cache. ● GeoLocation.java – důležitá třída, jejímž úkolem je uchovávat informaci o názvu a souřadnicích lokace. Dále obsahuje funkce pro serializaci do bytového pole. public class GeoLocation { public String name = null; public short pixX; public short pixY; public byte[] serialize() { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); String nameW = name == null ? "" : name; try { dos.writeUTF(nameW); dos.writeShort(pixX); dos.writeShort(pixY); return baos.toByteArray(); } catch(Exception ex) { return null; } } public boolean initFrom(byte[] data) { if(data == null) return false; ByteArrayInputStream bais = new ByteArrayInputStream(data); DataInputStream dis = new DataInputStream(bais); try { name = dis.readUTF(); pixX = dis.readShort(); pixY = dis.readShort(); } catch (Exception ex) { AppFrame.log(this, ex); return false; } return true; } ... }
49
● GeoPath.java – cesta. Obsahuje také funkce pro serializaci do a z bytového pole. Cesta je zadána jménem a vektorem objektů typu GeoLocation. ● MapFile.java – abstraktní třída, definuje, jakým způsobem se komponenty dotazují na data.
// získá obrázek x, y pro zadanou úroveň přiblížení abstract public Image getImage(int zoomLevel, int x, int y); // existující první písmena měst abstract public String getFirstChars(); // pro dané první písmeno vrátí všechna druhá písmena abstract public String getSecondChars(char first); // vrátí pole lokací pro zadané první a druhé písmeno abstract public GeoLocation [] getCities(char first, char second); // převod do pixelových souřadnic abstract public void pixelCoords(int [] geo, int zoom, int [] out); // převod do globálních souřadnic abstract public void geoCoords(GeoLocation gl, int zoom, int[] out); // získá gps souřadnice dané lokace abstract public void gpsCoords(GeoLocation gl, int[] out); .. ●
MapFileJar.java – implementace pro data, která jsou součástí archivu
jar ●
MapView.java – komponenta, která zobrazuje dlaždice, cesty apod.
public void draw(Graphics g) { grState.storeAndIntersectClip(g, regionX, regionY, regionWidth, regionHeight); if(preZoomIn) { Image buff = Image.createImage(regionWidth, regionHeight); drawTiles(buff.getGraphics()); buff = Utils.scale(buff.createImage(buff, regionWidth/4, regionHeight/4, regionWidth/2, regionHeight/2, Sprite.TRANS_NONE), regionWidth, regionHeight); g.drawImage(buff, 0, 0, 0); } else if(preZoomOut) { Image buff = Image.createImage(regionWidth, regionHeight); drawTiles(buff.getGraphics()); buff = Utils.scale(buff, regionWidth/2, regionHeight/2); g.setColor(0); g.fillRect(0, 0, regionWidth, regionHeight); g.drawImage(buff, regionWidth/4, regionHeight/4, 0); } else { // dlaždice
50
drawTiles(g); // body zájmu drawPois(g); // uložené lokace (červený křížek) drawSavedLocations(g); // cesta drawActivePath(g); // střed drawCenter(g); // nejbližší bod zájmu drawNearestPoi(g); // gps souřadnice drawGPSCoords(g); } grState.restoreClip(g); }
Pokud jsou nastaveny proměnné preZoomIn nebo preZoomOut, vloží se do oblasti zvětšený nebo zmenšený obrázek vzniklý z aktuálního podkladu. Výsledkem je pěkný efekt postupného vykreslování. MapViewThumb.java – komponenta zobrazující panel s náhledem mapy. Umožňuje rychlý přesun po mapě. ● RandomAccessFile.java – třída umožňující pracovat se souborem tak, jako by šlo o soubor s náhodným přístupem. J2ME poskytuje pouze sekvenční čtení souboru. Je toho docíleno buď načtením celého souboru do pole (pro malé soubory), nebo opětovným zavíráním, otevíráním souboru a přeskakováním (skipBytes()) na požadované místo. ●
public RandomAccessFile(String path, boolean fromJar, boolean cached) throws IOException { this.path = path; this.fromJar = fromJar; this.cached = cached; if(!fromJar) { conn = (FileConnection) Connector.open(path, Connector.READ); if(!conn.exists()) throw new IOException(path + " doesn't exist!"); if(conn.isDirectory()) throw new IOException(path + " is directory!"); if(!conn.canRead()) throw new IOException(path + "is not readable!"); dis = conn.openDataInputStream(); } else { if(cached) { loadData(); dis = new DataInputStream(new ByteArrayInputStream(data)); } else {
51
dis = new DataInputStream( this.getClass().getResourceAsStream(path)); } } } private void loadData() { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); InputStream is = this.getClass().getResourceAsStream( path); byte [] b = new byte[16]; int r = 0; while(true) { r = is.read(b); if(r == -1) break; bos.write(b, 0, r); } data = bos.toByteArray(); } catch(Exception ex) { ex.printStackTrace(); } }
com.xstu.mapcr.panels – balík obsahuje třídy všech panelů. Pro ilustraci uveďme vytvoření panelu s nápovědou. private Component createContent() { content = new BPanel(true, false); XCellLayout xcl = new XCellLayout(true); content.setLayout(xcl); int i = 0; LabelFull ls = new LabelFull(Config.STR_HELP + ":", Utils.loadImage(Config.imgPath + "help.png"), null, null); xcl.setCellParam(i++, ls.getPreferredHeight(), false); content.add(ls); // ---------xcl.setCellParam(i++, 3, false); content.add(new Spacer(false, true)); // ---------TextArea ta = new TextArea(Config.STR_HELP_TEXT); content.add(ta);
}
return content;
BPanel je panel, který libovolný jiný panel obalí bílým rámem. Proto bude tvořit nejvyšší panel v hierarchii. Dále vytvářím layout a přiřazuji jej k panelu. Následně přidám nadpis, vodorovný oddělovač a komponentu s textem. Layout nastavím vždy tak, aby výška komponenty byla optimální a v pixelech. U textové komponenty výšku nenastavuji, proto komponenta obsadí zbytek místa panelu.
52
5. Literatura Referenční API, Sun Corporation, dokument dostupný na URL http://java.sun.com/javame/reference/apis.jsp MIDP Programming with J2ME, Sams Publishing, dokument dostupný na URL http://www.developer.com/java/j2me/article.php/10934_1561591_1 (leden 2008) Beginning J2ME: Building MIDlets, dokument dostupný na URL http://www.javaworld.com/javaworld/jw-05-2005/jw-0502-midlet.html (leden 2008) Domovské stránky popisovaných knihoven
53
Příloha 1 – parametry běhového prostředí microedition.encoding: Cp1250 microedition.configuration: CLDC-1.1 microedition.profiles: MIDP-2.0 microedition.locale: en-US microedition.profiles: MIDP-2.0 microedition.io.file.FileConnection.version: 1.0 file.separator:\ microedition.pim.version: 1.0 microedition.locale: en-US microedition.profiles: MIDP-2.0 microedition.commports:COM1,COM2 microedition.hostname: ath wireless.messaging.sms.smsc: +12345678900 microedition.platform: SonyEricssonW810/JAVASDK microedition.encoding: Cp1250 microedition.configuration: CLDC-1.1 microedition.profiles: MIDP-2.0 microedition.smartcardslots: 0H,1H microedition.location.version: 1.0 microedition.sip.version: 1.0.1 microedition.m3g.version: 1.0 microedition.jtwi.version: 1.0 microedition.locale: en-US microedition.profiles: MIDP-2.0 wireless.messaging.sms.smsc: +12345678900 wireless.messaging.mms.mmsc: http://mmsc.127.0.0.01 CHAPI-Version: null microedition.media.version: 1.1 supports.mixing: false supports.audio.capture: false supports.video.capture: false supports.recording: false audio.encodings: encoding=pcm encoding=pcm&rate=8000&bits=8&channels=1 encoding=pcm&rate=22050&bits=16&channels=2
54
video.encodings: encoding=rgb565&width=160&height=120 encoding=rgb565&width=320&height=240 encoding=rgb565&width=120&height=160 video.snapshot.encodings: encoding=jpeg encoding=png streamable.contents: audio/x-wav video/3gpp video/mpeg audio/x-tone-seq audio/x-wav image/gif audio/midi audio/mpeg audio/imelody audio/amr audio/sp-midi
55