IMPORTANT: Per accedir als fitxer de subversion: http://acacha.org/svn (sense password). Poc a poc s'aniran migrant els enllaços. Encara però funciona el subversion de la farga però no se sap fins quan... (usuari: prova i la paraula de pas 123456)

Java ME

De SergiTurWiki
Share/Save/Bookmark
Dreceres ràpides: navegació, cerca

Contingut

Arquitectura Java ME

Components

L'arquitectura Java ME té els següents components:

  • Configuració: Una configuració té dos parts:
    • Màquina virtual (en vermell al gràfic): La màquina virtual Java adaptada a les característiques dels dispositius mòbils
    • La configuració en sí (blau al gràfic) que no és res més que un conjunt d'API per al suport d'aplicacions que són compartides pel conjunt de dispositius que suporten la configuració
  • Perfils (en verd fort al gràfic ): és un conjunt d'API de més alt nivell que les proporcionades per les configuracions, que proporcionen suport per al desenvolupament d'aplicacions (cicles de vida, interfícies d'usuari, capacitats de connexió, etc) en dispositius mòbils. Cada perfil està dissenyat per a una configuració concreta.
  • Paquets opcionals (en verd fluix al gràfic): Són paquets opcionals de programari que permeten estendre les capacitats de les aplicacions Java ME.
ArquitecturaJavaME.png ArquitecturaJavaME1.png

Màquina virtual

La màquina virtual és la capa més baixa de l'arquitectura Java ME i per tant és la més propera al maquinari. A diferència del que succeeix en ordinadors personals, les màquines virtuals en dispositius mòbils venen proporcionades pel fabricant del dispositiu, és a dir, estan incorporades en el dispositiu.

En Java ME, ens trobem amb 2 màquines virtuals principals, segons el tipus de configuració:

Configuració

Connected Limited Device Configuration (CLDC)

Consulteu:

http://ca.wikipedia.org/wiki/CLDC

Connected Device Configuration (CDC)

Consulteu:

http://ca.wikipedia.org/wiki/CDC_(informàtica)

Perfils

Perfils CLDC

Mobile Information Device Profile (MIDP)

Consulteu:

http://ca.wikipedia.org/wiki/MIDP

Information Module Profile

El perfil Information Module Profile (definit al JSR 195) està dissenyat per a màquines d'auto-vending, targetes de xarxa, encaminadors o altres dispositius amb una pantalla molt simple o sense pantalla i que tenen connexió limitada a xarxa.

Els creadors d'aquesta especificació van ser Siemens Mobile i Nokia.

DoJa Profile

Aquest perfil va ser dissenyat per als mòbils i-mode de DoCoMo

Perfils CDC

  • Foundation Profile
    • API similar a la de Plataforma Java SE.
    • Sense interfície gràfica d'usuari
  • Personal Basis Profile
    • És una extensió dels perfil Foundation Profile
    • Suport per a interfícies gràfiques d'usuari lleugeres. Suporta algunes classes de AWT, però no tots els Widgets. Per exemple no suporta java.awt.Button perquè aquest tipus de widget pressuposa que es disposa d'un dispositiu punter com un ratolí.
    • Basada en BD-J
  • Personal Profile
    • Extensió del perfil Personal Basis Profile
    • Suport complet AWT
    • Suport complet d'Applets
    • Les aplicacions Java són fàcils de passar a aquest perfil

Recursos Limitats

  • Memòria
  • Processador
  • Dispositius entrada sortida de tamany limitat (teclats, pantalles)
  • Bateria
  • Connexió intermitent (wireless, etc.)
  • Màquina Virtual Java especial: Java ME runtime environment

JAD. Java Application Descriptors

Entorns de desenvolupament Integral (IDE)

Netbeans

Consulteu l'article Netbeans i Java ME

Eclipse

Plataformes d'emulació (Emulator Platforms)

Sun Java Wireless Toolkit

Consulteu Sun_Java_Wireless_Toolkit

Altres plataformes d'emulació

Supported Toolkit List

  • Sun Wireless Toolkits
  • Motorola SDK for J2ME™
  • Nokia Developer Suite 2.2 for J2ME
  • Sony Ericsson J2ME SDK
  • Sprint PCS Wireless Toolkit for Java
  • Siemens SMTK for Series 60

http://eclipseme.org/docs/support_wtk.html

MIDlets

Per a una definició de MIDlet, consulteu l'article de la viquipèdia [Connected Limited Device Configuration MIDlet].

Cicle de vida i esquelet d'un MIDlet

Diagrama d'estats d'un MIDlet

Un MIDlet té el diagrama d'estats que podeu veure a la dreta, amb 3 estats:

  • Pausa
  • Actiu
  • Destruit

Quan volem crear un MIDlet hem de crear una nova classe Java que hereti de la classe abstracta:

javax.microedition.midlet.MIDlet

L'esquelet d'un MIDlet és el següent:

import javax.microedition.midlet.*;

public class MiMIDlet extends MIDlet {
 protected void startApp()
              throws MIDletStateChangeException {
      // Mou el MIDlet a l'estat actiu -> iniciar
 }
 protected void pauseApp() {
      // Mou el MIDlet a l'estat pausa -> pausar
 }
 protected void destroyApp(boolean incondicional)
              throws MIDletStateChangeException {
      // Mou el MIDlet a l'estat destruït -> allibera recursos
 }
}

API Limited Connected Devices User Interface (LCDUI)

La interfície gràfica amb CLDC es realitza amb l'API LCDUI que forma part del perfil MIDP. Cal tenir en compte que hi ha 2 versions de MIDP:

  • MIDP 1.0 (JSR 37)
  • MIDP 2.0 (JSR 118)


LCDUI vol dir Limited Connected Devices User Interface, i representa el mateix per a Java ME que les interfícies Swing i AWT per a Java SE.

El paquet que conté aquesta API és javax.microedition.lcdui i podeu consultar els javadocs online a:

http://java.sun.com/javame/reference/apis.jsp#api

O amb Netbeans els trobareu a:

~/netbeans-6.5/mobility8/WTK2.5.2/docs/api

La classe Display representa una pantalla de mòbil. Aquest objecte permet accedir a la pantalla i al teclat. Cal tenir en compte que cada MIDlet només té associat un sol Display. Es pot obtenir el Display amb:

Display display = Display.getDisplay(midlet);

Es pot emmagatzemar el Display en un Bean Pattern. Per exemple el codi Hola Mon! de Netbeans utilitza el següent codi:

public Display getDisplay () {
       return Display.getDisplay(this);
}
JerarquiaComponentsGraficsJavaMe.png

En una pantalla (Display) es poden mostrar components gràfics també anomenats Displayables per què tots són extensió (hereten) de la classe abstracta Displayable.

Només es pot mostrar un component gràfic a l'hora i els components gràfics estan dividits en 2 nivells lògics:

  • Alt nivell (Screen): són components predefinits que hereten de la classe Screen. Es tracta de components com llistes, formularis, alertes, camps de text que són proporcionats de forma nativa per Java ME. Aquest components són adequats per a la creació d'aplicacions "corporatives". Les aplicacions d'alt nivell són més portables.
  • Baix nivell: són components a mida que hereten de la interfície Canvas. En aquests components cal especificar per codi com mostrar el contingut de la pantalla. i tenim control dels esdeveniments de teclat. Són més adequats per a la creació de jocs però la portabilitat és més reduïda.

Es determina quin és el component gràfic a mostrar per pantalla en cada moment amb:

Display.setCurrent ( displayable);

Els components d'alt nivell hereten de la classe abstracta Screen (buida) que a l'hora hereta de Displayable. Cada component pot tenir:

  • 0 o 1 títol
  • 0 o 1 ticker
  • O o més comandes i els corresponents Listeners associats a aquestes comandes.

La interfície gràfica sovint es pot desenvolupar mitjançant eines RAD. Amb Netbeans, les interfícies gràfiques, es poden implementar utilitzant la pestanya Flow:

NetbeansLCDUI.png

Consulteu l'exemple: [Projecte_Java_ME_GestioAules#1r_Exercici._Creaci.C3.B3_de_la_interf.C3.ADcie_gr.C3.A0fica_d.27usuari_d.27alt_nivell.LCDUI._Versi.C3.B3_0.1 Projecte Gestió Aules], concretament l'exercici 1, per tal de veure un exemple d'aplicació implementada amb Netbeans i LCDUI.

Imatges

Es poden incloure imatges en molt components gràfics. Les imatges s'encapsulen a la classe Image i l'únic format reconegut per l'estàndard és PNG. Es poden obtenir a partir de fitxer continguts dins del fitxer JAR:

Image img = Image.createImage(“/logo.png”);

Control d'esdeveniments i comandes

L'entrada dels usuaris es realitza mitjançant comandes (classe Command)

Per afegir una comanda a un component gràfic:

TextBox tb = new TextBox("Login", "", 8, TextField.ANY);
Command cmdOK = new Command("OK", Command.OK, 1);
Command cmdAyuda = new Command("Ajuda", Command.HELP, 1);
Command cmdSalir = new Command("Sortir", Command.EXIT, 1);
Command cmdBorrar = new Command("Esborrar", Command.SCREEN, 1);
tb.addCommand(cmdOK);
tb.addCommand(cmdAyuda);
tb.addCommand(cmdSalir);
tb.addCommand(cmdBorrar);
Display d = Display.getDisplay(this);
d.setCurrent(tb);

En Java per tractar els esdeveniments s'utilitzen Listeners que és un component que escolta un determinat esdeveniment. Quan succeïx l'esdeveniment s'executa el codi del Listener. Hi ha 3 tipus de Listeners a Java ME:

El MIDlet ha d'implementar la classe Listener desitjada. Cal implementar els mètodes de la interfície i Registrar el Listener al component gràfic el qual ha de rebre els esdeveniments

public class HelloMIDlet extends MIDlet implements CommandListener {

L'exemple Hola Mon! de Netbeans:

public class HelloMIDlet extends MIDlet implements CommandListener {
...
public void commandAction(Command command, Displayable displayable) {     
        // write pre-action user code here
        if (displayable == form) {                                           
            if (command == exitCommand) {                                         
                  // write pre-action user code here
                exitMIDlet();                                           
                 // write post-action user code here
            }     }                                                
        // write post-action user code here
    }       

public Form getForm() {
        if (form == null) {                                 
            // write pre-init user code here
            form = new Form("Welcome", new Item[] { getStringItem() });                                    
            form.addCommand(getExitCommand());
            form.setCommandListener(this);                                  
            // write post-init user code here
        }                         
        return form;     }

API de baix nivell

MIDP 1.0

  • Falta de suport per a aritmètica de punt flotant fa molt complicat implementar jocs en 3 dimensions.
  • No hi ha suport per a àudio. Només suporta fer "bips"
  • No és possible llegir o escriure en un píxel concret fet que no permet fer manipulacions bàsiques de imatges.
  • La classe Graphics no suporta cap tipus de transparència
  • L'únic protocol suportat és HTTP.

MIDP 2.0:

Dibuixant a la pantalla

A Java ME, el sistema de dibuix es basa en un objecte anomenat Canvas. Canvas vol dir llenç (la superfície sobre la qual un pintor pinta). El procés de pintat sobre un llenç és molt similar al procés de pintat amb AWT o Swing. La idea bàsica és que l'objecte Canvas proporciona una àrea de dibuix que és accedida cada cop que el mètode paint() és invocat. Les tasques de dibuix són realitzades per una classe anomenada Graphics. Aquesta classe proporciona mètodes primitius per a dibuixar sobre el llenç.

Cal tenir en compte que la Classe llenç és abstracta i, per tant, cal implementar una classe filla de canvas que implementi el mètode paint():

protected abstract void paint(Graphics g)

Per exemple:

public class MyCanvas
extends Canvas {
...
 public void paint(Graphics g) {
 //Aquí posem el codi per pintar al Canvas
 }
...
}

Recursos:

L'objecte Graphics

L'objecte Graphics proporciona primitives de dibuix. Es poden pintar:

  • Línies: drawLine()
  • Arcs: drawArc(). Pot ser omplert per colors sòlids.
  • Rectangles: drawRect() i drawRoundRect(). Pot ser omplert per colors sòlids. També poden tenir els cantons arrodonits.

Totes les primitives poden ser pintades amb línia sòlides (SOLID) o puntejades (DOTTED). El tipus de línia a utilitzar es modifica amb el mètode setStrokeStyle().

Totes les operacions de dibuix es duen a terme en un espai de 2 dimensions amb coordenades (X,Y) on la coordenada (0,0) és el cantó superior-esquerre de la pantalla.

public void translate(int x, int y) 

També es pot treballar amb Imatges i text:

Per pintar imatges i text s'utilitzen anchor points que permeten col·locar les imatges i el text en posicions molt concretes de la pantalla. Per exemple:

g.drawString(mMessage, mWidth / 2, mHeight, Graphics.BOTTOM | Graphics.HCENTER);

Es poden combinar és valors (amb |):

  • Posició horitzontal: (LEFT, HCENTER, RIGHT)
  • Posició vertical: (TOP, BASELINE, BOTTOM)

Mostra un missatge a la part inferior centrada de la pantalla.

Colors:

El color s'estableix amb un Model_de_color_RGB (red,green,blue). Disposem dels mètodes:

public void setColor(int red, int green, int blue);
public void setColor(int RGB);
public void setGrayScale(int value);

per tal d'establir el color que s'està utilitzant per pintar.

Els colors es codifiquen en 24 bits. Cal tenir en compte però que ens podem trobar amb dispositius amb limitacions:

  • El dispositiu no suporta colors. Pot ser Blanc o Negre o d'escala de grisos.
  • El número màxim de bit per color és menor de 8
  • El número màxim de colors diferents és limitat

Es recomana com a bona pràctica, comprovar el tipus de dispositiu abans de començar a utilitzar colors. Hi ha una sèrie de mètodes de la classe Display que ens permeten comprovar les limitacions del dispositiu:

  • isColor(): torna true si el dispositiu suporta colors
  • numColors(): retorna el número de colors que són suportats pel dispositiu. Si el dispositiu no suporta colors, aleshores retorna el nombre de nivells de gris que suporta (en pantalles blanc i negre aquest valor és 2).

En qualsevol moment podeu consultar el color actual amb:

public int getColor()

O el valor de gris amb:

public int getGrayScale()

També es pot accedir directament als component del color RGB (red,green,blue) amb els mètodes:

public int getBlueComponent();
public int getGreenComponent();
public int getRedComponent();

Fonts

L'objecte Font controla el tipus de fonts que es poden utilitzar. Hi han diversos mètodes per dibuixar text:

public void drawString(String str, int x, int y, int anchor)
public void drawSubstring(String str, int offset, int len, int x, int y, int anchor)
public void drawChar(char character, int x, int y, int anchor)

La font actual és consulta amb:

public Font getFont();

I s'estableix amb:

public void setFont(Font font);

Veiem un exemple:

font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_PLAIN, Font.SIZE_LARGE);

Fixeu-vos que no s'indica un tipus de font concret sinó unes característiques. El mòbil retornarà la font més similar de la que disposi.

Tenim mètodes que ens permeten consultar l'alçada de la font i així pode escriure diverses línies sense xocar. Un exemple:

height = f.getHeight();
for (i = 0; i < txt.length; i++) {
  g.drawString(txt[i], x, y, TOP|LEFT);
  y += height;
}

Com forçar la crida del mètode paint

El mètode paint és cridat pel propi mòbil cada cop que necessita tornar a mostrar la pantalla. Per tant, és el MIDlet qui controla quan s'ha de tornar a dibuixar la pantalla.

Però en alguns cassos ens interessarà tornar a dibuixar la pantalla en moment concrets de temps. Alguns exemples:

  • Per a realitza animacions per pantalla
  • Un joc que necessita fer aparèixer un nou element gràfic per pantalla (per exemple un enemic).
  • etc...

Cal tenir en compte que la instància de l'objecte Graphics que és utilitzat pel mètode paint, només és vàlida durant la execució del mètode paint. Per tant, no podem accedir a l'objecte Graphics, modificar-lo i cridar aleshores el mètode paint, per que les modificacions que hem fet a l'objecte Graphics no s'aplicaran. De fet el mètode paint no és mai cridat directament pel MIDlet.

Per tornar a pintar la pantalla cal utilitzar el mètode repaint(). Aquest mètode no crida el mètode paint() directament sinó que demana a l'entorn d'execució que cridi al mètode paint.

Veiem un exemple de com podem mostrar un missatge per pantalla amb repaint:

 public class MyCanvas
 extends Canvas {
  ...
  private String mMessage=null;
  ...

  public void setMessage(String s) {
    mMessage = s;
    repaint();
  }
 
  public void paint(Graphics g) {
    // Mostrar un missatge si és que hi ha un missatge
    if (mMessage != null)
      g.drawString(mMessage, mWidth / 2, mHeight, Graphics.BOTTOM | Graphics.HCENTER);
  ...
  }
...
}

Off-screen buffers (double-buffering)

Com ja hem comentat, podem utilitzar el mètode repaint per tal de crear una animació. Cal destacar però, que si l'animació és complexa és possible que per problemes de rendiment noteu un efecte indesitjable de parpelleig. Per evitar aquest efecte indesitjable t és comú utilitzar la tècnica anomenada double-buffering.

NOTA: Cal tenir en compte que hi ha dispositius que ja suporten per defecte double-buffering

Amb MIDP és fàcil implementar el double-buffering. La classe Image pot ser utilitzada per a crear una imatge off-screen amb el mètode createImage(width, height). Un cop has obtingut la imatge, es pot cridar el mètode getGraphics(), per tal d'obtenir una instància de l'objecte Graphics, que pot ser utilitzar per a pintar en la imatge de la mateixa forma que ho faríem al mètode paint per tal de pintar directament a la pantalla. Un cop teniu la imatge preparada, aquesta es pot pintar per pantalla utilitzant el mètode Graphics.drawImage().

Exemple:

private Image offscreen; 
...
offscreen = Image.createImage(getWidth(), getHeight());
g = offscreen.getGraphics(); 
paint(g);
...
public void paint(Graphics g) {
...
originalG = g;
g = offscreen.getGraphics();
...
}

Control d'esdeveniments

Control d'esdeveniments de teclat (Keystrokes)

L'objecte Canvas pot controlar esdeveniments de teclat amb els mètodes

protected void keyPressed(int keyCode)
protected void keyReleased(int keyCode)
protected void keyRepeated(int keyCode)

Podeu sobrescriure aquests mètodes a les classes filles de Canvas que utilitzeu. Els control d'esdeveniments per defecte d'aquests mètodes és no fer res.

Disposem d'una sèrie de constants predefinides (variables static i final de la classe Canvas):

  • Tecles numèriques: KEY_NUM0, KEY_NUM1, KEY_NUM2, KEY_NUM3, KEY_NUM4, KEY_NUM5, KEY_NUM6, KEY_NUM7, KEY_NUM8, KEY_NUM9
  • Asterisc i coixinet: KEY_STAR,KEY_POUND

Aquests codis són codis estàndards de la ITU-T. Poden existir altres tecles i altres codis en certs dispositius però si es vol garantir la portabilitat de l'aplicació només podem utilitzar les tecles estàndard.

Accions de joc (game actions)

En comptes d'utilitzar els codis específics de tecles com les fletxes de direcció, les aplicacions Java portables han d'utilitzar accions de joc (game actions).

A MIDP es defineixen les següents accions de joc:

  • Fletxes de direcció: UP, DOWN, LEFT, RIGHT
  • Altres accions: FIRE, GAME_A, GAME_B, GAME_C i GAME_D.

Cada codi de tecla està associat com a màxim a una acció de joc però una acció de jo pot estar associada a més d'un codi de joc.

Disposem dels mètodes:

getGameAction(int keyCode)
getKeyCode(int gameAction)

Per relacionar codis de tecla amb accions de joc.

Per tant tingueu em compte que:

   g == getGameAction(getKeyCode(g))     // (1)
   k == getKeyCode(getGameAction(k))     // (2)    

La expressió 1 sempre serà vàlida. No podem dir el mateix de la expressió (2).

Cal tenir en compte que per exemple les tecles de fletxa poden ser especifiques de cada dispositiu, i que alguns dispositius sense tecles de fletxa potser retornen els codis de les tecles 2,4,6 i 8 com a tecles de fletxa.

Idea!: Un menú inicial amb Canvas que permeti accedir a les opcions de menú directament pressionant un número.

Esdeveniments de punter

Cal que tenir en compte que no tots els mòbils suporten esdeveniments de punter

Els mètodes que controlen els esdeveniments de punter són:

protected void pointerDragged(int x, int y)

protected void pointerPressed(int x, int y)

protected void pointerReleased(int x, int y)

Podeu comprovar si el mòbil suporta esdeveniments de punter amb els mètodes:

public boolean hasPointerEvents()
public boolean hasPointerMotionEvents()

El segon mètode comprova si el mòbil permet controlar drags (arrossegament) del punter.

Desenvolupament de jocs

L'API de jocs (paquet javax.microedition.lcdui.game) proveeix de 5 classes:

  • GameCanvas: és una subclasse de javax.microedition.lcdui.Canvas que proveeix de la funcionalitat bàsica per a un joc. . Inclou mètodes que permeten consultar l'estat de les tecles del joc, i el control del flux de les imatges.
  • Layer: és una classe abstracta, pare de les classes Sprite i TiledLayer. Proporciona el suport per a desenvolupar gràfics per capes. Representa un element visual d'un joc, com un Sprite o un TiledLayer i té atributs com la posició, la mida, la visibilitat...
    • Sprite: és una capa animada bàsica que es capaç de mostrar un conjunts de marcs (frames) de forma animada. Els marcs són tos de la mateixa mida i s'emmagatzemen en una mateixa imatge (objecte Image). Suporta transformacions bàsiques com voltejar o rotar el marc, detecció de col·lisions, etc.
    • TiledLayer: representa un element visual format per una graella de cel·les que pot ser omplida per amb un conjunt d'imatges tile (rajola). Permet crear imatges grans sense la necessitat d'un objecte de tipus Imatge.
  • LayerManager: simplifica el desenvolupament de jocs automatitzant el procés de representació (rendering). Permet gestionar les capes d'un joc i entre d'altres permet definir quina és la vista (la zona visible) d'una imatge en cada moment.

Esquelet d'un Joc Java ME:

public class MyCanvas extends Canvas implements Runnable {

   public void run() {
      while(true) {
         repaint(); // update the game state
   }
   
   public void paint(Graphics g) {
      // code for painting
   } 
   
   protected void keyPressed(int keyCode) {
      // respond to key events
   } 

}

Recursos:

Game Canvas

L'objecte GameCanvas és una subclasse de l'objecte javax.microedition.lcdui.Canvas. També és una classe abstracta.

GameCanvas proporciona les següents ajudes per al desenvolupament de jocs:

  • Adquirir l'estat de les tecles del teclat: Hi ha un control millorat dels esdeveniments de teclat. Per controlar els esdeveniments de teclat s'utilitza el mètode getKeyStates() en comptes del mètode keyPressed(). Aquest mètode facilita el control de les tecles, ja que permet accedir a l'estat de les mateixes en qualsevol moment evitant problemes amb múltiples fils d'execució. A més, permet detectar esdeveniments com múltiples tecles premudes simultàniament.
  • Buffering de gràfics: Hi ha un control millorat del doble-buffering. Es poden utilitzar els mètodes getGraphics() i flushGraphics() en comptes de repaint() per tal de millorar el rendiment del joc.

GameCanvas simplifica la programació de jocs ja que tota la funcionalitat del joc es pot controlar en un sol bucle, sota el control d'un únic fil d'execució. L'esquelet d'un joc fet amb Game Canvas és:

public class MyCanvas extends GameCanvas implements Runnable {

   public void run() {
      Graphics g = getGraphics();
      while(true) {
         // update the game state
         // ...
         int k = getKeyStates();
         // respond to key events
         flushGraphics();
      }
   }
}

Layers. Sprite i TiledLayer

La classe abstracta Layer representa un element visual d'un joc, com un Sprite o un TiledLayer.

Els atributs d'una capa són:

  • Posició: Coordenades x-y de la cantonada superior-esquerre de la capa
  • Mida: amplitud i alçada de la capa. Les capes són rectangulars
  • Visibilitat: indicar si la capa és visible o no en un moment donat

Les capes es poden superposar entre si. Les capes poder ser visibles o invisibles. En el cas de superposició la capa superior és la que es mostrarà (si és visible).

Totes les capes han d'implementar el mètode paint(Graphics g), que és l'encarregat de mostrar el contingut gràfic de la capa.

Sprite

Els jocs 2D típics tenen un personatge que es mou al voltant d'un fons gràfic i que interactua amb l'entorn. Un exemple clar és el típic joc dels "comecocos" on una boca es va moment per un laberint, menjant cocos mentres evita els fantasmes.

En programació de jocs, el conjunt de diferents imatges d'aquests personatges són anomenades sprites (en català follets). La classe Sprite representa una imatge gràfica a on s'emmagatzemen totes les possibles imatges d'un sprite. Un sprite es divideix en diverses imatges que anomenen marcs (frames)

Les propietats d'un sprite són:

  • Posició: Coordenades cartesianes (x,y) relatives al cantó superior-esquerre de la pantalla.
  • Seqüencia d'animació: Es pot definir una seqüència d'imatges o conjunt de marcs de l'sprite amb el mètode setFrameSequence(int sequence[]). Es pot avançar d'un frame cap al següent amb nextFrame().
  • Transformacions simples: Els sprites es poden rotar, o girar.

Tots els frames d'un sprite es proporcionen en una sola imatge (objecte Image). L'ordre dels frames en la imatge pot ser de diferents formes. Us mostrem uns quants exemples d'sprites:

Sprites.png

Recursos:

TiledLayer

Les capes de rajola (la paraula Tile vol dir rajola en català) són similars als Sprites però tenen un objectiu diferent. S'utilitzen per a crear fons de jocs.

A diferència d'un sprite, un TiledLayer no té transformacions, ni seqüències de marcs ni pixels de referència.Una capa de rajoles és una graella formada per un conjunt de marcs. És similar a un terra de ceràmica, format per un conjunt de rajoles.

El conjunt de rajoles es guarda en una sola imatge, on tots els Tiles (rajoles) tenen la mateixa mida:

TiledLayer.gif

Igual que passa amb els sprites es poden guardar el conjunt de rajoles en diferents formes dins d'una imatge.

A cada rajola se li assigna un identificador únic. Per construir un fons de pantalla, s'utilitza una matriu on a cada posició s'indica l'identificador de la rajola a mostrar en aquella posició:

TiledLayer2.gif

Es poden incloure objectes animats, fent que algunes rajoles tinguin diferents imatges possibles.

Gestor de capes. LayerManager

Permet gestionar les capes d'un joc. L'ordre en que es pinten les capes és l'invers de l'ordre en que s'afegeixen les capes al gestor de capes.

Organització típica

  • Fons (background): TiledLayers
  • Objectes sobre el fons: Sprites

També permet definir quina és la vista (la zona visible) d'una imatge en cada moment.A aquesta zona visible també se l'anomena view port. Aquesta funcionalitat ens permet treballar amb imatges grans i anar “navegant” per la imatge

LayerManager.setViewWindow(int x, int y, int width, int height)

Jocs Open Source

Bases de dades

Els dispositius amb suport per a CDC disposen d'accés a gairebé tota la API de Java SE. Això inclou suport per a JDBC, i per tant es pot utilitzar accés a base de dades.

En canvi, els dispositius CLDC, no disposen de JDBC. Només s'ofereix un sistema primitiu de base de dades anomenat Record Managment System (RMS).

Alguns dispositius, poden implementar la API opcional JSR 75 (File Connection) que permet accedir a fitxers. En aquest cas però, cal provar com funciona el programa en diferents dispositius, ja que aquesta API és opcional (alguns dispositius no la proporcionaran...) i pot no funcionar igual en tots els dispositius.

Record Managment System (RMS)

Record Management System (RMS) és el sistema que permet emmagatzemar dades de forma persistent amb Java ME i el perfil MIDP. El paquet és javax.microedition.rms.

Cal destacar les següents característiques:

  • No hi ha tipus de dades. Totes les dades guardades són vectors de bytes
  • No és un sistema de base de dades relacional, ni jeràrquic...
  • És un sistema força rudimentari consistent en magatzems de registres
  • Les dades es guarden a la memòria no volàtil del mòbil. Cada mòbil implementa els magatzems com desitgi.
  • Les dades es tracten com a Streams de bytes
  • Un MIDlet pot crear i accedir a diversos magatzems anomenats RecordStore
  • Els magatzems només es poden compartir entre MIDlet de la mateixa Suite
  • Només apte per petites bases de dades com preferències, jocs guardats, etc.
Un altre cop, els que sapigueu desenvolupar aplicacions Java, oblideu-vos del suport de base de dades de Java SE. No podem 
utilitzar JDBC amb mòbils.

Un magatzem inclou diversos registres. Cada registre conté:

  • Un identificador (nombre enter)
  • Dades (un array de bytes)

Operacions amb els magatzems i els registres

Obrir/crear un magatzem

RecordStore rs = RecordStore.open(nomMagatzem, true);

Tancar un magatzem

rs.close();

Llistar els magatzems disponibles

String [] nomsMagatzems = RecordStore.listRecordStores(); 

Eliminar un magatzem

RecordStore.deleteRecordStore(nomMagatzem);

Enumerar i iterar els registres d'un magatzem

RecordEnumeration re = rs.enumerateRecords(null, null, false);
while(re.hasNextElement()) {
   int id = re.nextRecordId();
   byte [] datos = rs.getRecord(id);
   // Processar les dades 
   ...
}

Codificar les dades i escriure un registre

RecordStore rs;
   ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
try {
       dos.writeUTF(“http://www.upc.edu”);
       dos.writeInt(50)
} catch (IOException e) {
       e.printStackTrace();
}
byte [] barrURL = baos.toByteArray();

//Afegir les dades al registre
try {     rs = RecordStore.openRecordStore(this.PreferencesRecordStore, true);
           rs.addRecord(barrURL, 0, barrURL.length);
           rs.closeRecordStore();
           baos.close();
           dos.close();
   } catch (Exception e) {     
           e.printStackTrace();    
   }

Llegir un registre i descodificar les dades

 byte [] dades = rs.getRecord(id);
ByteArrayInputStream bais =
        new ByteArrayInputStream(dades);
DataInputStream dis = DataInputStream(bais);
String nom = dis.readUTF();
String edat = dis.readInt();

Eliminar un registre

rs.deleteRecord(id);

Input/Output (I/O)

Generic Connection Framework (GCF)

GCF col dir Generic Connection Framework i és una API que proporciona MIDP per a les connexions remotes de xarxa. El paquet que proporciona aquesta API és:

javax.microedition.io

És un sistema dissenyat per establir connexions amb independència del tipus de xarxa (commutació de circuits, commutació de paquets, sense fils, etc.)

Existeix un mètode (open) i una classe (Connector) genèrics de connexió:

Connection con = Connector.open(url);
IMPORTANT: Les URL no són només pàgines web!

Exemples de URL:

Les classes de l'API GCF tenen una estructura jeràrquica com la següent:

GFCApiArbre.png

Com podeu veure CLDC només implementa connexions genèriques i és MIDP qui implementa les connexions HTTP. HTTP és l'únic protocol que podem està segurs que tot mòbil que suporti Java ME suportarà. En API opcionals es poden implementar altres tipus de connexions (p. ex. Bluetooth).

Classe HTTPConnection:

L'únic protocol al qual s'assegura suport en tots els mòbils amb Java ME és HTTP i sempre funcionarà igual amb independència del tipus de xarxa subjacent (WAP, iMODE, 3G, Wifi TCP/IP).

Per obrir una connexió amb el protocol HTTP:

HttpConnection con = (HttpConnection) Connector.open(URL);

Un cop tenim una connexió, podem obrir un flux d'entrada amb les classes que implementen InputConnection:

InputStream is = con.openInputStream(); 

o

DataInputStream dis = con.openDataInputStream() 

Ara ja podem processar el flux de la connexió. El que fem en aquest punt depèn de cada aplicació però un cop hem acabat amb la connexió cal tancar-la:

in.close();
con.close();

La diferència entre DataInputStream i InputStream és que el primer té mètodes que ens permeten accedir a tipus de dades Java:

  • readBoolean()
  • readByte()
  • readUTF()
  • ...

Amb InputStream, en de treballar les dades en cru (tenim un stream de bytes).

La resta de mètodes de HTTPConnection permeten controlar el protocol HTTP. La connexió HTTP pot estar en un de 3 estats possibles:

  • Setup State: És l'estat en que esta la connexió un cop acabada de crear. En aquest estat és l'únic moment en que s poden invocar:
    • setRequestMethod()
    • setRequestProperty()
  • Connected State: Es passa a aquest estat a l'invocar qualsevol mètode que requereixi enviar o rebre informació (openInputStream, openDataInputStream, getLength, getType, getEncoding, getHeaderField, getResponseCode...
  • Closed: Generat pel mètode close().

Podem controlar tant les peticions com les respostes del protocol HTTP i també les capçaleres:

Requests: Request Methods suportats:

  • GET, POST i HEAD. El mètode per defecte és GET.

La resta de HTTP no estan suportats (PUT, DELETE...). Podem establir el mètode amb:

con.setRequestMethod(HttpConnection.GET);

Cal recordar que això cal executar-ho abans de començar a enviar o rebre dades de la connexió (estat setup de la connexió).

Podem afegir capçaleres a la petició HTTP amb:

con.setRequestProperty(nom, valor);

Per exemple, a vegades és interessant indicar al servidor web al que ens connectem, quin tipus de client (navegador) utilitzem. Per a aplicacions Java ME seria:

con.setRequestProperty("User-Agent","Profile/MIDP-1.0 Configuration/CLDC-1.0");

Response:

Podem obtenir el codi d'estat de la connexió HTTP (Status Codes) i el missatge corresponent amb:

int codi = con.getResponseCode();
String misatge = con.getResponseMessage();

I podem obtenir qualsevol capçalera amb:

String valor = con.getHeaderField(nomCapçalera);

Per a les capçaleres més importants, tenim mètodes específics:

  • getLength(): obté la longitud en bytes de la resposta
  • getType(): Indica el Mime Type de la resposta.
  • getLastModified(): obté la data de l'última modificació (important per a la implementació de memòries cau).

Recursos:

Threading

L'ús de d'execució fils d'execució en Java ME és una qüestió força habitual, per no dir necessària. Els fils s'utilitzen per deslligar l'execució del model de la nostra aplicació de l'execució de l'entorn gràfic. Qualsevol aplicació es pot classificar en 3 parts:

  • Model: El model, és aquella part de l'aplicació encarregada de l'execució dels algorismes encarregats de la lògica de processament de les dades amb les que treballa l'aplicació. El càlculs que es realitzen amb la informació així com el codi necessari per accedir a aquesta informació formen part del model. Un exemple clar és l'accés a una base de dades i el processament de la informació d'aquesta base de dades.
  • Vista: La vista és tot lo referent al que és interfície d'usuari. Les pantalles que es mostren als usuaris finals, els formularis, etc formen part de la vista de l'aplicació.
  • Controlador: El controlador s'encarrega de controlar el flux de l'aplicació. És la lògica encarregada d'indicar en quin ordre s'executaran les pantalles i com la interacció amb l'usuari afectarà aquest flux.

Per a més informació podeu consultar a la wikipedia l'article Model Vista Controlador.

Al programar amb entorns mòbils, una de les normes a seguir és que tot el codi que executem en els controladors  
d'esdeveniments, a de ser codi que s'executi de forma ràpida de manera que no bloquegi l'execució de la interfície 
gràfica.

La solució a aquest problema és utilitzar fils d'execució. Aquelles tasques que requereixin de cert temps per executar-se s'executaran de forma paral·lela a la lògica de la interfície gràfica, utilitzant fils d'execució.

IMPORTANT: Cal tenir en compte les limitacions de processament dels dispositius mòbils a l'hora de programar 
fils d'execució en Java ME. La creació de fils d'execució és una tasca costosa i per tant, hem d'intentar crear 
només els fils d'execució imprescindibles. 

El cas més habitual d'ús de fils d'execució en Java ME esdevé al utilitzar l'API GCF. Molts MIDlets tard o d'hora requereixen connectar-se a la xarxa. Com que actualment les connexions a la xarxa són lentes, és necessari utilitzar fils d'execució per tal de no bloquejar l'execució de la lògica de la interfície gràfica. A més, cal proporcionar una interfície gràfica adequada a l'usuari que li mostri que el mòbil està executant un procés.

L'apartat 4.2 del projecte Gestió Aules, mostra un exemple pas a pas de com utilitzar fils d'execució per establir una connexió HTTP.

Recursos:

Serialització i persistència

Recursos:

MMAPI. Mobile Media API

Llibre sobre MMAPI

MMAPI ha estat dissenyat per a suportar diversos continguts multimèdia i proporcionar mecanismes de captura per a àudio i vídeo. MMAPI és l'especificació de Java JSR 135. La implementació proporcionada per Sun proporciona suport bàsic per a la reproducció simple d'àudio.

En molts sentits MMAPI s'assembla a l'API Generic Connection Framework (GCF).

No tots els dispositius suporten MMAPI. Podeu consultar la llista a:

http://developers.sun.com/mobility/device

Característiques:

  • Suporta la generació de tons, reproducció i enregistrament de medis multimèdia (àudio i vídeo).
  • Bon rendiment (small footprint): funciona amb les estrictes restriccions de memòria dels dispositius CLDC.
  • Independent del protocol i del contingut (protocol and content agnòstic): La API no esta esbiaixada cap a cap protocol o tipus de mitjà especific.
  • Flexible: Els desenvolupadors poden limitar el suport a certs tipus de suport.
  • Extensible: Es poden afegir noves funcionalitats de forma fàcil sense trencar la compatibilitat cap endarrere. És fàcil afegir suport per a nous formats


Recursos:

Arquitectura i classes

Els paquets:

Són els que contenen MMAPI.

MMAPI està centrat en la classe Manager. Manager és una factoria que permet crear objectes Player. Per tal d'indicar l'origen d'un medi multimedia s'utilitzen URI o objectes DataSource. Un objecte Player especifica les seves característiques funcionals (i opcionals) com control de volum, capactitat de reproducció/grabació, etc. mitjançant les interfícies Control.

Per exemple, un reproductor de tons disposarà de la classe ToneControl, un capturador de vídeo de VideoControl i RecordControl.

La següent gràfica mostra l'arquitectura de l'API MMAPI:

MMAPIArquitectura.gif

Classe Manager

La classe Manager és un classe factoria estàtica encarregada de crear objectes MMAPI. Per exemple, el mètode createPlayer() s'encarrega de crear objectes Player.

static Player 	createPlayer(String locator) 

Hi ha 3 tipus de locator:

  • Reproducció (media playback): les reproduccions poden ser de àudio o vídeo.
  • Captura (media capture): Les captures poden ser de àudio o de vídeo.
  • Reproductor MIDI o de tons: Per executar MIDIs o tons.

Un locator és similar a una URI i permet executar recursos remots. Per exemple, es pot crear un player per a un wav remot amb:

createPlayer("http://www.yourwebsite.com/audio/song.wav")

Una sèrie d'exemples:

Tipus de medi Exemple
Captura d'àudio "capture://audio" captura àudio del dispositiu de captura d'àudio per defecte.
Captura d'audio "capture://devmic0?encoding=pcm" captura d'àudio del dispositiu devmic0 i codificació PCM
Captura de vídeo "capture://video" captura vídeo del dispositiu de captura de vídeo per defecte.
Captura de vídeo "capture://devcam0?encoding=rgb888&width=100&height=50" captura vídeo del dispositiu de captura de vídeo devcam0 amb unes mides específiques.
Escoltar la radio "capture://radio?f=105.1&st=stereo" en estèreo i al 105FM
rtp media "rtp://host:port/type"
Executar un to "device://tone" proporciona un reproductor per a executar tons
Executar un midi "device://midi" proporciona un reproductor per a executar un MIDI

No tots els protocols seran suportats per tots els dispositius. El mètode:

static String[] getSupportedProtocols(String contentType)

El tipus de dades de cada URI dependrà del MIME Type. Per exemple la següent URI:

http://host/sample.mid

Serà associada per un servidor web HTTP al MIME-Type audio/midi i per tant el mòbil crearà un reproductor de fitxers MIDI. Els MIME-type que suporta cada dispositiu varien segons el fabricant i el model del mòbil.

Per a Nokia podeu obtenir una llista a

http://sw.nokia.com/id/444aa596-38d4-473c-81ed-657da920d163/Browser_MIME_Types_in_Nokia_GSM_Devices_v1_8_en.pdf

En temps d'execució podeu obtenir una llista dels MIME-type suportats amb el mètode:

static String[] getSupportedContentTypes(String protocol)

Finalment també disposem del mètode:

static Player 	createPlayer(InputStream stream, String type) 

Que permet crear un player a partir d'un stream de dades qualsevol. La variable type serveix per especificar el MIME-type.

Per exemple podem llegir directament de la base de dades RMS:

...
RecordStore store;
int id;
// Reproduir des de un RecordStore
try {
   InputStream is = new ByteArrayInputStream(store.getRecord(id));
   Player player = Manager.createPlayer(is, "audio/X-wav");
   p.start();
} 
catch (IOException ioe) {
} 
catch (MediaException me) {
}
...

Classe Player

La interfície Player conté les funcionalitats comunes a tots els tipus de reproductor multimèdia. Un reproductor té un cicle de vida format per una sèrie d'estats:

PlayerStates.gif

L'estat d'un reproductor té un gran impacte sobre el consum de recursos (per exemple un capturador de vídeo que no s'hagi tancat no permetrà a altres aplicacions utilitzar la càmera).

Estats

  • UNREALIZED: Estat del reproductor al ser creat
  • REALIZED: generat pel mètode realize(). Inicialitza la informació necessària per adquirir els recursos multimèdia
  • PREFETCHED: generat pel mètode prefetch(). Estableix les connexions de xarxa i realitza altres tasques d'inicialització.
  • STARTED: generat pel mètode start(). Es reprodueixen les dades multimèdia. Al finalitzar torna a l'estat PREFETCHED.
  • CLOSED: generat pel mètode close()

Altres mètodes de la interfície Player:

Els següents mètodes estan disponibles a tots els Players:

String getContentType ()
long getDuration ()
long getMediaTime ()
long setMediaTime (long now)
int getState ()
TimeBase getTimeBase ()
void setTimeBase (TimeBase master)
void setLoopCount (int count)
NOTA: Tots els temps estan en microsegons.

Interfície Control

La interfície Control defineix que un objecte és de tipus Control. Un objecte de Control és utilitzat per a realitzar operacions específiques amb un mitja multimèdia.

La interfície Player implementa la interfície Controllable i per aquesta rao disposa dels mètodes per obtenir els controls d'un reproductor concret:

 Control getControl(java.lang.String controlType)
 Control[] getControls() 

Múltiples controls poden ser implementats en un mateix objecte.

L'API base de Java ME només ofereix 2 controls:

  • ToneControl: Permet controlar el to
  • VolumeControl: Permet controlar el volum

Amb MMAPI 1.2 disposem de més controls:

FramePositioningControl | GUIControl
MetaDataControl | MIDIControl
PitchControl | RateControl
RecordControl | StopTimeControl
TempoControl | ToneControl
VideoControl | VolumeControl

Un exemple d'ús de controls:

Player p;
VideoControl vc;  
try {
   p = Manager.createPlayer("http://server/somemovie.mpg");
   p.realize();
   // Obtenir el control de volum
   vc = (VideoControl) p.getControl("VideoControl");
   ....
   p.start();
} 
catch(IOException ioe) {
} 
catch(MediaException me) {
}

Interfície PlayerListener

Permet controlar els esdeveniments d'un reproductor. La interfície PlayerListener declara només un mètode:

 void playerUpdate(Player player, String event, Object eventData) 

Aquest mètode és invocat cada cop que succeïx un esdeveniment de reproductor.

Els tipus d'esdeveniments són declarats com a variable estàtiques a la interfície PlayerListener:

  • PlayerListener.BUFFERING_STARTED
  • PlayerListener.BUFFERING_STOPPED
  • PlayerListener.CLOSED,
  • PlayerListener.DEVICE_AVAILABLE
  • PlayerListener.DEVICE_UNAVAILABLE
  • PlayerListener.DURATION_UPDATED
  • PlayerListener.END_OF_MEDIA
  • PlayerListener.ERROR
  • PlayerListener.RECORD_ERROR
  • PlayerListener.RECORD_STARTED
  • PlayerListener.RECORD_STOPPED
  • PlayerListener.SIZE_CHANGED
  • PlayerListener.STARTED
  • PlayerListener.STOPPED
  • PlayerListener.STOPPED_AT_TIME
  • PlayerListener.VOLUME_CHANGED

Un parell de coses ha tenir en compte:

  • Cal recordar que hi han noms d'esdeveniments específics per als diferents estats del reproductor (CLOSED, STARTED i STOPPED)
  • L'esdeveniment DEVICE_UNAVAILABLE succeïx quan hi ha una trucada entrant. L'esdeveniment DEVICE_AVAILABLE succeïx quan la trucada finalitza.

Hi han dos mètodes a la classe Player que permeten afegir/treure control d'esdeveniments:

void addPlayerListener (PlayerListener listener)
void removePlayerListener (PlayerListener listener)

Recursos

Reproducció de tons simples

La classe Manager proporciona entre d'altres mètodes un mètode per executar sons simples basats en tons. Aquest mètode és Manager.playTone().

Un mètode simple com:

private void simpleTone(int note, int duration) {
	try {
	    Manager.playTone(note, duration, 80 /*vol*/);
	} catch (Exception ex){
	   System.out.println(ex);
	}
}

El podem utilitzar per a produir diferents tipus de tons. Dels exemples (Samples de Netbeans) us mostrem uns quants exemples:

Un to simple i curt:

simpleTone(ToneControl.C4, 100);

Un to simple llarg

simpleTone(ToneControl.C4 + 4, 1000);

On ToneControl és una interfície que permet controlar els tons.

ToneControl.C4 és un Do  
(C en anglès). De fet és el Do central d'un teclat i el seu valor és 60.
300px-Klaviatur-3-en.svg.png

Els possibles tons van de 0 (C-1) a 127 (G9) on C-D-E-F-G-A-B és una octava de l'escala musical :

  • C: Do
  • D: Re
  • E: Mi
  • F: Fa
  • G: Sol
  • A: La
  • B: Si
tone = 12 * log2 (freq/220) + 57

Note

Frequency (Hz)

MIDI Tone

A4

440.00

69

A#

466.16

70

B

493.88

71

C

523.25

72

C#

554.36

73

D

587.33

74

D#

622.25

75

E

659.25

76

F

698.45

77

F#

739.98

78

G

783.99

79

G#

830.60

80

Recursos:

Reproducció de MP3

try {
  Player p = Manager.createPlayer ("http://server/somemusic.mp3");
  p.setLoopCount(5);
  p.start();
} 
catch(IOException ioe) {
} 
catch(MediaException e) {
}

Amb control de volum:

Player p;
VolumeControl vc;
try {
   p = Manager.createPlayer("http://server/somemusic.mp3");
   p.realize();
   // Obtenir el control de volum i establir el volum al màxim?
   vc = (VolumeControl) p.getControl("VolumeControl");
   if(vc != null) {
      vc.setVolume(100);
   }
   p.prefetch();
   p.start();
} 
catch(IOException ioe) {
} 
catch(MediaException e) {
}
...

Reproducció de vídeo

try {
           Player p = Manager.createPlayer(
                   "http://www.fileformat.info/format/mpeg/sample/567fd6a0e0da4a8e81bdeb870de3b19c/DELTA.MPG");
           p.realize();
           GUIControl gc;
           if ((gc = (GUIControl) p.getControl("GUIControl")) != null) {
               Form form = new Form("My GUI");
               form.append((Item) gc.initDisplayMode(GUIControl.USE_GUI_PRIMITIVE, null));
               Display.getDisplay(this).setCurrent(form);
           }
           p.start();
       } catch (MediaException pe) {
       } catch (IOException ioe) {
       }

Exemple de melodia

new Thread() {
         public void run() {
           byte[] melodia = {
               ToneControl.VERSION, 1,
               ToneControl.TEMPO, 22,
               ToneControl.BLOCK_START, 0, 60, 8, 62, 4, 64, 4, 65, 4, 67, 4, 69, 4, 71, 4,
               72, 4, 74, 4, 76, 4, 77, 4, 79, 4, 81, 4, 83, 4, 84, 4,
               83, 4, 81, 4, 80, 4, 81, 4, 86, 4, 84, 4, 83, 4, 81, 4,
               81, 4, 79, 4, 78, 4, 79, 4, 60, 4, 79, 4, 88, 4, 79, 4,
               57, 4, 77, 4, 88, 4, 77, 4, 59, 4, 77, 4, 86, 4, 77, 4,
               56, 4, 76, 4, 86, 4, 76, 4, 57, 4, 76, 4, 84, 4, 76, 4,
               53, 4, 74, 4, 84, 4, 74, 4, 55, 4, 74, 4, 83, 4, 74, 4,
               84, 16, ToneControl.SILENCE, 16, ToneControl.BLOCK_END, 0, ToneControl.BLOCK_START, 1,
               79, 4, 84, 4, 88, 4, 86, 4, 84, 4, 83, 4, 81, 4, 79, 4,
               77, 4, 76, 4, 74, 4, 72, 4, 71, 4, 69, 4, 67, 4, 65, 4,
               64, 8, 76, 8, 77, 8, 78, 8, 79, 12, 76, 4, 74, 8, ToneControl.SILENCE, 8,
               ToneControl.BLOCK_END, 1, ToneControl.SET_VOLUME, 100, ToneControl.PLAY_BLOCK, 0,
               ToneControl.SET_VOLUME, 50, ToneControl.PLAY_BLOCK, 0,
               ToneControl.SET_VOLUME, 100, ToneControl.PLAY_BLOCK, 1,
               ToneControl.SET_VOLUME, 50, ToneControl.PLAY_BLOCK, 1,
               ToneControl.SET_VOLUME, 100, ToneControl.PLAY_BLOCK, 0,};
           try {
             Player player = Manager.createPlayer(Manager.TONE_DEVICE_LOCATOR);
             player.realize();
             player.setLoopCount( -1);
             ToneControl control = (ToneControl) player.getControl("javax.microedition.media.control.ToneControl");
             control.setSequence(melodia);
             player.start();
           }  catch (IOException ex) { ex.printStackTrace(); }  catch (MediaException ex) { ex.printStackTrace(); }
         } }.start();

MMAPI i fils d'execució

Per executar un player en un nou fil d'execució:

  new Thread(new Runnable() {
     public void run() {
        //Poseu aquí el codi del player
     }
  }).start();

Per exemple:

new Thread(new Runnable() {
public void run() {
   Manager.PlayTone(ToneControl.C4, 100,100);
 }
}).start();


Recursos:

Location API

Exemples d'aplicacions: Google Lattitude.

Android té un API.


Recursos:

Blueetooth Optional API 1.1. JSR82

JSR 82 és la API que permet als desenvolupadors, crear aplicacions que utilitzin la tecnologia Bluetooth. L'API també es anomenada Java platform access Bluetooth through the Java APIs for Bluetooth Wireless Technology (JABWT o BTAPI)

Bluetooth pot ser utilitzar per intercanviar fitxers, fotos, business cards o qualsevol tipus de dades entre dos dispositius mòbils.

MSA: Mobile Service Architecture http://www.google.es/url?sa=t&source=web&ct=res&cd=1&url=http%3A%2F%2Fjava.sun.com%2Fjavame%2Freference%2Fdocs%2Fmsa_datasheet.pdf&ei=LruRSaqoPJKT_gbDvrHBDA&usg=AFQjCNGq6NmKcq2Fp31FmoQkvuAQFbpwVQ&sig2=zhZMU5-WshwOfJIv515f-w

També anomenada

Obex:

OBEX és l'abreviatura de OBject EXchange, és un protocol de comunicacions que facilita l'intercanvi de fitxers binaris entre dispositius. El protocol es mantingut per la Infrared Data Association i ha estat adoptat per Bluetooth. Inicialment s'utilitzava amb sistemes d'infrarojos.

--acacha 18:51, 10 febr 2009 (CET)

Wireless Toolkit:

Suport la API i el test d'aplicacions Bluetooth sense disposar de maquinari bluetooth.


Recursos:

Web Services API (WSA), JSR 172

L'API per a serveis web (també coneguda per les inicials WSA - web services api-) està dissenyada per tal de funcionar amb CDC i CLDC. Ha estat desenvolupada per la comunitat Java (JSR 172) i és una API opcional de Java ME.

Característiques:

  • És un subconjunt restringit de l'API de J2SE
    • XML-Based RPC (JAX-RPC 1.1)
    • Algunes classes per al suport de Remote Method Invocation (RMI)
    • La API per analitzar sintàcticament documents XML és Simple API for XML, version 2 (SAX2).

L'API té 2 parts:

  • Invocació remota de serveis: És un subconjunt restringit de JAX-RPC 1.1 que possibilita l'accés a serveis web remots.
  • XML Parsing: permet treballar amb fitxers XML. És un subconjunt restringit de JAXP amb suport per a SAX 2.

Serveis web

Segons el W3C, un Servei Web és un programari dissenyat per ser interoperable entre màquines a través d'una xarxa (P. ex. Internet).

Un servei web ha de tenir una interfície que sigui descrita en un format processable per màquines (p. ex. WSDL). Els sistemes interactuen amb els SW segons la seva interfície utilitzant missatges (SOAP Envelopes) o segons la filosofia REST.

L'objectiu que persegueixen els serveis web és la interoperabilitat (un client PHP, Python, corrent en una plataforma Windows utilitzant un servei web Java en plataforma Linux... ) i per aquesta raó esta pensat per utilitzar estàndards madurs com XML i HTTP.

Els principals responsables de l'arquitectura i la estandardització dels serveis web són:

En mòbils l'arquitectura d'un servei web és la següent:

ArquitecturaServeisWebMobils.png

Analitzadors sintàctics (parsers):

El verb to parse en anglès significa analitzar sintàcticament.

En informàtica, "parsejar" és el procés d'analitzar una seqüència d'entrada per tal de determinar la seva estructura gramatical 
respecte a una gramàtica formal específica (en el nostre cas XML).

Un analitzador sintàctic o "parser" és un programa informàtic encarregat d'aquesta tasca. Amb Java analitzar sintàcticament documents XML representarà transformar documents XML a una estructura d'arbre o classes Java.

L'anàlisi sintàctic té 2 fases:

  • Identificar les unitat mínimes de significat (tokens).
  • Construir l'arbre sintàctic que relaciona els tokens.

L'analogia és clara amb l'anàlisi sintàctic en llengua. En XML els nodes representen els tokens i l'arbre sintàctic és l'estructura XPath d'un document XML.

AnalisiSintacticXML.png

Un document XML té una estructura en arbre. Podem distingir 2 tipus principals d'API a l'hora de processar document XML:

  • Tree-Based: Es crea un arbre en memòria amb tota l'estructura del document XML. L' especificació més important és DOM del W3C.
  • Event-Based. El document es processa mitjançant esdeveniments amb accés seqüencial. L'especificació més important és SAX del grup XML-DEV.
Java ME només suporta SAX. Més adequada a les restriccions de memòria dels dispositius mòbils

SAX vs DOM:

  • L'arbre es carrega complet en memòria.
  • Més lent.
  • Orientat a documents (Pull)
  • Documents petits/mitjans.
  • Menys línies de codi.
  • L'estructura és important.
  • Manipulació dels nodes segons el context.

SAX:

  • Menys consum de recursos de memòria.
  • Més ràpid.
  • Orientat a dades (Push)
  • Documents grans.
  • Més línies de codi.
  • Només les dades són importants.
  • Manipulació seqüencial

SAX (Simple API for XML (SAX)):

Es tracta d'un estàndard “de facto” per parsers Java basats en processament per esdeveniments. Java ME treballa amb la versió 2.0. SAX va ser desenvolupat originalment de forma col·laborativa pel grup de treball XML-DEV. Podem trobar altres implementacions en altres llenguatges (Python, Perl, etc.).

SAX funciona per esdeveniments:

  • Accés seqüencial unidireccional.
  • Analogia amb l'accés seqüencial a fitxers.
  • Sense informació d'estat. No es pot accedir a elements anteriors o posteriors (no hi ha memòria). Si volem accedir a estats anteriors ho hem d'implementar nosaltres mateixos (sovint utilitzant una Pila)
  • Poc ús de recursos de memòria.
SAXEvents.png

Amb Java ME utilitzarem JAXP (Java API for XML processment). Aquesta API proveeix de capacitat a Java per analitzar sintàcticament, validar i transformar documents XML.

Java ME no suporta tot JAXP:

  • Suporta SAX 2.0 (1.0 NO)
  • Suporta espais de noms XML.
  • No suporta DOM
  • No suporta fulls de transformació XSLT
  • Suporta DTD però no XML Schema

Els paquets de la API són:

javax.xml.parsers
org.xml.sax
org.xml.sax.helpers

Anem a veure un exemple de com analitzar sintàcticament un document XML amb SAX. Utilitzant en patró de disseny de factoria, podem obtenir un nou parser:

SAXParserFactory factory = SAXParserFactory.newInstance();
SAXParser saxParser = factory.newSAXParser();

El mètode més important de la classe SAXParser és parse(). Tenim 2 opcions:

parse(InputSource is, DefaultHandler dh) 
parse(java.io.InputStream is, DefaultHandler dh)

Com podeu veure teniu 2 paràmetres que indiquen:

  • InputSource|InputStream: Origen de dades amb el document XML a “parsejar”
  • Handler: Classe que especifica com s'ha de “parsejar” el document

Imagineu-vos que voleu analitzar el següent document XML:

 <grup nom="1r ESI Tarda">
       <alumne>
           <nom>Pere</nom>
           <cognoms>Abad Abellà</cognoms>
           <foto>no_avatar.gif</foto>
       </alumne>
       <alumne>
           <nom>Pau</nom>
           <cognoms>Ferret Amela</cognoms>
           <foto>no_avatar.gif</foto>
       </alumne>
       <alumne>
           <nom>Joan</nom>
           <cognoms>Coll Català</cognoms>
           <foto>no_avatar.gif</foto>
       </alumne>
       <alumne>
           <nom>Julia</nom>
           <cognoms>Mauri Soler</cognoms>
           <foto>no_avatar.gif</foto>
       </alumne>
   </grup>

Un possible Handler podria ser el següent:

public class GrupHandler extends DefaultHandler {
 ...
public void startElement(String uri, String localName, String qName, Attributes attributes)                 throws SAXException {
       if ("grup".equals(qName)) {
           stack.push(new Grup());
           String nom= attributes.getValue("nom");
           Grup grup = ((Grup) stack.peek());
           grup.setNom(nom);
       } else if ("alumne".equals(qName)) {
           stack.push(new Alumne());
       } else if ("nom".equals(qName) || "cognoms".equals(qName) ||
               "foto".equals(qName)) {
           stack.push(new StringBuffer());
           isStackReadyForText = true;
       }
   }

public void characters(char[] ch, int start, int length) throws SAXException {

       // if stack is not ready, data is not content of recognized element
       if (isStackReadyForText == true) {
           ((StringBuffer) stack.peek()).append(ch, start, length);
       } else {
           // read data which is not part of recognized element
       }
   }

public void endElement(String uri, String localName, String qName) throws SAXException {
	StackReadyForText = false;
       Object tmp = stack.pop();
       if (qName.equals("grup")) {
           grup = (Grup) tmp;
       } else if (qName.equals("alumne")) {
           ((Grup) stack.peek()).getAlumnes().addElement((Alumne) tmp);
       } else if (qName.equals("nom")) {
           ((Alumne) stack.peek()).setNom( tmp.toString() );
       } else if (qName.equals("cognoms")) {
           ((Alumne) stack.peek()).setCognoms(tmp.toString() );
       } else if (qName.equals("foto")) {
           ((Alumne) stack.peek()).setFotoURL(tmp.toString() );
       } else {
           stack.push(tmp);
       }
   } 

Com podeu veure s'utilitza una PILA per tal d'accedir a la informació d'esdeveniments anteriors. En aquest exemple estem convertint el fitxer XML en un (o uns) objectes Java que emmagatzemen la informació del document XML. Es tracta d'una forma de persistència. A aquest procés se l'anomena marshalling/unmarshalling.

XMLMarshalling.png

La pila té 2 operacions:

  • Push: Col·loca un objecte a la pila.
  • Pop: Treu un objecte de la pila.

Funcions startElement

  • Cada cop que comença un element nou, es crea l'objecte equivalent i es col·loca (push) a la pila

Funcions EndElement:

  • Es treu l'element amb un pop() i s'envia a l'element pare amb un peek()

Funció Characters

  • El nodes simples (o que només tenen text) com nom, cognoms o foto són Objectes StringBuffer

S'omple l'objecte amb el valor del text del node simple

Hi han llibreries com Commons Digester que estan especialitzades en el procés de marshalling/unmarshalling.

Recursos:

WMA Wireless Messaging API (JSR 120 i JSR 205)

L'API opcional Wireless Messaging API (WMA) proporciona a Java ME la possibilitat d'utilitzar amb independència de la plataforma serveis de missatgeria com SMS. WMA pot ser utilitzat amb CLDC i MIDP.

ArquitecturaWMA.gif

WMA és una api molt relacionada amb l'API GCF:

ArquitecturaWMA2.gif

Recursos:

JMUnit

JMUnit és un framework de proves unitàries (unit test framework) basat en JUnit però adaptat per a Java ME.

En programació, els testos unitaris és un mètode de disseny i desenvolupament d'aplicacions mitjançant el qual el programador verifica que unitats individuals de codi funcionen correctament. Una unitat (unit) és la part més petita que pot ser provada d'una aplicació.

En programació procedimental, una unitat pot ser un program, una funció, un procediment, etc. En programació orientada a objectes, la unitat més petita de test és el mètode.

Unit testing millora la qualitat del codi provant de forma local els components individuals d'una aplicació. És una eina útil per a identificar i solucionar errors de forma ràpida.

Junit és una eina per als test unitaris molt utilitzada en la versió estàndard de Java (Java SE) i en la versió Enterprise (Java EE). El problema però, és que Junit es basa en l'API de reflexió i com ja hem vist anteriorment aquesta API no esta completament suportada a Java ME. Per als desenvolupadors de Java ME hi ha dos alternatives:

  • JMUnit
  • J2MEUnit

Tant Netbeans (des de la versió 5.5) com Eclipse, portant incorporat suport per a les proves unitàries amb JMunit.

Propietats:

  • Funciona amb l'emulador de Sun (WTK) i amb múltiples dispositius reals.
  • És petit i adaptat per a córrer en dispositius antics (MIDP 1.0 devices).
  • Té una llarga llista de mètodes assert que faciliten la comprovació d'errors de test.
  • Suporta tant TestCases com TestSuites.
  • Suporta Ant (hi han tasques Ant per a correr test unitaris)

Alternativa:

Recursos:

Altres temes

Propietats del sistema

De forma similar al que succeïx amb Java SE, amb Java ME dins del paquet java.lang trobem la classe System. Entre d'altres coses, la classe System permet accedir a les propietats del sistema: Per exemple, els següent codi permet accedir a la propietat de sistema microedition.locale :

String locale = System.getProperty("microedition.locale");

Les propietats són valors de configuració formats per una parella clau/valor. La clau s'utilitza per identificar el valor i tant clau com valor són de tipus text. Les propietats del sistema són un subconjunt de propietats que proporcionen informació sobre el sistema on s'està executant una aplicació per a dispositius mòbils.

Algunes de les propietats de sistema més habituals en Java són:

  • "file.separator"
  • "java.class.path"
  • "java.home"
  • "java.vendor"
  • "java.vendor.url"
  • "java.version"
  • "line.separator"
  • "os.arch"
  • "os.name"
  • "os.version"
  • "path.separator"
  • "user.dir"
  • "user.home"
  • "user.name"

Amb Java SE hi ha un suport molt més potent per a treballar amb propietats (per exemple la classe java.util.Propierties). Tota aquesta funcionalitat no està disponible en Java ME.

També cal que tingueu en compte que en entorns mòbils Java ME, hi ha propietats de sistema que són específiques d'aquest entorn. Fins i tot, la existència o no de certes variables del sistema dependrà de la implementació de Java ME (p. ex. pot ser diferent en un Nokia, que en un Siemens, i fins i tot diferent entre diferents models d'una marca concreta).

Alguns exemples habituals de variables de sistema amb Java ME:

  • microedition.profiles: perfils mòbils suportats
  • microedition.configuration: configuració suportada
  • microedition.locale: idioma del mòbil
  • microedition.platform
  • microedition.encoding
  • etc...

A l'adreça de la web de nokia:

http://www.forum.nokia.com/info/sw.nokia.com/id/37086440-dcce-4fb4-aa3e-8d8c16d62b33/MIDP_System_Properties_v1_2_en.zip.html

Podeu trobar un document amb més exemples de propietats de sistema Java ME i un MIDLet que us permet consultar les propietats de sistema del vostre mòbil.


Recursos:

Internacionalització (i18n) i Localització (i10n)

La internacionalització és el procés de dissenyar programari que pugui ser adaptat a múltiples llengües i regions del món sense la necessitat d'aplicar canvis en el codi. En canvi, la localització és el procés d'adaptar programari per a una zona i/o llengua específica.

Sovint s'utilitzen els termes i18n i i10n per tal de referir-se a internacionalització i localització respectivament. Aquests termes tenen el seu origen en agafar la primera i la última lletra dels vocables internacionalization i localization i comptar el nombre de lletres que hi ha entre mig d'aquestes lletres (18 en el cas d'internacionalització i 10 en el cas de localització).

I com es duu a terme la internacionalització d'aplicacions? El sistema més utilitzat és guardar els textos susceptibles de ser traduïts en fitxers de text independents del codi font. D'aquesta forma es poden modificar els textos sense necessitat de tornar a compilar i es poden afegir tants fitxers de text, com idiomes es vulguin suportar.

En Java utilitzarem fitxers de propietats (també anomenats Resource Bundles). Resource Bundles es pot traduir com a paquets de recursos. En Java és comú anomenar als fitxers d'internacionalització:

message.properties

Aquest fitxer conté l'idioma per defecte. Si tenim altres idiomes, els noms del fitxers serien:

messages_es_MX.properties  messages_zh_CN.properties
messages_cs_CZ.properties  messages_ja_JP.properties
messages_de.properties     messages.properties

Però com llegim aquests fitxers d'internacionalització i els utilitzem en el nostre codi font? Amb Java SE tenim classes de suport com java.util.ResourceBundle i d'altres que faciliten la tasca de crear aplicacions internacionals. A més, existeixen llibreries extres de suport a i18n, com les proporcionades per jakarta-commons de la Apache Software Foundation.

IMPORTANT: A Java ME NO tenim cap mena de suport. S'ha d'implementar des de zero

Per sort, el WTK proporciona un exemple d'una classe que ens permet gestionar la internacionalització. L'exemple el podeu trobar a:

~/netbeans-6.5/mobility8/WTK2.5.2/apps/i18nDemo

La classe s'anomena LocalizationSupport. La majoria de IDEs (com Netbeans o Eclipse) permeten treballar amb aquests tipus de fitxers. Per exemple, amb Netbeans podeu crear un fitxer d'internacionalització amb l'assistent de creació de fitxers (Ctrl+n o Menú File>New):

ResourceBundle.png

Per obtenir un missatge:

LocalizationSupport.getMessage("CLAU_DEL_MISSATGE")


Recursos:

WAP

Consulteu l'article WAP.

UEI. Unified Emulator Interface

Idees d'aplicacions Java:

  • SMS. Aplicació que enviï missatges SMS (encriptació-desencriptació)....

Documentació. Java docs

Podeu trobar la documentació de referència de Java ME a:

http://java.sun.com/javame/reference/apis.jsp

Amb Netbeans, tenim la documentació al disc dur local. La podeu trobar a:

~/netbeans-6.5/mobility8/WTK2.5.2/docs/api
$ cd ~/netbeans-6.5/mobility8/WTK2.5.2/docs/api
$ ls
jsr082  jsr135  jsr172  jsr177  jsr211  jsr239  midp

Si entreu a la carpeta midp, podeu mostrar la API executant:

$ cd midp/
$ ls
allclasses-frame.html    constant-values.html  help-doc.html   index.html  javax                overview-summary.html   
package-list  stylesheet.css
allclasses-noframe.html  deprecated-list.html  index-all.html  java        overview-frame.html  overview-tree.html     resources
$ firefox index.html 

Aquesta és la documentació de MIDP 2.0 (JSR 118) del CLDC

Tota la resta de carpetes corresponen als paquets opcionals:

  • jsr082:
  • jsr135:
  • jsr172:
  • jsr177:
  • jsr211:
  • jsr239:


Resolució de problemes. Troubleshooting

La interfície gràfica del mòbil és penja al utilitzar threads (fils d'execució) i connexions de xarxa

Si us trobeu que la vostra aplicació Java ME es penja durant l'intent de connexió (pantalla airtime):

Mobil penjat durant l'execució de la pantalla airtime

Aleshores és que no esteu utilitzant els fils d'execució correctament. Consulteu l'apartat Threading d'aquest article.

També us pot ser de molta ajuda el següent enllaç:

Recursos