JAVA ME – JAD a MF Soubory .MF a .jad Atributy: MIDlet-Name – název sady midletů (přepínač jar: P, přepínač pro JAD: P) – dále viz help jaru MIDlet-Version – verze sady midletů MIDlet-Vendor – poskytovatel sady midletů (výrobce) MIDlet-n – popis midletu v sadě midletů, kde n je číslo, které slouží k identifikaci midletu (n se čísluje od 1) MicroEdition-Profile – specifikace MIDP se kterými mohou midlety spolupracovat (může jich být více – těch verzí) např. MIDP-1.0 MicroEdition-Configuration – verze J2ME (resp. konfigurace), kterou midlety od zařízení požadují MIDlet-Description – popis sady midletů – zobrazí se uživateli MIDlet-Icon – odkaz na ikonu sady midletů, formát obrázku je png MIDlet-Info-URL – url souboru s případným rozsáhlejším popisem sady midletů (aplikace) MIDlet-Data-Size – požadavek sady midletů na velikost úložiště persistentních dat (např. skóre hry, adresář kontaktů ICQ resp. Jimm), v bytech MIDlet-Jar-URL – používá se v jad souboru, adresa umístění samotného jar souboru s aplikací MIDlet-Jar-Size – používá se v jad souboru, velikost souboru jar – tj. co se stáhne (v bytech) MIDlet-Install-Notify – není ve spec. MIDP 2.0, ale je podporováno WirelessToolkitem, URL použité k umístění hlášení stažení nebo nestažení sady midletů MIDlet-Delete-Confirm – zpráva pro uživatele, která se zobrazí při odstraňování midletů ze zařízení MIDlet-další_atributy – atributy přidané vývojářem, které lze získat za běhu – slouží ke konfiguraci midletů - žlutě podbarvené atributy – společné pro .MF a .jad Např.: obsah manifest souboru SuperStrilecka.MF MIDlet-Name: SuperStrilecka MIDlet-Vendor: TAM-Students s.r.o. MIDlet-Version: 1.3.8 MIDlet-Description: Skvela gamesa pro male i velke MIDlet-Icon: /eu/cz/zcu/fel/students/TAM/ikonka.png MIDlet-Info-URL: http://info.gamesaci.cz/SuperStrilecka_info.html MIDlet-Data-Size: 512 MicroEdition-Profile: MIDP-2.0 MicroEdition-Configuration: CLDC 1.1 -pokud bude v sadě více midletů: MIDlet-1: X-wing, /eu/cz/zcu/fel/students/TAM/ikonaX.png, eu.cz.zcu.fel.students.TAM.X-wing MIDlet-2: Y-wing, /eu/cz/zcu/fel/students/TAM/ikonaY.png, eu.cz.zcu.fel.students.TAM.Y-wing - v jar souboru by byly: /eu/cz/zcu/fel/students/TAM/ikonaX.png /eu/cz/zcu/fel/students/TAM/ikonaY.png /eu/cz/zcu/fel/students/TAM/ikonka.png /eu/cz/zcu/fel/students/TAM/X-wing.class /eu/cz/zcu/fel/students/TAM/Y-wing.class - případně další „pomocné“ třídy a obrázky
Pokud by např. midlet Y-wing neměl ikonu: MIDlet-2: Y-wing, , eu.cz.zcu.fel.students.TAM.Y-wing obsah jad souboru SuperStrilecka.jad MIDlet-Name: SuperStrilecka MIDlet-Vendor: TAM-Students s.r.o. MIDlet-Version: 1.3.8 MIDlet-Description: Skvela gamesa pro male i velke MIDlet-Info-URL: http://info.gamesaci.cz/SuperStrilecka_info.html MIDlet-Data-Size: 512 MIDlet-Jar-Size: 12756 MIDlet-Jar-URL: http://hry.gamesaci.cz/SuperStrilecka/install.jar
-2-
Architektura JAVA ME Java Micro Edition (dříve JAVA ME) představuje podmnožinu jazyka Java Standard Edition (Java SE). Jeho použití je zaměřeno na malá bezdrátová zařízení s omezeným paměťovým prostorem – mobilní telefony, pagery a osobní digitální asistenti (PDA). Z tohoto důvodu má ve s rovnání s J2SE vytvořen menší virtuální stroj a omezeny API funkce. Z obecného pohledu JAVA ME definujeme následující komponenty: • skupinu javovských virtuálních strojů, z nichž každý se uplatňuje u jiného typu zařízení s odlišnými nároky a zajišťuje komunikaci s hostitelským operačním systémem (firmware), • množinu knihoven a API, které lze spustit na každém virtuálním stroji, nazývají se konfigurace a profily, • nástroje pro vývoj a nastavení zařízení. První dvě komponenty tvoří pracovní prostředí JAVA ME. Obrázek 1.1 ukazuje uspořádání v pracovním prostředí. Centrum tvoří javovský virtuální stroj, který pracuje na hostitelském operačním systému daného zařízení. Nad ním je specifická konfigurace JAVA ME skládající se z programových knihoven. Ty zajišťují základní funkce vycházející z požadavků na zdroje daného zařízení. Vrchol konfigurace tvoří JAVA ME profily. Jedná se o doplňkové programovací knihovny, využívající příbuzných funkcí v podobných zařízeních.
-3-
Profily
MIDP
PDAP
IMP
PERSONAL RMI GAME FOUNDATION PROFILE
Konfigurace
CLDC
CDC
Virtuální stroj
KVM
CVM Operační systém
Obrázek 1.1 – Architektura JAVA ME Virtuální stroje JAVA ME Z obrázku č. 1.1 je vidět, že virtuální stroj je základem programování v JAVA ME a v jakékoli Javě. Virtuální stroj je v JAVA ME dvojí. To je způsobeno dvěma skupinami malých zařízení, resp. paměťovou náročností těchto dvou skupin. Tyto dva stroje jsou KVM (Kilo Virtual Machine) a CVM (Compact Vitual Machine) KVM (Kilo Virtual Machine) Je kompletní javovský virtuální stroj pro malá zařízení. Jedná se o opravdový virtuální stroj Javy podle specifikace pro virtuální stroje, ovšem s jistými odchylkami nutnými ke správnému fungování na malých zařízeních. KVM bylo navrženo speciálně pro malá zařízení s omezenými zdroji, která mají pouze několik set kilobajtů celkové paměti. KVM navazuje na výzkumný projekt s názvem „Spotless“ laboratoří společnosti Sun Microsystem. Cílem projektu bylo zavést systém Java na Palm Connected Organizer. KVM je napsané v programovacím jazyku C, takže ho lze snadno přenést na různé platformy, které disponují kompilátorem jazyka C. Stejně jako skutečné JVM může i KVM nahrávat třídy ze zvoleného adresáře i z JAR souboru.
-4-
Zpráva JAVA ME specifikuje KVM takto: • Vytvořené pro 16-bitové a 32-bitové CISC nebo RISC procesory a procesory s minimální frekvencí 25 MHz. • Malé, s nároky na statickou paměť od 50 do 80 KB. • Vysoce mobilní s ustálenými rozměry a přizpůsobivé. • Za daných podmínek co možná nejrychlejší a kompletní bez nutnosti obětovat jiné, výše zmíněné vlastnosti designu. Pozn.: CLDC 1.0 – JSR 30 na jcp.org – detailní specifikace http://jcp.org/en/jsr/detail?id=30 CLDC 1.1 – JSR 139 na jcp.org – detailní specifikace http://jcp.org/en/jsr/detail?id=139 CLDC 1.1.1 JSR 139 (Maintenance Release) Následující seznam uvádí přehled vlastností, které nejsou virtuálním strojem KVM podporovány: • JNI
(Java Native Interface) – toto omezení má v zásadě dva důvody:
zabezpečení a skutečnost, že vzhledem k omezené paměti je zavedení JNI velice náročné, • uživatelské „class-loadery“ (zavaděče tříd) – je to kvůli zabezpečení, protože uživatel nesmí nahradit nebo odstranit vestavěný zavaděč tříd, • reflexe, • skupiny vláken a podprocesy typu démon, • finalize (chybí Object.finalize()) – z důvodu přílišné paměťové náročnosti, • slabý typ reference (weak reference) – dle verze CLDC, • kontrola class souborů – plná verifikace (nahrazeno z části preverifikací), • u starší verze podle specifikace CLDC 1.0 chyběla též např. podpora plovoucí řádové čárky
-5-
CVM (Compact Virtual Machine) CVM je vytvořeno pro větší zařízení, např. pro ta, která používají CDC. Podporuje všechny rysy a knihovny virtuálního stroje Java 2 v oblasti: • zabezpečení • slabé odkazy • JNI a RMI (Remote Method Invocation – JSR 66). CVM má následující vlastnosti: • pokročilá správa paměti • zkrácená čekací doba garbage collectoru • oddělení CVM ze systémové paměti • přenositelnost CVM na jiné platformy • zrychlený typ synchronizace a zamykání objektů • podpora pro nativní vlákna, mapování vláken v Javě přímo na nativní vlákna • podpora pro real-timové operační systémy (RTOS) Existují i další specializované virtuální stroje, např. experimentální virtuální stroj Squawk – pro experimentální platformu SUN Spot – http://www.sunspotworld.com (popř. https://spots.dev.java.net/) Konfigurace JAVA ME – shrnutí Konfigurace definují programové vybavení pro určité skupiny zařízení. Tyto skupiny jsou rozlišeny podle typu a velikosti paměti, typu a rychlosti procesoru a síťového připojení. Konfigurace zahrnuje virtuální stroj, knihovny, třídy a API funkce. Rozlišují se
-6-
dvě JAVA ME konfigurace CDC (Connected Device Configuration) a CLDC (Connected Limited Device Configuration).
CLDC (Connected Limited Device Configuration) Jedná se o nejmenší (mimo Java Card) definovanou konfiguraci Javy. Uplatňuje se zejména v bezdrátových zařízeních s omezenými zdroji (mobilní telefony, pagery, PDA). Zde jsou požadavky pro JAVA ME CLDC podle oficiální specifikace JAVA ME: • zařízení může mít celkem 160 až 512 KB paměti pro prostředí Java včetně RAM i paměť flash nebo ROM, • CLDC by mělo mít minimálně 128 KB stálé paměti pro virtuální stroj Javy a pro CLDC knihovny a minimálně 32 KB dočasné paměti využitelné pro práci virtuálního stroje, to vše nezávislé na jiných aplikacích, • zařízení může mít omezený zdroj energie, např. bateriové napájení, • zařízení je propojitelné s některým typem sítě často bezdrátovým přerušovaným dvousměrným připojením a s omezenou šířkou pásma (často 9600 b/s a méně), • zařízení může mít i poměrně propracované uživatelské rozhraní, ale není to povinností, • 16-ti nebo 32-ti bitový procesor s minimální taktovací frekvencí 25MHz. CDC (Connected Device Configuration) Konfigurace CDC je určena pro výkonná zařízení, která jsou občas připojena k síti. Patří sem přídavná zařízení, televize po internetu, domácí spotřebiče, herní konzole a navigační systémy pro vozidla.
-7-
CDC obsahuje plnou verzi virtuálního stroje Javy podobného tomu, který se dnes používá u J2SE. Rozdíl spočívá v paměti příslušného zařízení a v zobrazovací schopnosti. Zde jsou požadavky na zdroje pro CDC zařízení podle oficiální specifikace JAVA ME: • zařízení je řízeno 32-bitovým procesorem, • zařízení má 2MB a více pamětí pro Javu. Tento údaj zahrnuje RAM i paměť flash nebo ROM, • zařízení vyžaduje plně funkční virtuální stroj Java 2 „Blue Book", • zařízení je připojitelné k některému typu sítě, často bezdrátovým přerušovaným připojením a s omezenou šířkou pásma (často 9600 b/s nebo méně), • zařízení může mít i poměrně propracované uživatelské rozhraní, ale není to povinností Profily JAVA ME – definují java prostředí pro různé produkty stejné úrovně – profil je vlastně sada programových rozhraní (API) tvořících nadstavbu konfigurace – profil nabízí programu přístup k vlastnostem specifickým pro dané zařízení Profil MIDP (Mobile Information Device Profile) Profil MIDP je pro bezdrátová zařízení (mobilní telefony, pagery, a další zařízení) konfigurace CLDC. Tato zařízení musí splňovat tyto parametry (starší MIDP 1.0): • Displej musí mít minimálně 96x54 pixelů a dvě barvy. • Klávesnice popř. dotyková obrazovka.
-8-
• 32 KB paměti pro práci Javy, 128 KB stálé paměti pro komponenty MIDP a 8KB stálé paměti pro dlouhodobé ukládání dat aplikací. • Možnost obousměrného síťové spojení.
OEM -Specific Applications
MIDP Applications
OEM-Specific Classes MIDP CLDC Native System Software MID
Obrázek 2 – Architektura MIDP Jak je z obrázku patrné, kromě MIDP implementace může výrobce zařízení dodávat také rozširující API (OEM-Specific Classes). Aplikace, které tyto třídy využívají jsou však závislé na daném zařízení, pro které byly vytvořeny. (Například Siemens dodával k mobilním telefonům SL45i také API, které umožňuje posílat krátké textové zprávy SMS (což MIDP 1.0 samo o sobě nenabízí)). Na jedné straně je to výhodné, protože toto API umožňuje využít funkce telefonu, které by normálně MIDP API neumělo obsloužit. Na druhé straně však takto vytvořené aplikace nepoběží na jiném telefonním přístroji, než od firmy Siemens, což je trochu v rozporu s filozofií jazyka Java. Proto již dnes vznikají určitá standardizovaná API, na kterých pracují společně přední výrobci mobilních telefonů (například Game API pro snadné vytváření MIDP her).
-9-
Profily – pokračování MIDP 1.0: • paměť použitelná pro prostředky Javy přibližně 512K (ROM + RAM), • omezený výkon procesoru, • omezený zdroj energie, typicky baterie, • omezený prostor pro ukládání dat, • připojení k bezdrátové síti (většinou s omezenou šířkou pásma), • různorodost uživatelského interface, omezené ovládání (vstupní zařízení), malý display s nízkou hloubkou barev. MIDP 2.0: • Zpětná kompatibilita s MIDP 1.0, přidává nové třídy • Podpora HTTPS (GenericConnectionFramework) • Multimédia – vlastní Media API pro práci s multimedii, podmnožina Mobile Media API, zahrnuje jen audio • Rozšíření javax.microedition.lcdui – lepší práce s Form a Item. • Game API – javax.microedition.lcdui.game – možnost vykreslit hru přes celý display, zmenšení kódu aplikace • Práce s RGB obrázky • Ověřování důvěryhodnosti MIDletu • Sdílené úložiště dat mezi MIDlety – na rozdíl od MIDP 1.0 může jedna aplikace číst RMS jiné
- 10 -
MIDP 3.0: • platné od 9.12.2009. • Přináší především věci, co chybí v MIDP 2.0 ale jsou hojně využívány buď pomocí API výrobců, nebo pomocí balíčků • Podpora více spuštěných MIDletů současně • Běh MIDletu na pozadí (s MIDP 2.0 je MIDlet možné pouze zapauzovat) • Možnost automatického spuštění (například po zapnutí telefonu) • Vylepšena podpora velkých displejů, možnost využívat i druhý displej • Zlepšena přímá podpora mobilních her • Spolupráce mobilní javy s jinými funkcemi telefonu jako například MMS, paměťová média, atd... JSR 271 (MIDP 3.0) JSR 118 (MIDP 2.0) JSR 37 (MIDP 1.0) Ke knihovnám specifikovaným v CLDC přidává MIDP 1.0 další: • javax.microedition.rms – správa persistence dat • javax.microedition.midlet – třída MIDlet, která je základní třídou MIDP profilu • javax.microedition.io – zejména třída HttpConnection • javax.microedition.lcdui – třídy pro tvorbu uživatelského rozhraní MIDP 2.0 rozšiřuje dále funkcionalitu o následující balíky: • javax.microedition.media – přehrávání multimedií. Jedná se o podmnožinu JSR 135 Java Mobile Media API.
- 11 -
• javax.microedition.lcdui.game – herní API zaměřené na jednoduché 2D hry • javax.microedition.pki – autentizační API pro zabezpečené přípojení (certifikáty) Profil PDAP (Personal Digital Assistant Profile) Profil PDA je podobný MIDP, ale je určen pro organizéry PDA, které mají lepší displeje více paměti než mobilní telefony. Obsahuje důmyslnější knihovnu uživatelského rozhraní na bázi Javy pro přístup k užitečným funkcím hostitelského operačního systému. Profil poskytuje API pro uživatelské rozhraní a API pro ukládání dat v příručních zařízeních. Profil IMP (Information Module Profile) Tento profil je využíván pro zařízení bez displeje (prodejní automaty, výherní automaty, bezdrátové GSM moduly atd…) • IMP 1.0 – základem je MIDP1.0 ořezaný o grafické rozhraní lcdui • IMP-NG (next generation) - odvozena od MIDP2.0, zahrnuje bezpečnostní a síťová rozšíření, ale opět bez lcdui API a game API (JSR 228 – viz jcp.org)
Základní profil (Foundation Profile) Přidává většinu základních tříd, které CDC chybí oproti standardní edici. Neobsahuje žádné uživatelské rozhraní a také neobsahuje knihovny java.beans, java.rmi ani java.sql. Důvodem toho je druh zařízení, pro něž je CDC určeno (primárně pro autonomní zařízení jako jsou set-top boxy, TV apod.)
Tato zařízení nejsou ve srovnání s osobními asistenty a PDA tak silně fixována na použití GUI, a proto v základním profilu nejsou GUI knihovny zahrnuty. Jak naznačuje název „Základní" (Foundation), tento profil má sloužit jako základní pro další profily. Vyžaduje 1 MB ROM a 512 KB RAM. Osobní profil (Personal Profile) Osobní profil rozšiřuje možnosti Základního profilu o grafické uživatelské rozhraní (GUI), na němž lze spustit applety pro Java Web. Jelikož se PersonalJava mění v Osobní profil, bude zpětně kompatibilní s aplikacemi pro PersonalJava 1.1 a 1.2
Profil RMI (Remote Method Invocation) RMI profil rozšiřuje možnosti Základního profilu o RMI pro daná zařízení. Protože navazuje na Základní profil, je RMI profil určen pro CDC/Základní profil a ne pro CLDC/MIDP. RMI profil je kompatibilní s J2SE RMI API 1.2x a vyššími. Tento profil podporuje tyto vlastnosti: • plná podpora pro RMI sémantiku • distribuované chování „garbage collectoru“ jak na klientu, tak na serveru • RMI registry a export vzdálených objektů • aktivační protokol na klientské straně Jedná se o nejdůležitější části RMI protokolu, které jsou zásadní pro jeho běh. Na druhou stranu bylo nutné některé části RMI odstranit (systémové nároky, přenosové kapacity sítě). Vynechané jsou tyto funkce RMI protokolu:
• podpora pro průchod RMI přes proxy a firewall • RMI multiplexový protokol • podpora pro stub/skeleton model a stub/skeleton kompilátor • podpora pro „dřívější“ (deprecated) RMI rozhraní (zpětná kompatibility)
Herní profil (Game Profile) Tento profil je určený pro herní konzole. JSR 134
Zvukové možnosti Ve specifikaci MIDP 2.0 přibyla možnost ovládání zvuku, které si v MIDP 1.0 řešili výrobci telefonů vlastním rozhraním. Vedlo to ale k nepřenositelnosti aplikací využívajících tato speciální rozhraní. K ovládání zvuku slouží následující balíčky: • javax.microedition.media • javax.media • javax.microedition.media.control, které obsahují podmnožinu volitelného rozhraní Mobile Media API. Tato podmnožina vznikla jako minimální varianta práce se zvukem pro nejmenší zařízení s profilem MIDP. Architektura rozhraní pro práci se zvukem má tři základní stavební kameny, kterými jsou třída Manager a rozhraní Player a Control. Třída Manager Hlavní třídou je javax.microedition.media.Manager sloužící ke zjištění podporovaných protokolů (HTTP, soket atd.), typů obsahů, které lze přehrát, a získání instancí objektů typu Player. Protokoly a typy přehrávaných souborů jsou spolu provázány. Jsou zde implementovány metody pro zjištění typů souborů, které lze přehrát daným protokolem a metody pro zjištění protokolů pro konkrétní typ souboru. Ze známých formátů je to např. WAV, AU, MP3, MIDI audio soubory a posloupnost tónů. Povinně podporované formáty zvukových souborů jsou WAV a MIDI, podpora ostatních formátů závisí na výrobci zařízení. Třída Manager obsahuje např. metody pro získání objektu typu Player nebo metodu na přehrávání tónů. Přehrání tónu zadané frekvence:
Příprava: SEMITONE_CONST = 17.31234049066755 = 1/(ln(2^(1/12))) note = ln(freq/8.176)*SEMITONE_CONST Např. komorní A = 69, tj. 440 Hz. (note = 69;) private void pipni() { int note = 83; try { Manager.playTone(note, 2000, 100); } catch (MediaException ex) { ex.printStackTrace(); } } Rozhraní Player Toto rozhraní slouží k práci se zvukovými daty. Během své existence se může nacházet v jednom z pěti stavů: nerealizovaný stav, realizovaný stav, připravený stav, běžící stav a stav ukončený. Jednoduše: try { Player p = Manager.createPlayer("/abc.wav"); p.start(); } catch (MediaException pe) { } catch (IOException ioe) { }
(obr. z MIDP2.0 API doc)
Komplexnější příklad: private static final String[] audioTypy = {"audio/x-wav", "audio/basic", "audio/mpeg", "audio/midi", "audio/x-tone-seq"}; private static final int WAV = 0; private static final int AU = 1; private static final int MP3 = 2; private static final int MIDI = 3; private static final int SNDSEQ = 4; private Player prehravac; private boolean initZvuk() { try { Object pomObj = new Object(); prehravac = Manager.createPlayer(pomObj.getClass().getResourceAsStrea m("/bomb-03.wav"), audioTypy[WAV]); } catch (Exception ex) { System.out.println("Nepodařilo se načíst soubor /bomb03.wav : " + ex.getClass().getName()); prehravac = null; return false; } return true; } public void hrajZvuk(int kolikrat) { stopZvuk(); if (initZvuk() == false) { return; } try {
prehravac.realize(); prehravac.setLoopCount(kolikrat); prehravac.start(); } catch (Exception ex) { System.out.println("Nepodařilo se přehrát soubor /bomb03.wav : " + ex.getClass().getName()); stopZvuk(); } } public void stopZvuk() { if (prehravac == null) { return; } try { prehravac.stop(); } catch (Exception ex1) { // nemusím nic dělat, už nehraje } try { prehravac.deallocate(); prehravac.close(); } catch (Exception ex2) { // nemusím nic dělat, už bylo uvolněno } prehravac = null; }
Rozhraní Control Toto rozhraní je určeno k ovládání vlastností přehrávání zvukových dat. Neobsahuje žádné metody ani pole (ve smyslu datové prvky – fields). V balíku jsou dvě konkrétní rozhraní, která rozhraní Control rozšiřují. Těmi jsou rozhraní VolumeControl, sloužící k nastavení nebo zjištění hlasitosti zvuku a rozhraní ToneControl k přehrávání sekvencí tónů.
Grafické uživatelské rozhraní: Pro vývoj MIDP se nabízela možnost použít podmnožinu soupravy (Abstract Windows Toolkit - AWT) nebo součásti Swing z J2SE. Ani jedno však není praktické řešení. Nasazení Swingu vylučuje omezení prostředků a základní model pro uživatelská rozhraní založený na AWT je příliš složitý, než aby byl použit na malých zařízeních. AWT i Swing jsou založené na tom, že poskytují maximální svobodu při vytváření rozmanitých rozhraní GUI v prostředí s více okny. Vzhledem k omezené velikosti obrazovky nemohou uživatelé mobilních telefonů pracovat s více než jedním oknem či s více než jedním midletem najednou. Místo snahy najít vhodnou podmnožinu AWT pro omezené prostředí byla zavedena daleko jednodušší množina součástí a odlehčený programovací model založený na obrazovce. Výsledkem je knihovna tříd, která je o hodně menší, snáze použitelná, méně náročná na paměť a prostředky procesoru než Swing či AWT. Použití této knihovny tříd je ovšem na úkor přesnějšího ovládání vzhledu uživatelského rozhraní mobilního zařízení. Jedná se o tzv. „vysokoúrovňové“ API funkce, které neumožňují změnu úpravy barev, písem nebo rozvržení komponent. Funkce jež mají mnohem větší úroveň kontroly nad zobrazováním se nazývají „nízkoúrovňové“ API. Ty poskytují přesný opak vysokoúrovňového API – úplnou kontrolu nad obrazovkou, přístupem ke klávesnici a libovolným ukazovacím zařízením, které je k dispozici. Použití nízkoúrovňových API znamená větší kontrolu nad zařízením, ale také větší zodpovědnost při navrhování aplikace, což znamená psát kód tak, aby zařízení reagovalo na každý stisk klávesnice či pohyb ukazatele.
Základem všech uživatelských rozhraní MIDletů je rodičovská třída Displayable. Od ní je odvozena sada užitečnějších tříd, které lze použít jako základ k sestavení skutečných uživatelských rozhraní. Hierarchii těchto tříd ukazuje obr. 6. Třída Displayable má dvě
přímé podtřídy, které jsou rovněž abstraktní. Tyto podtřídy jsou výchozími body dvou různých
stylů
programování
uživatelského
rozhraní,
jež
podporuje
balíček
javax.microedition.lcdui.
Obrázek 3 – Hierarchie grafického rozhraní Canvas Tato třída představuje základ nízkoúrovňových API pro grafická uživatelská rozhraní (GUI). Chová se jako prázdný list papíru pokrývající většinu obrazovky. Za účelem vytvoření GUI je vytvořena podtřída třídy Canvas a pro kreslení na obrazovku je implementována metoda paint(). Na uživatelský vstup lze také reagovat překrytím metod, jež lze volat v reakci na stisk kláves a pohyb ukazatele. Nízkoúrovňové API neobsahují žádné jednotlivé komponenty k zajištění vstupu textu, zobrazení seznamů, nabídky voleb atd. Tyto API jsou nejvíce využívány při psaní grafických her nebo zobrazování dat pomocí grafů. Screen Jde o bázovou abstraktní třídu od které se odvozuje vrchol hierarchie oken vysokoúrovňového API. Na rozdíl od Canvas není ale vytvářena její podtřída za cílem
implementace uživatelského rozhraní midletu. Screen přidává do Displayable možnost volitelného textu titulku nebo běžícího textu, který zobrazuje nepřetržitě se posouvající textovou zprávu. Nejběžněji používaná podtřída je Form, která umožňuje sestavit uživatelské rozhraní přidáním standardních komponent (nazývaných Item). List, TextBox a Alert jsou ekvivalenty dialogu v MIDP. Vysokoúrovňové API, na rozdíl od nízkoúrovňového, nedovoluje kreslit přímo na obrazovku nebo se zabývat událostmi přímo z klávesnice nebo ukazatele. Místo toho jsou tyto události zpracovány vnitřně a je-li to vhodné, jsou převedeny na vysokoúrovňové události vyvolané položkami Item zobrazenými na displeji uživatele. Herní rozhraní – Game API Game API poskytuje množství tříd, které umožňují snadněji vyvíjet hry pro bezdrátová zařízení. Snižuje množství kódu nutný pro vytvoření aplikace, a tím také velikost, která je jedním z hlavních limitujících faktorů. Používá standardní nízkoúrovňové grafické třídy, např. Graphics, Image aj., a tak je možné využívat metody těchto základních grafických objektů. Jedna z možností je vykreslit kompletní pozadí a poté, s pomocí výše zmíněných základních tříd, na něj kreslit základní grafické objekty jako jsou přímky, čtverce apod. Pro vykreslování objektů se v MIDP používá standardní přístup z Javy. Použití metod, které mění stav objektů jako jsou Layer, LayerManager, Sprite a TiledLayer, na ně nemá okamžitý vizuální efekt. Tyto objekty jsou překreslovány až při následném volání metody paint(). Game API disponuje pěti třídami jež jsou popsány níže. Layer Základní koncept je takový, že obrazovka je složena z jednotlivých vrstev. Každá vrstva má svoji pozici - základní pozice je na souřadnicích [0, 0] - výšku, šířku a může být
viditelná nebo neviditelná. Její jednotlivé podtřídy musí implementovat metodu paint(), aby mohly být také vykreslovány. GameCanvas Tato třída poskytuje základní obrazovkovou funkcionalitu. Jedná se o podtřídu třídy javax.microedition.lcdui.Canvas, takže kromě jejích schopností disponuje také grafickým bufferem sloužícím k efektivnímu vykreslování bez zbytečných prodlev a problikávání. Umožňuje také např. zjišťovat stavy jednotlivých tlačítek klávesnice používaných ve hře. LayerManager Pomocí něj lze ovládat jednotlivé vrstvy (jednotlivé instance TiledLayer). Udržuje indexovaný seznam vrstev, do něhož mohou být připojovány, vkládány a vyjímány jednotlivé vrstvy. Ty jsou pak zobrazeny v tom pořadí, v jakém se nacházejí v daném seznamu. LayerManager umožňuje nastavit "pohled" na libovolnou scénu, která je pak zobrazena na displeji uživateli a následné posouvání "pohledu" vyvolává tzv. scrollingefekt. Sprite Jedná se o potomka třídy Layer, který umožňuje z Image objektu tvořit animace. Na Sprite objekt lze aplikovat různé transformace jako zrcadlení či otáčení. Každý frame má svoji velikost a šířku a ve Sprite objektu může být uložen v libovolném pořadí. TiledLayer Jedná se o objekt, jež je tvořen rastrem jednotlivých buněk, které obsahují obrázky. Používá se především pro tvorbu vrstev tvořící pozadí ve hrách.
Push architektura Takzvaný „push mechanizmus“ umožňuje spustit aplikaci automaticky bez přispění uživatele. Spuštění může být buď načasováno na konkrétní okamžik, nebo iniciováno přes síťové rozhraní z jiného zařízení. Všechna příchozí spojení musí být registrována buď staticky atributem v souboru s příponou .jad, nebo dynamicky za běhu aplikace metodami objektu PushRegisty. Ten je součástí manažera javových aplikací a má na starosti životní cyklus aplikace. Udržuje seznam příchozích spojení a alarmů a k nim seznamy midletů, jež se mají spustit při jednotlivých událostech. Spuštění midletu probíhá stejně jako při přímém spuštění uživatelem, a to zavoláním metody MIDlet.startApp(). Descriptor: MIDlet-Name: SunNetwork - Chat Demo MIDlet-Version: 1.0 MIDlet-Vendor: Sun Microsystems, Inc. MIDlet-Description: Network demonstration programs for MIDP MicroEdition-Profile: MIDP-2.0 MicroEdition-Configuration: CLDC-1.0 MIDlet-1: InstantMessage, /icons/Chat.png, example.chat.SampleChat, * MIDlet-Push-1: datagram://:79, example.chat.SampleChat, * MIDlet-Permissions: javax.microedition.io.PushRegistry, \\ javax.microedition.io.Connector.datagramreceiver Ukázkový MIDlet – přijetí datagramu public class SampleChat extends MIDlet { // Current inbound message connection. DatagramConnection conn; // Flag to terminate the message reading thread. boolean done_reading; public void startApp() { // List of active connections. String connections[]; // Check to see if this session was started due to // inbound connection notification. connections = PushRegistry.listConnections(true); // Start an inbound message thread for available // inbound messages for the statically configured // connection in the descriptor file. for (int i=0; i < connections.length; i++) { Thread t = new Thread (new MessageHandler( connections[i]));
t.start(); } ... } } // Stop reading inbound messages and release the push // connection to the AMS listener. public void destroyApp(boolean conditional) { done_reading = true; if (conn != null) conn.close(); // Optionally, notify network service that we're // done with the current session. ... } // Optionally, notify network service. public void pauseApp() { ... } // Inner class to handle inbound messages on a separate thread. class MessageHandler implements Runnable { String connUrl ; MessageHandler(String url) { connUrl = url ; } // Fetch messages in a blocking receive loop. public void run() { try { // Get a connection handle for inbound messages // and a buffer to hold the inbound message. DatagramConnection conn = (DatagramConnection) Connector.open(connUrl); Datagram data = conn.newDatagram(conn.getMaximumLength()); // Read the inbound messages while (!done_reading) { conn.receive(data); ... } } catch (IOException ioe) { ... } ...
Alarm v rámci PushRegistry Midlet si může zaregistrovat čas, ve kterém chce být spuštěn. Je to praktické např. pro pravidelnou kontrolu serveru, zda je vše v pořádku, nebo implementaci budíku v Javě. Každý midlet může mít zaregistrován maximálně jeden alarm, další registrací by se starý
alarm přepsal. Např. při implementaci kalendáře je potřeba zjistit, která událost se stane nejdříve a zaregistrovat u PushRegisty její čas.
Příchozí spojení v rámci PushRegistry Ke spuštění midletu pomocí síťového spojení se hodí takové protokoly, které umožňují iniciaci spojení ze serveru, což jsou například datagramy nebo soketové spojení. Příchozí spojení mohou být u PushRegisty registrována staticky v deskriptoru aplikace nebo dynamicky po spuštění aplikace. Dynamická registrace probíhá za běhu aplikace pomocí javového rozhraní PushRegisty metodou registerConnection(). Ta má stejné parametry jako statická registrace a může při ní nastat mnoho výjimek Kromě použití push mechanizmu potřebuje sada midletů kromě práva javax.microedition.io.PushRegisty ještě právo na použití protokolu, který má být k příchozímu spojení použit. Manažer javových aplikací je za monitorování příchozích spojení odpovědný v době, kdy aplikace neběží. Po spuštění aplikace a otevření spojení je aplikace odpovědná za příchozí spojení sama až do ukončení aplikace. Zavře-li aplikace spojení, budou ztracena všechna data, která přijdou před jejím ukončením. Pokud midlet o příchozí spojení dál nemá zájem, může je odregistrovat.
Ant, Antenna a Java ME Ant (respektive jeho odnož pro Java ME – Antenna) je systém pro sestavování aplikací na základě XML skriptu. Podpora Antu (resp. Antenny) je obvykle integrována ve většině vývojových nástrojů cílených na jazyk Java. Co Ant umí: základní operace se soubory a adresáři (vytvoření/přejmenování/mazání), spouštění externích programů, preprocessing (úprava kódu ještě před samotným překladem - jako v jazyce C), build, preverifikace, obfuskace, zabalení, vytvoření deskriptoru, spuštění emulátoru a další. Ukázka Antenna skriptu <project name="TheGame" default="build" basedir="."> <property name="wtk.home" value="C:\WTK22" /> <property name="wtk.midpapi" value="${wtk.home}\lib\midpapi.zip" /> <property name="midlet.name" value="TheGame" />
<wtkjad jadfile="${midlet.name}.jad" jarfile="${midlet.name}.jar" name="${midlet.name}"> <midlet name="${midlet.name}" class="Main" /> <wtkbuild srcdir="src_prep" source="1.2" preverify="false" />
destdir="classes"
target="1.1"
<wtkpackage jarfile="${midlet.name}.jar" jadfile="${midlet.name}.jad" obfuscate="true" preverify="true" basedir="classes" libclasspath="res" /> <wtkrun jadfile="${midlet.name}.jad" device="Nokia_S40_DP20_SDK_1_1"
wait="true"/>
Bajtkód a obfuskace
Nevýhodou bajtkódu je jeho 100% dekompilatovatelnost. S využitím vhodného decompileru např. DJ Java Decompiler (http://members.fortunecity.com/neshkov/dj.html) je možné z class souboru získat zpět zdrojový kód včetně všech názvů tříd, proměnných atd. Jednou z možností jak tomu zabránit, je tzv. obfuskace. Obfuskace je proces, při kterém program, obfuskátor (obfuscator – nejznámější: ProGuard, RetroGuard, JShrink) provede s bajtkódem několik operací: odstraní přebytečné (nepoužité) proměnné, metody a třídy, optimalizuje bajtkód, ale především „ořízne“ veškeré uživatelské názvy tříd, proměnných, metod atd. Takto upravený class soubor je až o 30% menší než původní. Toho se již standardně užívá právě u mobilních aplikací, protože nejenom znemožňuje dekompilaci (respektive znepřehlední dekompilovaný kód), ale zkrácením jmen také výrazně zmenší velikost výsledných class souborů. Ukázka metody před protected final void startApp() throws MIDletStateChangeException { display = Display.getDisplay(this); splash = new SplashScreen(); display.setCurrent(splash); splash.init(); gameCanvas = new MyGameCanvas(); gameCanvas.init(this); splash.run(); splash = null; menu = new Menu(this); menu.show(0); }
a po obfuskaci (zůstaly zachovány jen názvy tříd a výjimek z API) protected final void startApp() throws MIDletStateChangeException { a = Display.getDisplay(this); b = new g();
a.setCurrent(b); b.a(); d = new e(); d.a(this); b.b(); b = null; c = new c(this); c.a(0); }
Životní cyklus midletu
Spouštění a zavádění MIDletu třídou javax.microedition.midlet.MIDlet probíhá v několika krocích. Po požadavku OS na spuštění se zavolá bezparametrický konstruktor třídy a je vytvořena nová instance. Aplikace se nachází v tzv. přerušeném stavu. Když je vše připraveno, metoda startApp() uvede midlet do stavu aktivního. Ten označuje normální běh MIDletu. Zavoláním metody notifyDestroyed() a následným spuštěním destroyApp(boolean unconditional) je možné MIDlet uvést do stavu zrušení a fakticky ho ukončit. V běžícím stavu je možno ho také přerušit a to buď z programu metodou notifyPaused() nebo se o to postará správce aplikací (např. při příchozím hovoru). V obou případech je zavolána metoda pauseApp(). Zpět do aktivního stavu MIDlet vrátí resumeRequest(). Všechny metody měnící stavy je možné (a vhodné) překrýt a postarat se v nich o alokaci/dealokaci zdrojů.
Životní cyklus midletu, zdroj: přednášky Vybrané partie z jazyka Java, Petr Hnětynka, UK
Vysoko- a nízko-úrovňová API
GUI (Graphics User Interface – grafické uživatelské rozhraní) se v MIDP skládá z tzv. vysokoúrovňových a nízkoúrovňových API. Vysokoúrovňové API
Vysokoúrovňové API jak už z názvu vyplývá, operuje vysoko nad komponentami grafických a uživatelských rozhraní, je tedy více abstraktní. Obsahem je základní balík grafických komponent (AWT z Java SE) Programátor nemá moc možností jak ovlivnit vzhled aplikace a programování vypadá ve stylu metod nakresliTlacitko() nebo coSeVybraloZMenu(). Barvy, fonty a celý vzhled aplikace je závislý na implementaci rozhraní výrobcem zařízení. Tím pádem může stejná aplikace na různých zařízeních vypadat různě, avšak právě díky tomu je zaručena absolutní přenositelnost mezi všemi mobilními informačními zařízeními implementujícími daný profil. Mnoho práce tak za nás obstarává již implementace obsluhy GUI v zařízení. Programování je jednodušší, z hlediska uživatele je výsledek mnohem přívětivější (uživatel je „navyklý“ na určitý sjednocený grafický vzhled) Některé součásti, jako je například scrolling, uživatelský vstup, navigace apod. programátor vůbec neřeší, jsou totiž automaticky obstarávány zařízením. Třídy spadajících pod vysokoúrovňové API Screen – základní třída reprezentující „obrazovku“; pro představu lze použít přirovnání k jedné statické webové stránce: může obsahovat následující prvky List – výběrový seznam; ekvivalent HTML prvků checkbox a radio TextBox – textové vstupní pole; ekvivalent input type=text Alert – dočasné okno s upozorněním
Form – formulář; obalový prvek; může obsahovat další formulářové prvky: posuvníky, vstupní pole, texty, obrázky, seznamy apod.
Nízkoúrovňové API Nízkoúrovňové API je pak naprostý opak. Důraz je zde kladen na téměř nejnižší přístup ke komponentám zařízení (displej, tlačítka, stylus). Míra abstrakce je zde maximálně potlačena a metody vypadají typově jako nastavBarvuPixelu() nebo jeStisknutaKlavesa(). Dává nám tak maximální přístup k zařízení, avšak právě kvůli tomu se aplikace obvykle stávají závislými na zařízení a následně nepřenositelnými. Pro multimediální aplikace jsou důležitá obě rozhraní. Vysokoúrovňová pro implementaci navigací, menu, formulářových vstupů apod. a nízkoúrovňová pro samotný výkonný kód. Jelikož klademe důraz na multimediální možnosti mobilních zařízení, zaměříme se dále na nízkoúrovňová API. MIDP a 2D grafika V současné jsou grafické aplikace postaveny téměř výhradně na třídách Canvas a Image ze standardního balíčku javax.microedition.lcdui, případně na jejich „kamarádkách“ z MIDP 2.0 – GameCanvas, Sprite, Layer a dalších z balíku javax.microedition.lcdui.game. Pro začátek věnujeme tedy kousek prostoru těm méně známým a méně používaným rozhraním pro práci s 2D grafikou. M2G
Mobile 2D Graphics (M2G) známý také jako Scalable 2D vector Graphics API (JSR-226) je nepovinný balíček doplňující profil MIDP. Slouží zásadně pro práci s vektorovou grafikou, jejíž přednosti oproti rastrové grafice jsou především velikost souborů (u
mobilních zařízení podstatná výhoda) a snadná možnost transformace (velikosti, natočení atd.), protože vektorový formát je nezávislý na rozlišení. Rozhraní používá otevřený formát SVG-Tiny (http://www.w3.org/TR/SVGMobile/), což je oficiální úprava „velkého“ formátu SVG (http://www.w3.org/TR/SVG/), který nepodléhá žádné licenci a je volně šiřitelný. Setkat se s ním lze například na webu, přičemž sám o sobě umožňuje i animace. Třídy a rozhraní lze nalézt v balíku javax.microedition.m2g, popř. org.w3c.dom.svg. Canvas a ti ostatní v MIDP 1.0
Třída Canvas z balíku javax.microedition.lcdui je základní třídou pro aplikace vyžadující přímý přístup k displeji zařízení. Kromě zobrazování se stará také o zpracování vstupu od uživatele – poskytuje rozhraní k zachytávání událostí stisku kláves. Každá klávesa má přiřazen konstantní kód, pomocí kterého lze snadno zjistit, o kterou klávesu se jedná. Tyto kódy jdou definovány pro standardní ITU-T telefonní klávesnici. Ukázka čtení kláves protected void keyPressed(int keyCode) { if(keyCode > 0) switch(keyCode) { case Canvas.KEY_NUM4: // '4' // stisknuta klávesa s číslem 4 break; case Canvas.KEY_NUM6: // '6' // stisknuta klávesa s číslem 6 break; // ...
Canvas poskytuje také vstupní rozhraní k herním aplikacím. Zde jsou definovány abstraktní akce typu NAHORU, DOLEVA apod. Abstraktní jsou proto, že konstanta UP (nahoru) může na různých klávesnicích znamenat různé klávesy, čímž je ale zaručena přenositelnost. Implementace pak závisí na výrobci telefonu. Obsahem Canvasu jsou i metody na obsluhu stylusu, resp. dotykového diplaye v případě, že jej zařízení podporuje.
Instancí této třídy (nebo tříd zděděných, což je obvyklejší) může být při běhu víc. Aktivní (viditelná) však může být v jeden okamžik maximálně jedna. O předávání řízení se v tomto případě starají metody showNotify a hideNotify, které zaregistrují instanci k zachytávání vstupních událostí. Standardně je zobrazovací plochou Canvasu celý displej, nicméně v některých implementacích může být část displeje vyhrazena na nabídku příkazů (třída Command) nebo na titulek. Proto byla v MIDP 2 přidána metoda setFullScreenMode, která explicitně nastaví, zda má třída pokrývat celou plochu displeje. V MIDP 1 bylo toto chování řešit proprietátními API, typicky pro telefony Nokia bylo potřeba při požadavku na fullscreen použít namísto standardního Canvasu třídu FullCanvas z balíku com.nokia.mid.ui. O samotný obsah toho co se má vykreslit se stará metoda protected abstract void paint(Graphics g) kterou je třeba překrýt. Kontext, do kterého se bude kreslit je předán v parametru g. Důležité je však vědět, že samotné vykreslení se neděje voláním této metody, ale zprostředkovaně a to sice požadavkem z midletu na překreslení Display.getDisplay().setCurrent(aktualniObraz); nebo pomocí metody repaint() popř. systémového volání serviceRepaints(), které se liší tím, že pozastaví ostatní systémové akce jako čtení kláves, stylusu apod. a provede pokud možno okamžité překreslení. Graphics - základní třída pro vykreslování 2D primitiv. Podporovány jsou 24bitové barvy (RGB - 3x8 bitů) a škála metod pro základní kreslení – čar, obdélníků, polygonů, textu, zakřivených čar a dalších. To, zda se na barevnou škálu použije všech 24 bitů záleží samozřejmě na cílovém zařízení. Obecně však lze s touto barevnou hloubkou
pracovat, pak je totiž úkolem koncového zařízení provést potřebnou konverzi. Graphics je víceméně grafickým kontextem, který se získá od požadované plochy, na kterou chceme kreslit (obrazovka, off-screen) a získává se pomocí Image.getImage(), pokud chceme kreslit do off-screen obrázku (ten musí být vytvořen jako „muttable“, viz dále) nebo je předáván jako povinný parametr v metodě Canvas.paint(), pokud chceme kreslit přímo na displej. Souřadný systém je „zleva-doprava“ a „shora-dolů“, pixel v horním levém rohu má koordináty [0;0]. Pozor: koordináty neznamenají jednotlivé pixely, ale mřížku mezi nimi. Např. metoda fillRect(0, 0, 3, 2) vyplní samozřejmě předpokládanou oblast 3x2 pixely. Z praxe: bohužel, někteří výrobci telefonů si dokumentaci vykládají různě a na některých zařízeních ten samý kód kreslil obrázek o pixel nahoru či dolů oproti ostatním. Nastavení barvy pro kreslení všech čar a textů - metoda setColor(int RGB), přičemž parametr lze zapsat obvyklým hexadecimálním zápisem 0x00RRGGBB, u čar lze navíc nastavit styl čáry pomocí setStrokeStyle(int style), v nejběžnější verzi MIDP 2.0 zatím jen ve dvou stylech – plná čára a tečkovaně. Tloušťka čáry je vždy jeden pixel. Clipping (ořezávání) je metoda sloužící k nastavení obdélníkové oblasti v objektu Graphics, do níž smí grafický kontext zakreslovat. Používá se převážně k optimalizaci vykreslování, kdy uzamčení části plochy a kreslení do jednoho místa je rychlejší, než když zařízení musí celou plochu zpřístupnit k vykreslování. Další výhodou je možnost tvorby výřezů.
Zarovnání (anchor point) se používá při zobrazení obrázků a textů. Pomocí bitového OR lze snadno nastavit, zda vzhledem k zadanému bodu se má grafika vykreslit nalevo, napravo, uprostřed, nahoře či dole. Tato vlastnost slouží čistě k usnadnění práce programátora a zpřehlednění kódu.
Vykreslení obrázku doprostřed displeje klasickou metodou a s využitím anchor // klasicky g.drawImage(img, resolution.x/2 - obrX/2, resolution.y/2 – obrY/2, 0); // a s anchor point g.drawImage(img, resolution.x/2, resolution.y/2, Graphics.HCENTER | Graphics.VCENTER);
Image - třída z balíku javax.microedition.lcdui je určena k uchování grafických rastrových dat. Objekt typu Image může vzniknout dvojím způsobem a podle toho se typově dělí na mutable a immutable. Obrazy mutable (měnitelné) vznikají zavoláním Image.createImage(int width, int height)
Tato metoda vytvoří prázdný obraz o rozměrech width x height do kterého lze po získání kontextu Graphics kreslit, kopírovat fragmenty či celé další obrázky. Narozdíl od toho obrazy typu immutable (neměnné) nelze po jejich vytvoření a iniciování měnit ani na ně nijak kreslit. Typicky se jedná o proměnné Image vytvořené nahráním ze zdrojového souboru či bloku dat v paměti. Máme-li však požadavek na editaci grafiky získané ze souboru, lze immutable obrázek snadno přeměnit na muttable pomocí jednoduchého kódu:
Konverze immutable objektu na objekt mutable Image source; // zdrojový obrázek source = Image.createImage(...cesta k souboru); Image copy = Image.createImage(source.getWidth(), source.getHeight()); Graphics gCopy = copy.getGraphics(); gCopy.drawImage(source, 0, 0, TOP|LEFT); -----------------------------------------------------------------------------------Pozor: častou chybou bývá získávání kontextu např. při každém průchodu cyklem. Nejenom že se kód citelně zpomalí, ale především bude nefunkční, protože získaný kontext je při každém volání getGraphics() jiný. Následující ukázkový kód nebude fungovat korektně: img.getGraphics().setColor(0x00FF0000); // nastavení barvy štětce na červenou img.getGraphics().drawLine(0, 0, 10, 10); // čára se vykreslí implicitní barvou (černou)
narozdíl od správného: Graphics g = img.getGraphics(); g.setColor(0x00FF0000); // nastavení barvy štětce na červenou g.drawLine(0, 0, 10, 10); // čára se již vykreslí červeně (pokud nemáme černobílý displej :)
Standardním obrazovým formátem je bezeztrátový kompresní formát PNG (Portable Network Graphics) ve verzi 1.0. Podpora dalších typů je závislá na libovůli výrobce zařízení. (Mezi obvyklé patří ještě GIF a BMP, nicméně oproti PNG nepřináší žádnou výhodu – BMP jako nekomprimovaný formát je datově příliš veliký pro nasazení na mobilních zařízeních, GIF je pak omezen na škálu maximálně 256 barev a navíc podléhá tvrdé licenční politice.)
Průhlednost Neměnné (immutable) obrazy mohou obsahovat kromě neprůhledných a plně průhledných i poloprůhledné pixely (narozdíl od mutable, které nemohou mít průhledný byť jediný pixel). Implementace Javy na konkrétním zařízení musím umět pracovat s plnou nebo žádnou průhledností. Podpora částečné průhlednosti (alpha blending) je opět implementačně závislá na výrobci a nelze se tedy na její podporu vždy spolehnout. Od MIDP 2 lze alespoň zjistit, kolik úrovní průhlednosti zařízení podporuje metodou Display.numAlphaLevels() Vrácená hodnota bude vždy alespoň 2, větší číslo znamená podporu částečné průhlednosti. Uživatelské rozhraní v MIDP Abstraktní třída Displayable je základním elementem zobrazitelným na displeji. Obsahuje v sobě mechanismus pro práci s příkazy (Commands). Protože třída je definována jako abstraktní, zaměříme se na její potomky. Displayable má dva přímé potomky: Canvas a Screen. Canvas, česky plátno, je zástupce low-level API, Screen, česky obrazovka, je zástupcem high-level API.
Hierarchie tříd grafického uživatelského rozhraní MIDP
Příkazy Commands jsou mechanismem pro obsluhu vysokoúrovňových událostí. Filozofie práce s nimi je taková, že objektu, který je potomkem Displayable, případně potomkem Screen se přiřadí libovolný počet příkazů (Command), každý určeného typu a s určitou prioritou. Protože obsluha těchto příkazů probíhá obvykle pomocí kontextových kláves, kterých je omezené množství (obvykle dvě), pomáhá určení typu a priority sdělit aplikačnímu rozhraní, které příkazy má zobrazit přímo a které se „schovají“ v podnabídce. O následnou obsluhu událostí generovaných těmito příkazy se stará metoda commandAction() z rozhraní CommandListener.
Použití Commands v praxi public class Main extends MIDlet implements CommandListener { // základní proměnné private List sampleList; // seznam private Command exitCmd = new Command("Exit", Command.EXIT, 1); // příkaz private Command backCmd = new Command("Back", Command.BACK, 1); // příkaz // inicializace při spuštění aplikace public void startApp() { sampleList = new List("Samples", List.IMPLICIT); // inicializace seznamu for (int i = 0; i < SAMPLES.length; i++) { // naplnění seznamu SAMPLES[i].getDisplayable().addCommand(exitCmd); SAMPLES[i].getDisplayable().addCommand(backCmd); SAMPLES[i].getDisplayable().setCommandListener(this); sampleList.append(SAMPLES[i].getName(), null); } sampleList.addCommand(exitCmd); sampleList.setCommandListener(this); Display.getDisplay(this).setCurrent(sampleList);
} // metoda na zachytávání akcí public void commandAction(Command command, Displayable displayable) { if (command == exitCmd) { // příkaz „konec aplikace“ destroyApp(true); notifyDestroyed(); } else if (command == backCmd) { // příkaz „krok zpět“ Display.getDisplay(this).setCurrent(sampleList); // zobrazení seznamu } else if (command == List.SELECT_COMMAND) { // zobrazení požadované třídy } } }
GameCanvas a ti ostatní v MIDP 2.0 Nové třídy určené primárně k usnadnění vývoje herních aplikací. Bezdrátová zařízení jsou tvrdě limitována výpočetním výkonem a pamětí => každý přesun kódu do API knihoven znamená zvýšení výkonu. Game API nepřidává téměř nic, co by nebylo možná napsat pomocí MIDP 1, ale přenáší obvykle používané konstrukce do standardních knihoven. Metody měnící vlastnosti (pozice, viditelnost...) následujících objektů nemají žádný přímý viditelný výsledek. Hodnoty jsou uloženy uvnitř objektů a změna se projeví až při překreslení metodou paint(). Tento přístup je vhodný právě pro herní aplikace, kde herní cyklus sestává obvykle ze sekvence čtení vstupu-výpočet-vykreslení.
Celé API je balíkem pěti tříd GameCanvas – podtřída třídy Canvas poskytuje základní rozhraní pro zobrazovací funkce. Obsahuje všechny zděděné metody a navíc přidává frontu událostí vstupního zařízení tak jako je tomu v desktopových OS (v MIDP 1 se metody pro čtení stavu kláves musely „trefit“ do okamžiku kdy klávesa byla zrovna stisknuta; pokud byla smyčka aplikace příliš dlouhá a uživatel stiskl tlačítko velmi krátce, mohlo dojít k tomu, že se stisk vůbec nezaznamenal) a nativní podporu pro double buffer. Třída je vytvořena děděním a je zpětně kompatibilní, lze tedy používat všechny konstrukce známé z normálního lowlevel API. Layer – abstraktní třída reprezentující viditelný objekt; je definována pozicí, rozměry a viditelností; dědí od ní třídy Sprite a TiledLayer LayerManager – zastřešující třída pro objekty typu Layer; usnadňuje vykreslování většího počtu objektů a poskytuje podporu pro snadné vytváření a úpravy pohledu na vykreslovanou scénu; vloženým objektům přiřazuje úroveň vnoření a tím řeší problematiku viditelnosti – jakýsi zjednodušený z-buffer Sprite – potomek Layer; reprezentuje základní grafický prvek; umožňuje vytváření animací a jednoduchou rotaci či zrcadlení; primární vlastnosti a dovednosti: • vytváření frame-sekvencí; obvyklá délka sekvence je rovna počtu framů, je však možné si nadefinovat vlastní sekvenci s libovolným množstvím framů, které se mohou opakovat, vynechávat jednotlivé snímky či vytvářet reverzní animaci; každý Sprite může obsahovat jen jednu libovolně dlouhou sekvenci • referenční pixel; chceme-li vykreslit Sprite v bodě [x;y], znamená to, že od tohoto bodu se vykreslí levý horní roh; toto chování lze změnit nastavením referenčního pixelu na libovolný bod v oblasti Spritu • transformace; existují zde jen výpočetně nenáročné transformace – otáčení při kroku
90° a/nebo zrcadlení • kolize; Sprite umožňuje vyhodnocování kolizí mezi Sprity, TiledLayer nebo objektem Image a to buď na úrovni kolizního obdélníku nebo metodou pixel-bypixel, kde je detekována kolize pixelů s nulovou průhledností TiledLayer – třída pro vytváření čtvercových map pomocí tzv. „tiles“ (dlaždic); obvyklá technika pro vytváření různých pozadí; protože mobilní zařízení jsou paměťově výrazně omezená, není možné jako pozadí/mapu použít velký obrázek, proto se používá metoda, kdy jsou do čtvercové sítě kladeny jednotlivé buňky, které na sebe motivem navazují a vytvářejí pocit jednolitého pozadí; princip je podobný jako u Sprite – jednotlivé framy mají svůj index a podle matice definující pozadí jsou skládány vedle sebe; jednotlivé buňky mohou být také animovány
Matice pozadí a výsledný obraz, zdroj: dokumentace WTK [8] Animace rotace Zeměkoule pomocí Sprite private javax.microedition.lcdui.game.Sprite zeme; // inicializace zeme Image spriteImage=null; try { spriteImage = Image.createImage("/earth.png"); } catch(Exception e) { e.printStackTrace(); } zeme = new javax.microedition.lcdui.game.Sprite(spriteImage, 50, 50); // vykreslování
zeme.paint(_gBuffer); zeme.nextFrame();
MIDP a 3D grafika OpenGL ES OpenGL ES (JSR-239) je určen především pro vývojáře kteří znají OpenGL na PC. M3G Pozadí vzniku Vzhledem ke vzrůstajícímu výkonu mobilních zařízení se postupem času došlo k závěru, že jsou již připravena pro implementaci 3D rozhraní. Prvním byl renderovací engine Mascot Capsule na telefonech firmy SonyEricsson. Jen pro zajímavost, jeho výkon je i dnes obvykle vyšší než dále zmíněné M3G. Na jeho základě vznikla expertní komise předních vývojářů pro mobilní zařízení, která měla za úkol vytvořit standardizované API pod hlavičkou JCP. Tak 19. listopadu 2003 vznikla první finální specifikace JSR-184, nazvaná Mobile 3D Graphics API (M3G API). To je zaměřeno na zařízení s malým výpočetním výkonem a pamětí a která nemají hardwarovou podporu 3D grafiky (myšleno GPU). Implementace rozhraní může být celá dokonce čistě softwarová. Pokud však zařízení má k dispozici specializované hardwarové prostředky pro rychlejší výpočty, API je umí využít. Při návrhu se bralo v potaz i předpokládané využití – hry, vizualizace map, spořiče obrazovek, graficky bohatá uživatelská rozhraní a další. Z těchto rozdílných požadavků nakonec vzešlo řešení dvou různých úrovní přístupu – retained a immediate mode, viz níže. API vyžaduje podporu čísel s desetinnou čárkou, respektive CLDC 1.1.
Obecná teorie Systém M3G používá pravotočivý souřadný systém. 3D prostor je orientován tak, že horizontální posun značí osa x, vertikální osa y a posun v hloubce osa z. Toto rozložení používají oba majoritní 3D zobrazovací systémy i na platformě PC (Direct3D, OpenGL). Definice se někdy rozchází v tom, zda kladný přírůstek na ose z znamená přiblížení ke kameře (pozorovateli) nebo naopak oddálení. V M3G je osa z orientována (má kladný přírůstek) směrem ke kameře. Objekty v běžných 3D zobrazovacích systémech reprezentují shluky polygonů (núhelníků). Tyto polygony lze pak rozložit na menší jednotky – trojúhelníky, které tvoří nejmenší možnou zobrazitelnou plochu. Vrcholy každého trojúhelníku jsou body v prostoru definované třemi [X, Y, Z] souřadnicemi. Tyto vrcholy se nazývají vertexy.
View frustum, zdroj: IBM developerWorks
Již byl zmíněn pojem kamera. Kamera je okem pozorovatele. Je dána svojí pozicí, úhlem natočení (ve všech třech osách), pozorovacím úhlem (viewing angle) a řeznými rovinami. Pozorovací úhel a řezné roviny definují v prostoru jakýsi čtyřstěnný jehlan se seříznutou špicí (view frustum) a všechny objekty které se budou nacházet uvnitř
tohoto jehlanu budou zobrazeny na výsledném zobrazovacím zařízení (v našem případě obvykle LCD mobilního zařízení).
Mobile 3D Graphics API
MIDP
CLDC 1.1
Vztah M3G, MIDP a CLDC API Veškeré třídy a rozhraní se nacházejí v balíku javax. microedition.m3g. V aplikaci lze zjistit dostupnost rozhraní zavoláním System.getProperty("microedition.m3g.version"), které vrátí číslo verze, respektive null, pokud API není přítomno. Krom základních tříd jsou v balíku přítomny třídy popisující materiálové vlastnosti, klíčování animací, meshe, textury, systémy hierarchie scény a další. Důležité třídy, tak jak jsou popsány v dokumentaci [10]: • Appearance – soubor komponent popisujících 3D objekt (textura, materiál, kompozice...) • Camera – uzel grafu scény popisující pozici a polohu pozorovatele a parametry projekce (pozorovací úhel, perspektiva...)
• Graphics3D – grafický kontext (třída je jedináček – singleton, získává se statickou metodou getInstance()), nastavuje cíl renderingu, jeho parametry (antialiasing, dithering...) a realizuje samotné vykreslování pomocí jedné ze čtyř metod render (2 pro retained mode a 2 pro immediate mode) • Image2D – rastrový obrázek pro použití jako textura, pozadí nebo sprite; může být mutable i immutable (viz. Sprite v kapitole 5.4) • Loader – třída obsahující dvě statické metody pro vytváření známých datových struktur – obecně Object3D (což zahrnuje World, Image2D a další) • Mesh – uzel grafu scény popisující 3D objekt složený z polygonů • Node – abstraktní třída obecného uzlu • Object3D - abstraktní třída; základ všech objektů umístitelných do 3D prostoru • Transform – obecná transformační matice 4x4 • World – úložiště pro graf scény; potomek třídy Group nejvyšší úrovně (kořen scény)
Scene graph Graf scény je stromovou strukturou - místo kde se dělí větev se nazývá uzel (node). Uzel na vrcholu této struktury se nazývá kořen (World node). Uzlem mohou být meshe (objekty), sprity, světla a celé skupiny těchto objektů zabalené do uzlu pomocí obalové třídy Group. S těmi se pak zachází jako s jedním objektem, což je výhodné pro skupiny prvků které spolu nějak logicky souvisejí. Přestože World je také typu uzel, nemůže být sám o sobě jeho součástí, tzn. World musí stát vždy na vrcholu pyramidy. Výhoda použití grafu scény je, že s malým programovacím úsilím lze snadno zobrazit komplexní scénu. Stromový typ grafu klade však některá omezení. Jeden uzel (node) může náležet vždy maximálně do jedné skupiny (Group). Graf musí být větvený, tzn. je třeba se vyhnout zacyklení větví.
Restrikce ve stromu grafu, zdroj: Mobile 3D Graphics API Specification
Důležité je také přidělování a správa takzvaných „user ID“ jak uvidíme později na příkladu. Ty slouží ke rychlé a jednoznačné identifikaci objektů ve scéně. V modelovacím nástroji je možné používat vícero kamer než je jen jedna. Ty se vyexportují společně se zbytkem scény a lze mezi nimi snadno přepínat právě třeba pomocí „user ID“. Scene graph se snadno vytvoří exportem z běžného 3D modelovacího nástroje (3D Studio MAX – ve verzi 8 má exporter zabudován, Blender – pro export existuje Python skript atd.) a to včetně světel, textur a kamer. Zde prezentovaný příklad byl vytvořen právě pomocí open source nástroje Blender. Strukturu je samozřejmě možné vytvořit i programově, za běhu.
Ukázka struktury grafu, zdroj: Mobile 3D Graphics API Specification Immediate a retained mode M3G obsahuje dva zobrazovací módy. Immediate, což je nízkoúrovňové API, založené na derivátu OpenGL ES), používá se k zobrazování základních prvků – vertexů, trojúhelníků, jednotlivých světel atd. Umožňuje absolutní kontrolu nad zobrazovanou scénou, příkazy jsou okamžitě vyhodnoceny a zpracovány grafickou jednotkou. Oproti tomu retained mode představuje abstraktnější vysokoúrovňové API, kde se využívá především importu celých scén vytvořených externími modelovacími programy. Tyto scény v sobě obsahují stromové grafy sestávající z uzlů - meshů (logické celky objektů), systémů světel, virtuálních kamer a dalších objektů, ke kterým se v programu snadno přistupuje přes jednoznačný identifikátor (ID) jak již bylo výše řečeno. Standard dále definuje formát dat modelů, včetně texturování a systému animací. Oba módy lze kombinovat a používat najednou, avšak při použití retained módu a zavolání render(World) se použijí světla a kamera definovaná ve World, ignorují se tedy všechna předchozí nastavení. Toto neplatí při vykreslení pomocí render(Node, transform).
Popsání alespoň hlavních mechanismů by bylo velmi rozsáhlé – pro co rychlé a srozumitelné předvedení uvádím okomentované příklady: Kostka, retained mode // ...inicializace // získání instance g3d = Graphics3D.getInstance(); // nahrání scény ze souboru Object3D[] parts=null; try { parts = Loader.load("/cube.m3g"); } catch(Exception e) { e.printStackTrace(); } _world = (World) parts[0]; // prvek na nultém indexu je vždy hlavní objekt World // získání odkazu na kostku (má definováno userID=12) // pro pozdější snazší manipulaci _cube=(Mesh)_world.find(12); // získání a nastavení kamery _camera = _world.getActiveCamera(); // nastavení projekce float[] lProjection = {0.0f, 0.0f, 0.0f, 0.0f}; _camera.getProjection(lProjection); float aspect = (float)getWidth()/(float)getHeight(); // nastavení prespektivy _camera.setPerspective( lProjection[0], // úhel pohledu aspect, // poměr stran lProjection[2], // první ořezávací rovina lProjection[3]); // druhá ořezávací rovina _world.setActiveCamera(_camera); // ... // a vykreslení _cube.postRotate(2.0f,0.0f,0.0f,1.0f); // rotace kostky try { g3d.bindTarget(g); // nastavení cíle (g je odkaz na grafický kontext) g3d.render(_world); // render } finally { g3d.releaseTarget(); // uvolnění cíle }
Kostka, immediate mode // definice proměnných; pozn.: pouze výňatky // vertexy private static final byte[] VERTEX_POSITIONS = { -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, -1, -1, ... };
1, // front 1, -1, // back
// indexy vertexů pro vytvoření triangle stripů private static final int[] TRIANGLE_INDICES = { 0, 1, 2, 3, // přední stěna 4, 5, 6, 7, // zadní stěna ... }; // délky triangle stripů v TRIANGLE_INDICES private static int[] TRIANGLE_LENGTHS = { 4, 4, 4, 4, 4, 4 }; // koordináty k namapování textury private static final byte[] VERTEX_TEXTURE_COORDINATES = { 0, 1, 1, 1, 0, 0, 1, 0, // přední 0, 1, 1, 1, 0, 0, 1, 0, // zadní ... }; // inicializace (zjednodušená) // získání instance graphics3d = Graphics3D.getInstance(); / vytvoření pozadí s barvou _background = new Background(); _background.setColor(0xFF113366); / vytvoření vertex bufferu _cubeVertexData = new VertexBuffer(); / vytvoření a nastavení pole vertexů VertexArray vertexPositions = new VertexArray(VERTEX_POSITIONS.length/3, 3, 1); VertexPositions.set(0, VERTEX_POSITIONS.length/3, VERTEX_POSITIONS); _cubeVertexData.setPositions(vertexPositions, 1.0f, null); / to samé pro textury VertexArray vertexTextureCoordinates = new VertexArray(VERTEX_TEXTURE_COORDINATES.length/2, 2, 1); VertexTextureCoordinates.set(0,
VERTEX_TEXTURE_COORDINATES.length/2, VERTEX_TEXTURE_COORDINATES); cubeVertexData.setTexCoords(0, vertexTextureCoordinates, 1.0f, null); // vytvoření polygonů (trojúhelníků) tvořících kostku _cubeTriangles = new TriangleStripArray(TRIANGLE_INDICES, TRIANGLE_LENGTHS); // vytvoření a nastavení kamery Camera camera = new Camera(); float aspect = (float) getWidth() / (float) getHeight(); camera.setPerspective(30.0f, aspect, 1.0f, 1000.0f); Transform cameraTransform = new Transform(); cameraTransform.postTranslate(0.0f, 0.0f, 10.0f); _graphics3d.setCamera(camera, cameraTransform); try { // vytvoření a nastavení textury Image2D image2D = (Image2D) Loader.load("/fel.png")[0]; // načtení ze zdroje _cubeTexture = new Texture2D(image2D); _cubeTexture.setBlending(Texture2D.FUNC_DECAL); // blending; viz. dokumentaci _cubeAppearance.setTexture(0, _cubeTexture); } catch (Exception e) { e.printStackTrace(); } // a vykreslení _cubeTransform.postRotate(2.0f, 0.0f, 1.0f, 0.0f); // rotace kostky (stejné jako u retained mode) try { _graphics3d.bindTarget(g); // nastavení cíle _graphics3d.clear(_background); // překreslení cíle pozadím _graphics3d.render(_cubeVertexData, _cubeTriangles, _cubeAppearance, _cubeTransform); // render } finally { _graphics3d.releaseTarget(); // uvolnění cíle }
Shrnutí M3G je vysokoúrovňové API a jako takové poskytuje rychlé výsledky. Pomocí modelovacího nástroje a několika řádek kódu je možné snadno vytvořit komplexní scénu. Má větší požadavky na výkon zařízení. To je způsobeno především požadavkem na výpočty s plovoucí desetinnou čárkou, který pro uspokojivý běh vyžaduje matematický koprocesor. Stejně jako u 2D, i zde se však lze pomocí optimalizace dosáhnout rozumných výsledků. Zvuky Podpora zvuku se v MIDP oficiálně objevila až od její druhé verze, nicméně už dřívější zařízení s podporou MIDP 1 byla schopna přehrávat zvuky s pomocí proprietárních API výrobců telefonů. Dnes na poli ozvučení existuje několik specifikací (standardů), které si následně rozebereme. Existují dvě hlavní skupiny zvuků lišící se principem, a to samply (datové proudy) a MIDI. Hlavní rozdíl je ve způsobu uložení, zatímco samply (wav, amr, mp3 atd.) obsahují pomocí převodníků zdigitalizované zvuky, MIDI definuje pouze kombinaci čas-notarychlost-hraný nástroj. Přehrávající zařízení tedy kompletně rekonstruuje (generuje) skladbu. V oblasti grafiky bychom našli analogii – rastrová grafika (samply) versus vektorová grafika (MIDI). Z toho následně plynou výhody i nevýhody. Hlavní výhodou, zvláště v oblasti zařízení s malými pamětmi je velikost dat. Protože v midi jsou zaznamenány pouze noty, může i několikaminutová pasáž zabírat pouze několik kB paměti. Na druhou stranu je však zřejmé, že takto zahrát lze pouze na nástroje, jež zařízení podporuje. Z toho plyne, že na různých zařízeních může ta samá skladba znít různě, či se dokonce některé nástroje nemusí přehrát vůbec. MIDI též z principu není schopno přehrát
nic jiného než hudbu, nedokáže tedy přehrát řeč, či různé ruchy, od toho jsou zde ostatní formáty. Mobile Media API Obecně MMAPI (JSR-135) obecně slouží k širšímu spektru akcí než je přehrávání zvuku. V závislosti na zařízení umožňuje též přehrávání videa nebo zachytávání zvuku či obrazu z videokamery. API bylo navrhováno jako vysokoúrovňové a dnes je v podstatě standardem, o čemž svědčí i seznam členů MMAPI Expert group: Nokia, Mitsibishi, Motorola, Philips, Siemens, Sun, Symbian, TI, Vodafone a další. Výsledkem jejich práce jsou ve skutečnosti dvě API: MMAPI a MIDP 2.0 Media API, které je pouze podmnožinou předchozího (je s ním tedy plně kompatibilní) a je zacíleno na zařízení s obecně sníženými schopnostmi multimediální reprodukce. Jak zmiňuje Lucie Rút Bittnerová v sérii článků „J2ME v kostce – jak na zvuk“, MMAPI je součástí většiny současných zařízení s podporou J2ME – od telefonů až po PDA. API se nachází v balíku javax.microedition.media, u některých výrobců bylo ukryto pod vlastní balík. Protože mají mít knihovny záběr v široké paletě různorodých zařízení, bylo definováno jako vysokoúrovňové. Základní koncept Architektura systému sestává ze tří hlavních částí: • třídy Manager • rozhraní Player • rozhraní Control
Architektura MMAPI, zdroj: developers.sun.com
Manager je přístupovým bodem ke všem možnostem přehrávání zvuku. Zodpovídá za dvě hlavní funkce: vytváření instancí Player a k přímému přehrávání tónů. Dále pak obsahuje metody pro zjištění podporovaných datových typů pro dané zařízení (povinná implementace je jen pro WAV a MIDI) metodou getSupportedContentTypes() a seznam podporovaných protokolů getSupportedProtocols(). Player je objekt, který je prostředníkem mezi zvukovým zdrojem (souborem) a jeho ovládacími prvky. Může být vytvořen třídou Manager buď ze zdroje typu InputStream, který je možno vytvořit například ze zdroje (resource) v JARu nebo z adresy URL. Po vytvoření metodou createPlayer() a zavolání start() se co nejrychleji alokují potřebné zdroje a spustí se přehrávání na pozadí, dokud skladba neskončí. To je nejjednodušší příklad použití. Podíváme se trochu hlouběji na kroky, které se provedou před samotným spuštěním skladby. Životní cyklus vytvořeného Player sestává z pěti stavů: UNREALIZED, REALIZED, PREFETCHED, STARTED a CLOSED. Jak se zmiňuje manuál [18], praktický význam těchto stavů je kvůli časové náročnosti jednotlivých kroků při požadavku na přehrání zvuku. Po zavolání createPlayer je Player v nerealizovaném
stavu. Zavoláním realize() se stav změní na realizovaný a v případě, že zvuková data byla definována jako URL, dojde k jejich stažení. Nakonec zavoláním prefetch() dojde k připravenému stavu, kdy jsou alokována zvuková zařízení a Player je připraven k okamžitému přehrávání. Zavoláním start() se přehraje zvuk a stav se změní na běžící. Skončí-li přehrávání buď metodou stop() nebo tím, že se přehrávač dostane na konec skladby, přejde opět do stavu připravenosti. Uvolnění zdrojů se provede z kteréhokoli předchozího stavu zavoláním close(). Cyklus možná lépe vystihne schéma.
Stavy objektu Player a jeho přechody, zdroj: J2ME v kostce, jak na zvuk
Zavoláním start() ze stavu UNREALIZED dojde k automatickému volání metod realize() a prefetch(), není tedy nutné všechny tyto metody exaktně volat. Pokud bychom vyžadovali zpětnou vazbu od přehrávače, je potřeba vytvořit třídu implementující rozhraní PlayerListener, respektive jeho metodu playerUpdate(Player player, String udalost, Object dataUdalosti) a tuto třídu u požadovaného přehrávače zaregistrovat. Ta se pak automaticky zavolá pokaždé, dojde-li k nějaké z událostí definovaných v rozhraní PlayerListener, například: konec skladby, vynucené stopnutí, změna hlasitosti, apod. Všechny události jsou podrobně rozepsány v dokumentaci [18]. Zajímavou pro praxi je schopnost přehrání několika zvukových vrstev současně. Tato vlastnost se bude jistě lišit přístroj od přístroje.
Control je rozhraní pro implementaci ovládacích prvků objektu Player. Jedná se o předka, který má dva přímé potomky: • ToneControl – užívá se pro přehrávání sekvencí jednoduchých tónů, více viz „Tónový generátor“ • VolumeControl – slouží k nastavení hlasitosti Tónový generátor Tónový generátor je důležitý pro zvukové aplikace jako jsou například hry. U velmi jednoduchých zařízení je to často jediný způsob jak z něj dostat zvuk. Vyvolat ho lze metodou Manager.playTone(int nota, int delka, int hlasitost). Pokud chceme přehrát celou sekvenci tónů (třeba krátkou písničku) je tento způsob nepohodlný. Lze využít vytvoření objektu Player s typem Manager.TONE_DEVICE_LOCATOR a následně přes rozhraní ToneControl nastavit sekvenci not třeba jako pole bytů, respektive datový typ audio/xtone-seq.
Immediate mode
Retained mode
Synchronní animace
Třída Anim.java public class Anim extends Canvas implements Sample, Runnable { private Sprite _sprite1; // sprite podle MIDP1 private javax.microedition.lcdui.game.Sprite _sprite2; // sprite podle MIDP2 // backbuffer private Image _buffer; private Graphics _gBuffer; private Thread _thread = null; // pomocné proměnné pro mechanismus řízení FPS long _startTime, _endTime; int _sleep; final int FPS=10; final int MSPERTICK=1000/FPS; // řídící proměnná pro _sprite1 int _actualFrame=0; // přehrávač hudby Player _player; // konstruktor Anim() { super(); } // pokyn pro zastavení public void stop() { _thread=null; // ukončení vlákna try { _player.stop(); // zastavení hudby } catch (MediaException e) { e.printStackTrace(); } } // pokyn pro spuštění public void showNotify() { init(); } // inicializace public void init() { // vytvoření backbufferu _buffer=Image.createImage(this.getWidth(),this.getHeight());
_gBuffer=_buffer.getGraphics(); // inicializace _sprite1 _sprite1=new Sprite("/earth.png",50,50); // inicializace _sprite2 Image spriteImage=null; try { spriteImage = Image.createImage("/earth.png"); } catch(Exception e) { e.printStackTrace(); } _sprite2=new javax.microedition.lcdui.game.Sprite(spriteImage,50,50); // spuštění vlákna _thread=new Thread(this); _thread.start(); // přehátí zvuku try { InputStream is = getClass().getResourceAsStream("ColdplayTrouble.mid"); _player = Manager.createPlayer(is, "audio/midi"); _player.start(); } catch (Exception e) { e.printStackTrace(); } } // překreslení obrazovky protected void paint(Graphics g) { _gBuffer.setColor(0); _gBuffer.fillRect(0,0,_buffer.getWidth(),_buffer.getHeight()); // smazání // _sprite1 _gBuffer.drawImage(_sprite1.getFrame(_actualFrame++),getWidth(),0,Graphics. TOP|Graphics.RIGHT); if(_actualFrame>=_sprite1.m_numFrames) _actualFrame=0; // _sprite2 _sprite2.paint(_gBuffer); _sprite2.nextFrame(); // vykreslení na obrazovku z backbufferu g.drawImage(_buffer,0,0,0); } // metoda vlákna
public void run() { while(_thread!=null) { // dokud vlákno nezrušíme _startTime=System.currentTimeMillis(); // zde je místo pro případnou herní logiku repaint(); // překreslení // a výpočet framerate _endTime=System.currentTimeMillis(); _sleep=(int)(MSPERTICK - (_endTime - _startTime)); if(_sleep<5) _sleep=5; // při 0 se zasekává vstup try { Thread.sleep(_sleep); } catch(Exception e) { e.printStackTrace(); } } } } Třída ImmediateMode.java public class ImmediateMode extends Canvas implements Sample, Runnable { private Thread _thread = null; // vertexy private static final byte[] VERTEX_POSITIONS = { -1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, // front 1, -1, -1, -1, -1, -1, 1, 1, -1, -1, 1, -1, // back 1, -1, 1, 1, -1, -1, 1, 1, 1, 1, 1, -1, // right -1, -1, -1, -1, -1, 1, -1, 1, -1, -1, 1, 1, // left -1, 1, 1, 1, 1, 1, -1, 1, -1, 1, 1, -1, // top -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, -1, 1 // bottom }; // indexy vertexů pro vytvoření triangle stripů private static final int[] TRIANGLE_INDICES = { 0, 1, 2, 3, // přední stěna 4, 5, 6, 7, // zadní stěna 8, 9, 10, 11, // pravá 12, 13, 14, 15, // levá 16, 17, 18, 19, // vrchní 20, 21, 22, 23, // spodní }; // délky triangle stripů v TRIANGLE_INDICES private static int[] TRIANGLE_LENGTHS = { 4, 4, 4, 4, 4, 4 }; // koordináty k namapování textury private static final byte[] VERTEX_TEXTURE_COORDINATES = {
0, 0, 0, 0, 0, 0, };
1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0,
// // // // // //
přední zadní pravý levý vrchní spodní
// vertex buffer private VertexBuffer _cubeVertexData; // triangle stripy private TriangleStripArray _cubeTriangles; // transformační matice kostky private Transform _cubeTransform; // "appaerance" kostky private Appearance _cubeAppearance; // vlastnosti polygonu private PolygonMode _polygonMode; // proměnná textury private Texture2D _cubeTexture; // grafický kontext private Graphics3D _graphics3d; // barva na pozadí private Background _background; //pokyn pro spuštění public void showNotify() { init(); } //pokyn pro zastavení public void stop(){ _thread=null; // ukončení vlákna } // inicializace protected void init() { // získání instance _graphics3d = Graphics3D.getInstance(); // vytvoření pozadí s barvou _background = new Background();
_background.setColor(0xFF113366); // vytvoření vertex bufferu _cubeVertexData = new VertexBuffer(); // vytvoření a nastavení pole vertexů VertexArray vertexPositions = new VertexArray(VERTEX_POSITIONS.length/3, 3, 1); vertexPositions.set(0, VERTEX_POSITIONS.length/3, VERTEX_POSITIONS); _cubeVertexData.setPositions(vertexPositions, 1.0f, null); // to samé pro textury VertexArray vertexTextureCoordinates = new VertexArray(VERTEX_TEXTURE_COORDINATES.length/2, 2, 1); vertexTextureCoordinates.set(0, VERTEX_TEXTURE_COORDINATES.length/2, VERTEX_TEXTURE_COORDINATES); _cubeVertexData.setTexCoords(0, vertexTextureCoordinates, 1.0f, null); // vytvoření polygonů (trojúhelníků) tvořících kostku _cubeTriangles = new TriangleStripArray(TRIANGLE_INDICES, TRIANGLE_LENGTHS); // vytvoření a nastavení kamery Camera camera = new Camera(); float aspect = (float) getWidth() / (float) getHeight(); camera.setPerspective(30.0f, aspect, 1.0f, 1000.0f); Transform cameraTransform = new Transform(); cameraTransform.postTranslate(0.0f, 0.0f, 10.0f); _graphics3d.setCamera(camera, cameraTransform); // počáteční natočení kostky _cubeTransform = new Transform(); _cubeTransform.postRotate(20.0f, 1.0f, 0.0f, 0.0f); _cubeTransform.postRotate(45.0f, 0.0f, 1.0f, 0.0f); // nastavení vlastností (Appearance) _cubeAppearance = new Appearance(); _polygonMode = new PolygonMode(); _polygonMode.setPerspectiveCorrectionEnable(true); _cubeAppearance.setPolygonMode(_polygonMode);
try { // vytvoření a nastavení textury Image2D image2D = (Image2D) Loader.load("/fel.png")[0]; _cubeTexture = new Texture2D(image2D); _cubeTexture.setBlending(Texture2D.FUNC_DECAL); _cubeAppearance.setTexture(0, _cubeTexture); } catch (Exception e) { e.printStackTrace();
} // spuštění vlákna _thread = new Thread(this); _thread.start(); } // překreslení obrazovky protected void paint(Graphics g) { if(_thread==null) return; // pojistka try { _graphics3d.bindTarget(g); // nastavení cíle _graphics3d.clear(_background); // překreslení cíle pozadím _graphics3d.render(_cubeVertexData, _cubeTriangles, _cubeAppearance, _cubeTransform); // render } finally { _graphics3d.releaseTarget(); // uvolnění cíle } } // metoda vlákna public void run() { while(_thread!=null) { // dokud vlákno nezrušíme _cubeTransform.postRotate(2.0f, 0.0f, 1.0f, 0.0f); // rotace kostky repaint(); // překreslení try { Thread.sleep(20); } catch (InterruptedException ie) { ie.printStackTrace(); } } } }
Třída RetainedMode.java class RetainedMode extends Canvas implements Sample, Runnable { private Thread _thread = null; private private private private
Graphics3D g3d; World _world; Camera _camera; Mesh _cube;
// konstruktor RetainedMode() { super(); } // pokyn pro spuštění public void showNotify() { init(); } // pokyn pro zastavení public void stop() { _thread=null; } // metoda vlákna public void run() { while(_thread!=null) { // dokud vlákno nezrušíme _cube.postRotate(2.0f,0.0f,0.0f,1.0f); // rotace kostky repaint(); // překreslení try { Thread.sleep(20); } catch (InterruptedException ie) { ie.printStackTrace(); } } } // překreslení obrazovky protected void paint(Graphics g) { if(_thread==null) return; // pojistka try { g3d.bindTarget(g); // nastavení cíle g3d.render(_world); // render } finally { g3d.releaseTarget(); // uvolnění cíle } } // inicializace void init(){
// získání instance g3d = Graphics3D.getInstance(); // nahrání scény ze souboru Object3D[] parts=null; try { parts = Loader.load("/cube.m3g"); } catch(Exception e) { e.printStackTrace(); } _world = (World) parts[0]; // prvek na nultém indexu je vždy hlavní objekt // získání odkazu na kostku (má definováno userID=12) // pro pozdější snazší manipulaci _cube=(Mesh)_world.find(12); // získání a nastavení kamery _camera = _world.getActiveCamera(); // nastavení projekce float[] lProjection = {0.0f, 0.0f, 0.0f, 0.0f}; _camera.getProjection(lProjection); float aspect = (float)getWidth()/(float)getHeight(); // nastavení prespektivy _camera.setPerspective(lProjection[0], // úhel pohledu aspect, // poměr stran lProjection[2], // první ořezávací rovina lProjection[3]); // druhá ořezávací rovina _world.setActiveCamera(_camera); // spuštění vlákna _thread = new Thread(this); _thread.start(); } }
RMS –Record Management Systém
Record Management System je určen pro ukládání dat pro dlouhodobé použití, tj. požadujeme-li, aby si naše aplikace pamatovala po vypnutí a znovuspuštění informace. Pro ukládání dat, tak aby byla přístupná i po vypnutí a znovu spuštění aplikace, slouží tzv. RMS (Record Management System) – systém pro správu záznamů. O uchování dat by se měl postarat mobilní telefon. Je možné, že u některých typů MT nemusí data "přežít" vypnutí mobilního telefonu nebo vyjmutí baterie na delší dobu.