javabog.dk  |  << forrige  |  indhold  |  næste >>  |  programeksempler  |  om bogen

13 Mobiltelefoner (J2ME)


13.1 Introduktion til midletter 182

13.1.1 Eksempel - en simpel midlet 182

13.1.2 Prøvekøre en midlet 182

13.1.3 Midletters livscyklus 183

13.2 Brugergrænseflader i midletter 184

13.2.1 Klassen Display (den fysiske skærm) 184

13.2.2 Klassen Displayable (skærmbilleder) 185

13.2.3 Klassen Ticker (rulletekst på et skærmbillede) 185

13.2.4 Klassen Image (billeder) 185

13.2.5 Klassen Font (skrifttyper) 186

13.3 Direkte grafiktegning og spil 187

13.3.1 Klassen Canvas 188

13.3.2 Klassen GameCanvas og lagdelt grafik 188

13.3.3 Klassen Graphics 189

13.4 Grafiske standardkomponenter 190

13.4.1 Eksempel: Gæt et tal 190

13.4.2 Kommandoer og hændelser i midletter 191

13.4.3 TextBox 192

13.4.4 Alert 192

13.4.5 List 193

13.4.6 Form 194

13.5 Tilgængelige klasser fra J2SE 195

13.5.1 Pakken java.lang 195

13.5.2 Pakken java.util 195

13.5.3 Pakken java.io 195

13.6 Netværkskommunikation 196

13.6.1 Pakken javax.microedition.io 196

13.6.2 Eksempel: Kommunikation med webserver 197

13.7 Gemme data i telefonen 199

13.8 Udviklingsværktøjer til midletter 200

13.8.1 Wireless Toolkit 200

13.8.2 Sun ONE Studio Mobile Edition 200

13.8.3 Borland JBuilder MobileSet 201

13.9 Opbygningen af J2ME 202

13.9.1 Konfigurationer 202

13.9.2 Profiler 203

13.10 Yderligere læsning 203

J2ME (Java 2 Micro Edition) er Suns Java-udviklingsplatform til at lave applikationer, der er beregnet til apparater med begrænset hukommelse.

Det er muligt at sammensætte bestanddelene i J2ME, sådan at man får de klasser, der er brug for til en bestemt kategori af apparater.

Der, hvor J2ME har vundet størst udbredelse, er inden for udviklingen af små programmer beregnet til mobiltelefoner (kaldet midletter), og det er det, vi vil beskæftige os med her. Andre anvendelsesmuligheder beskrives kort i afsnit 13.9.

For kortheds skyld vil vi benytte benævnelsen 'telefon' i det følgende, og det er op til læseren selv at læse 'telefon' som 'mobiltelefoner og en lang række andre små håndholdte computere og apparater'.

13.1 Introduktion til midletter

Midletter er små programmer, som er beregnet til at køre på en mobiltelefon. Ordet 'midlet' kommer fra MIDP (Mobile Information Device Profile) og skal opfattes analogt med ordet 'applet'. Ligesom appletter skal arve fra klassen Applet, skal midletter arve fra klassen MIDlet (der ligger i pakken javax.microedition.midlet).

13.1.1 Eksempel - en simpel midlet

Lad os se nærmere på, hvad man skal gøre, hvis man vil skrive sine egne midletter. Nedenfor er et simpelt eksempel på en midlet, der viser en (fiktiv) vejrudsigt:

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class Vejrmidlet extends MIDlet
{
   // systemet starter midletten
  public void startApp()
  {
    // opret et skærmbillede (en liste)
    List sb = new List("Vejret", List.IMPLICIT);
    sb.append("Det bliver let skyet og blæsende", null);
    sb.append("Temperatur mellem 17 og 22 grader", null);

    // vis skærmbilledet
    Display.getDisplay(this).setCurrent( sb );
  }

   // systemet standser midletten
  public void pauseApp() {}

   // systemet smider midletten væk
  public void destroyApp(boolean unconditional) {}
}

I dette eksempel bruger vi en List (en valgliste) og tilføjer den tekst vi ønsker skal vises ud for hvert af listens elementer.

Når startApp() kaldes af systemet skal midletten bestemme, hvad der vises på skærmen. Det gøres med

  Display.getDisplay(this).setCurrent( skærmbillede )

hvor 'skærmbillede' kan være f.eks. en List, TextBox, Form eller Canvas (se senere).

13.1.2 Prøvekøre en midlet

Før du kan oversætte og køre en midlet skal du have fat i et udviklingsværktøj til mobiltelefonudvikling. Udvilkingsværktøjer til J2ME behandles i afsnit 13.8.

Under udviklingen af en midlet vil man normalt også prøve at køre den, før man overfører programmet til en rigtig telefon. Til dette formål har de fleste udvilkingsværktøjer en emulator indbygget, der kan udføre midletten, som om den kørte på en rigtig telefon. Figuren til højre for programudskriften ovenfor viser en sådan emulator (kaldet DefaultColorPhone).

Hvis ens program består af flere klasser, er det nødvendigt at pakke dem i en JAR-fil, for at de kan hentes ned af brugeren. Derudover skal man lave en manifestfil, der indeholder information om indholdet i JAR-filen. Man kan oprette disse filer med de fleste udviklingsværktøj til midletter.

13.1.3 Midletters livscyklus

En midlets livscyklus styres af et program i mobiltelefonen, der hedder Application Management Software (AMS).

Når brugeren ønsker at køre en midlet, henter AMS midletten ned fra en server og starter midletten:

Metoderne startApp(), pauseApp() og destroyApp() er abstrakte og skal derfor altid implementeres.

Kode, der skal udføres ved initialiseringen af midletten, bør egentlig være i midlettens konstruktør fremfor i startApp().

I pauseApp() kan man placere kode til at gemme data indtil startApp() kaldes igen.

13.2 Brugergrænseflader i midletter

De muligheder, man har for at lave brugergrænseflader til en midlet, er defineret i pakken javax.microedition.lcdui.

Det centrale element, når man skal designe brugergrænseflader, er et skærmbillede (en klasse, der arver fra Displayable). Et skærmbillede er et objekt, som viser grafik og tager imod indtastninger fra brugeren. Der kan kun være ét skærmbillede synligt ad gangen.

Klassen Canvas er beregnet til at lave direkte grafiktegning (til f.eks. spil). Bruger man denne klasse, må man selv tage højde for skærmstørrelsen og farvedybden. Denne klasse er nærmere beskrevet i afsnit 13.3, Direkte grafiktegning og spil.

Der er 4 klasser til at lave grafiske brugergrænseflader v.hj.a. standardkomponenter, nemlig Form, Alert, List og TextBox. Når man bruge disse klasser behøver man ikke tænke på hvilken skærmstørrelse og farvedybde telefonen har (de er programmeret til at ligne det øvrige menusystem på den pågældende telefon). Disse klasser er nærmere beskrevet i afsnit 13.4 Grafiske standardkomponenter.

13.2.1 Klassen Display (den fysiske skærm)

Klassen Display repræsenterer den fysiske skærm og har bl.a. metoderne:

static Display getDisplay(midlet) skaffer Display-objektet

boolean isColor() om telefonen har farveskærm

int numColors() giver antallet af farver eller gråtoner på telefonen

void setCurrent(Displayable skærmbillede) beder telefonen vise et bestemt skærmbillede

Displayable getCurrent() giver det aktuelle skærmbillede, der vises

boolean flashBacklight(int varighed) blinker med telefonens baggrundsbelysning

boolean vibrate(int varighed) vibrerer med telefonen

Midletten kan skifte mellem de forskellige skærmbilleder ved at kalde metoden setCurrent(Displayable) på Display-objektet.

Klassen Display er implementeret som en singleton, så der kan kun være et skærmbillede synligt ad gangen. Man får fat i Display-objektet ved at kalde Display.getDisplay() med midletten som parameter.

13.2.2 Klassen Displayable (skærmbilleder)

Klassen Displayable - metoder fælles for alle skærmbilleder

String getTitle() giver titlen på skærmbilledet

void setTitle(String titel) sætter titlen

Ticker getTicker() giver rulleteksten på skærmbilledet

void setTicker(Ticker rulletekst) sætter en rulletekst (i stedet for den gamle)

boolean isShown() tjekker, om skærmbilledet er synligt

void addCommand(Command k) føjer en kommando til skærmen

void removeCommand(Command k) sletter en kommando fra skærmen

void setCommandListener(CommandListener l) sæt lytter til kommandoer for dette skærmbillede

int getWidth() giver bredden af skærmbilledet

int getHeight() giver højden af skærmbilledet

13.2.3 Klassen Ticker (rulletekst på et skærmbillede)

Man kan få en lille tekst til at rulle hen over skærmen (en rulletekst - eng.: ticker). Det gøres med f.eks.:

Ticker rulletekst = new Ticker("Dette er en tekst der ruller hen over skærmen");
Displayable skærmbillede = new List("Vejret", List.IMPLICIT);
skærmbillede.setTicker( rulletekst );

Klassen Ticker (rulletekst på et skærmbillede)

String getString() aflæser rulleteksten

void setString(String str) sætter rulleteksten

13.2.4 Klassen Image (billeder)

Et Image-objekt bruges til at tegne grafikbilleder på skærmen (med Canvas) eller i forskellige grafiske standardkomponenter såsom Alert, Choice, Form og ImageItem.

Klassen Image

static Image createImage(Image b) opretter et billede ud fra det eksisterende billede b

static Image createImage(Image b, int x, y, br, hø, transf) ditto, men med transformation og klipning

static Image createImage(byte[] data, int afs, int lgd) opretter billede fra array af byte

static Image createImage(String navnPåResurse) opretter billede fra navngiven resurse

static Image createImage(InputStream) opretter billede fra datastrøm

static Image createRGBImage(int[] rgb, int br, hø, boolean alfa) opretter billede fra farvedata

static Image createImage(int bredde, int højde) opretter et foranderligt billede (der kan tegnes på)

Graphics getGraphics() grafiktegning på et (foranderligt) billede

boolean isMutable() sand, hvis foranderligt, falsk, hvis uforanderligt

int getWidth() giver bredden af billedet

int getHeight() giver højden af billedet

void getRGB(int[] rgb, int afs, scLgd, x, y, br, hø) aflæser billeddata og gemmer i arrayet rgb

Et billede er, afhængigt af hvordan det er oprettet, enten uforanderligt (dvs. umuligt at ændre i, når det først er oprettet), eller foranderligt (dvs. der kan ændres i det, efter at det er blevet oprettet).

Uforanderlige billeder oprettes ved at kalde createImage() med et array af byte, en datastrøm fra en fil eller netværket.

Foranderlige (eng.: mutable) billeder kan man ændre i via et Graphics objekt. De oprettes ved at kalde createImage() med en bredde og højde og starter med at være helt hvide.

Bruges et foranderligt billede i grafiske standardkomponenter, såsom Alert, Choice, Form og ImageItem, vil der blive taget en kopi af billedet.

13.2.5 Klassen Font (skrifttyper)

Man kan bede om en given skrifttype ved hjælp af metoden Font.getFont() og angive skriftstil, størrelse og type som parametre. Metoden vil returnere den skrifttype, som kommer tættest på det ønskede, ud fra de muligheder den givne telefon har.

Klassen Font

static Font getDefaultFont() giver systemets standardskrifttype

static Font getFont(int skriftnr) giver standardskriften for enten:
static int FONT_STATIC_TEXT eller FONT_INPUT_TEXT

static Font getFont(int type, int stil, int størrelse) giver skrifttype tættest på de angivne parametre
static int STYLE_PLAIN, STYLE_BOLD, STYLE_ITALIC, STYLE_UNDERLINED skriftstil
static int SIZE_SMALL, SIZE_MEDIUM, SIZE_LARGE skriftstørrelse
static int FACE_SYSTEM, FACE_MONOSPACE, FACE_PROPORTIONAL skrifttype

int getStyle() returnerer skriftstilen (fed, kursiv, understreget)

int getSize() giver skriftstørrelsen

int getFace() giver skrifttypen

boolean isPlain() giver sand, hvis skriftstilen er 'plain' (ikke fed, kursiv, ...)

boolean isBold() giver sand, hvis skriftstilen er sat til fed skrift

boolean isItalic() giver sand, hvis skriftstilen er sat til kursiv

boolean isUnderlined() giver sand, hvis skriftstilen er sat til understreget

int getHeight() giver standardhøjden af en tekstlinje

int getBaselinePosition() giver afstand i punkter fra top af teksten til grundlinjen

int charWidth(char tegn) giver bredden af et tegn i skærmpunkter

int charsWidth(char[], int afs, int lgd) giver bredden af et array af tegn

int stringWidth(String streng) giver bredden af en streng

int substringWidth(String str, int afs, int lgd) giver bredden af en delstreng

13.3 Direkte grafiktegning og spil

Hvis man vil lave sin egen grafik, skal man arve fra klassen Canvas og definere dens paint()-metode. Canvas er også beregnet til at lave spil og har metoder til at opfange brugerens tastetryk og evt. brug af et pegeredskab.

Her er et eksempel, der tegner en tekst og en firkant, der kan styres med piletasterne.

Først skærmbilledet, der viser grafikken:

import javax.microedition.lcdui.*;

public class Canvasgrafik extends Canvas
{
  private int x,y;

  public Canvasgrafik()
  {
    x = getWidth()/2;
    y = getHeight()/2;
  }
    
  public void paint(Graphics g)
  {
    // slet baggrunden
    g.setColor( 0x00ffffff ); // hvid
    g.fillRect(0, 0, getWidth(), getHeight());

    g.setColor( 0x00000000 ); // sort
    g.drawString("Brug piletasterne",0,0,
      Graphics.TOP|Graphics.LEFT);
    g.fillRect(x,y,3,3);
  }

  protected void keyPressed(int tastkode)
  {
    switch (getGameAction(tastkode)) {
      case UP:   y--; break;
      case DOWN: y++; break;
      case LEFT: x--; break;
      case RIGHT:x++; break;
    }
    repaint();  // bed systemet kalde paint
  }
}

Vi har defineret paint() for at kunne tegne noget på skærmen. Graphics-objektet, der overføres, minder meget om det Graphics-objekt, man kender fra java.awt.

Vi har også defineret keyPressed() for at få at vide, når brugeren trykker på en tast. For at få tastkoden omsat til en handling i 'spillet' på en platformsuafhængig måde kalder vi getGameAction().

Her er midletten, der viser Canvas-objektet:

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class VisCanvasgrafik extends MIDlet
{
   Canvasgrafik grafik = new Canvasgrafik();

   public void startApp()
   {
      Display.getDisplay(this).setCurrent(grafik);
   }

   public void pauseApp() {}
   public void destroyApp(boolean unconditional) {}
}

13.3.1 Klassen Canvas

Klassen Canvas (der arver fra Displayable) er beregnet til at nedarve fra.

Metoder i klassen Canvas, der kan kaldes af programmøren

boolean isDoubleBuffered() om telefonen bruger dobbelt tegnebuffer til jævn grafik

boolean hasPointerEvents() om telefonen har en pegeenhed (tryk og slip-hændelser)

boolean hasPointerMotionEvents() om telefonen har en pegeenhed, der har træk-hændelser

boolean hasRepeatEvents() om tasterne kan repetere (se keyRepeated() senere)

String getKeyName(int tastkode) oversætter en tastkode til en streng, der beskriver tasten

int getGameAction(int tastkode) oversætter en tastkode til handling (f.eks. i et spil)

int getKeyCode(int handling) oversætter en handling (f.eks. i et spil) til en tastkode
tastkode>0 svarer til unikode-tegnet, så tegn=(char)tastkode
tastkoder på telefonen: KEY_NUM0, ... , KEY_NUM9, KEY_STAR, KEY_POUND
handlinger (spilknapper): UP, DOWN, LEFT, RIGHT, FIRE, GAME_A, ... , GAME_D

void setFullScreenMode(boolean) sætter fuldskærmstilstand (uden titel, menuer etc.)

void repaint() beder systemet om at gentegne hele skærmen

void repaint(x, y, br, hø) beder systemet om at gentegne et område af skærmen

void serviceRepaints() gennemtvinger evt. ventende gentegninger omgående

Metoder på Canvas, som systemet kalder

De følgende metoder kan du definere i nedarvingen. Systemet kalder dem på bestemte tidspunkter. De kan altså tilsidesættes for at få kaldt noget kode i bestemte tilfælde:

Tomme metoder som systemet kalder i Canvas (beregnet til, at programmøren definerer dem)

  // skal defineres. Kaldes, når skærmbilledet skal gentegnes
  protected void paint(Graphics g)

  // Kaldes med en tastkode, når brugeren ... 
  protected void keyPressed(int tast)   // trykker en tast ned
  protected void keyRepeated(int tast)  // holder en tast nede, så den repeterer
  protected void keyReleased(int tast)  // når brugeren slipper tasten igen

  // Kaldes når en evt. pegeenhed (f.eks. en mus) ... 
  protected void pointerPressed(x,y)    // trykkes ned (a la mousePressed)
  protected void pointerDragged(x,y)    // trækkes nedtrykket (a la mouseDragged)
  protected void pointerReleased(x,y)   // slippes (a la mouseReleased)

  // Kaldes lige før skærmbilledet bliver vist (bliver synligt på skærmen)
  protected void showNotify()
  // Kaldes lige efter at skærmbilledet er blevet skjult (ikke mere er synligt)
  protected void hideNotify()

  // Kaldtes når skærmbilledet skifter størrelse (f.eks. til fuld skærm)
  protected void sizeChanged(int nyBredde, int nyHøjde)

13.3.2 Klassen GameCanvas og lagdelt grafik

Klassen GameCanvas, der findes pakken javax.microedition.lcdui.game, er endnu mere specialiseret mod spil. Den udvider Canvas med faciliteter som dobbeltbuffer (at grafikken først tegnes i en separat tegnebuffer og derefter vises på skærmen for at opnå mere flydende grafik) og tastestatus (om en tast er holdt nede eller ej).

I samme pakke findes også understøttelse for lagdelt grafik. Det er interessant, når man skal tegne f.eks. en bane og nogle bevægelige figurer på banen (kaldet sprites).

13.3.3 Klassen Graphics

Klassen Graphics

final int HCENTER, LEFT, RIGHT horisontal justering

final int VCENTER, TOP, BOTTOM, BASELINE vertikal justering

void translate(int x, int y) sæt forskydning

int getTranslateX(), getTranslateY() aflæs forskydning

void setColor(int farve) sæt farve

void setColor(int r, int g, int b)

void setGrayScale(int farve)

int getColor() aflæs farve

int getRedComponent(), getGreenComponent(), getBlueComponent(), getGrayScale()

int getDisplayColor(int farve) giver en farves faktiske tegnefarve på skærm

final int SOLID, DOTTED linjetyper (ubrudt eller stiplet linje)

void setStrokeStyle(int linjetype) sætter linjetypen

int getStrokeStyle() aflæser linjetype

void setFont(Font) skrifttypen

Font getFont()

void setClip(int x, int y, int br, int hø) sæt klipning (hvor der skal tegnes)

int getClipX(), getClipY(), getClipWidth(), getClipHeight()

void clipRect(int x, int y, int b, int h) tilføj klipnings-rektangel

void drawLine(int x, int y, int x2, int y2)

void fillRect(int x, int y, int bredde, int højde)

void drawRect(int x, int y, int bredde, int højde)

void drawRoundRect(int x, int y, int bredde, int højde, int buebredde, int buehøjde)

void fillRoundRect(int x, int y, int bredde, int højde, int buebredde, int buehøjde)

void fillArc(int x, int y, int bredde, int højde, int startvinkel, int buevinkel)

void drawArc(int x, int y, int bredde, int højde, int startvinkel, int buevinkel)

void drawString(String tekst, int x, int y, int ankerpunkt)

void drawSubstring(String tekst, int afsæt, int længde, int x, int y, int ankerpunkt)

void drawChar(char tekst, int x, int y, int ankerpunkt)

void drawChars(char[], tekst, int afsæt, int længde, int x, int y, int ankerpunkt)

void drawImage(Image billede, int x, int y, int ankerpunkt)

void drawRegion(Image bil, int xBil, int yBil, int br, int hø, int trans, int x, int y, int anker)

void copyArea(int xFra, int yFra, int br, int hø, int trans, int x, int y, int anker)

void fillTriangle(int x1, int y1, int x2, int y2, int x3, int y3)

void drawRGB(int[] farver, int afs, int lgd, int x, int y, int br, int hø, boolean gennemsigtig)

13.4 Grafiske standardkomponenter

I dette afsnit vil vi gennemgå, hvordan man kan lave grafiske brugergrænseflader v.hj.a. standardkomponenter (klasserne Form, Alert, List og TextBox og deres hjælpeklasser).

13.4.1 Eksempel: Gæt et tal

Det følgende eksempel er det klassiske spil 'gæt tallet jeg tænker på', hvor brugeren skal gætte et tal mellem 1 og 100 og hele tiden får at vide, om tallet er højere eller lavere:

...

Det fungerer ved at bruge en formular (Form) med med to elementer, en streng (StringItem) og et indtastningsfelt (TextField).

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import java.util.*;

public class GaetEtTalMidletMIDlet extends MIDlet implements CommandListener
{
  private Display display = Display.getDisplay(this);

  private Form f        = new Form("Gæt tallet jeg tænker på");
  private StringItem si = new StringItem(null, null);
  private TextField tf  = new TextField(null, "", 2, TextField.NUMERIC);

  private Command afslutCommand = new Command("Afslut", Command.SCREEN, 2);
  private Command okCommand = new Command("OK", Command.SCREEN, 1);
  private Command nytSpilCommand = new Command("Nyt spil", Command.SCREEN, 1);

  private Random tilf = new Random();
  private int tallet;
  private int forsøg;


  public GaetEtTalMidletMIDlet() {
    f.addCommand(afslutCommand);
    f.addCommand(okCommand);
    
    // tilføj klassen (implementerer CommandListener) som lytter til formularen
    f.setCommandListener(this);

    f.append(si);  // tilføj strengen til formularen
    f.append(tf);  // tilføj indtastningsfeltet
  }

  private void nytSpil() {
    tallet = Math.abs(tilf.nextInt()) % 100 + 1; // et tal mellem 1 og 100
    forsøg = 0;
    si.setText("Tallet er mellem 1 og 100.");
    tf.setLabel("Skriv dit gæt:");
  }

  public void startApp() {    // kaldes lige efter konstruktøren
    nytSpil();
    display.setCurrent(f);
  }

  public void pauseApp() { }

  public void destroyApp(boolean unconditional) { }

  /**
   * Reagér på kommandoerne ok, afslut og nyt spil.
   * Metode skal defineres fordi klassen implementerer CommandListener-interfacet
   * og den bliver kaldt fordi formularen har fået tilføjet klassen som lytter.
   * Ved afslut ryddes op og midletten giver besked på at den skal smides væk.
   */
  public void commandAction(Command c, Displayable s) {
    if (c == afslutCommand) {
      destroyApp(false);
      notifyDestroyed();
    } 
    else if (c == okCommand) {
      int gæt = Integer.parseInt( tf.getString() );
      tf.setString("");
      forsøg++;
      if (gæt < tallet) si.setText("Det er højere end "+gæt+"!");
      else if (gæt > tallet) si.setText("Det er lavere end "+gæt+"!");
      else {
        si.setText("Rigtig på "+forsøg+" forsøg!");
        f.delete(1);      // Fjern indtastningsfeltet fra formularen
        f.removeCommand(okCommand);
        f.addCommand(nytSpilCommand);
      }
    }
    else if (c == nytSpilCommand) {
      f.removeCommand(nytSpilCommand);
      f.addCommand(okCommand);
      f.append(tf);       // Tilføj indtastningsfeltet til formularen igen
      nytSpil();
    }
  }
}

13.4.2 Kommandoer og hændelser i midletter

Hver af brugerens mulige handlinger repræsenteres af et Command-objekt. I 'Gæt et tal'-eksemplet ovenfor var de mulige handlinger 'Afslut', 'OK' og 'Nyt spil'.

Command-objektet har et navn (kort tekst, der vises på skærmen), en valgfri længere tekst, en type (der kan indvirke på hvordan kommandoen vises) og en prioritet.

Kommandoernes indbyrdes placering afgøres af deres prioritet. Afhængig af telefonens udformning kan det ske at den ikke kan vise alle kommandoerne. Første prioritet vises altid, mens kommandoer med anden- og tredieprioritet måske kun kan vises i en undermenu.

Klassen Command repræsenterer en mulig handling brugeren kan foretage

static int BACK, CANCEL, EXIT, HELP, ITEM, OK, SCREEN, STOP kommandotyper

Command(String navn, int type, int prioritet) konstruktør

Command(String navn, String langtNavn, int kommandoType, int prioritet) konstruktør

int getCommandType() giver typen af kommandoen

String getLabel() giver navnet på kommandoen

String getLongLabel() giver det lange navn på kommandoen

int getPriority() giver prioriteten i forhold til andre kommandoer (1 er højest)

Kommandoer skal føjes til et skærmbillede, hvorefter de vises nederst som mulige handlinger, brugeren kan gøre:

    skærmbillede.addCommand(okCommand);

Man skal sætte en lytter (der skal implementere CommandListener) på skærmbilledet

    skærmbillede.setCommandListener( lytter );

Interfacet CommandListener har metoden commandAction(), der bliver kaldt, hvis brugeren vælger handlingen, som Command-objektet repræsenterer.

13.4.3 TextBox

En TextBox giver brugeren mulighed for at indtaste og redigere tekst.

Klassen TextBox (arver fra Displayable)

TextBox(String titel,String tekst,int maxstørrelse,int type) opretter en TextBox, hvor type kan være:
ANY, EMAILADDR, NUMERIC, PHONENUMBER, URL, DECIMAL, PASSWORD,
SENSITIVE, UNEDITABLE, NON_PREDICTIVE,
INITIAL_CAPS_WORD, INITIAL_CAPS_SENTENCE

String getString() giver TextBoxens indhold som en streng

void setString(String nytIndhold) sætter indholdet

int getChars(char[]) giver TextBoxens indhold som et array af char

void setChars(char[] nytIndhold, int afs, int lgd) erstatter teksten med indholdet nytIndhold

void insert(String tekst, int position) sætter en tekst ind lige før den angivne position

void insert(char[] data, int afs, int lgd, int position) ditto.

void delete(int afs, int lgd) sletter lgd tegn startende fra afs

int getMaxSize() giver maksimale antal tegn, der er plads til

int setMaxSize(int) sætter det maksimale antal tegn

int size() giver antallet af tegn, der er i tekstboksen lige nu

int getCaretPosition() returnerer markørens aktuelle position

void setConstraints(int) sætter type (ANY, EMAILADDR, NUMERIC, ...)

int getConstraints() giver den aktuelle type

void setInitialInputMode(String måde) vink til inputmåde (f.eks. store bogstaver)

13.4.4 Alert

En Alert er en klasse, der viser data til brugeren, og venter et bestemt tidsrum, før det næste skærmbillede vises. Et Alert-skærmbillede kan vise tekst og billede og kan modtage brugerinput som andre skærmbilleder.

Klassen Alert (arver fra Displayable)

Alert(String titel) opretter en Alert med den givne titel
Alert(String titel,String tekst,Image billede,AlertType alerttype) ditto

static Command DISMISS_COMMAND kommando, der angiver, at brugeren har lukket alerten

int getDefaultTimeout() returnerer standardtiden, alerten vises

void setTimeout(int tid i millisek eller FOREVER) sætter, hvor længe alerten vises

int getTimeout() returnerer, hvor længe alerten vises

void setType(AlertType) sætter typen af alerten

AlertType getType() returnerer typen af alerten (typen kan være INFO,
WARNING, ERROR, ALARM, og CONFIRMATION)

void setString(String) sætter teksten

String getString() returnerer teksten i alerten

void setImage(Image) sætter alerten til også at vise et billedet

Image getImage() returnerer et evt. tidligere sat billede

void setIndicator(Gauge) sætter alerten til også at vise en "progress bar"

Gauge getIndicator() returnerer en evt. tidligere sat "progress bar"

13.4.5 List

En List er et skærmbillede med en liste af valgmuligheder, som brugeren kan navigere op og ned i.

Lister kan fungere på 3 måder:

Programmøren kan selv, vælge hvilke handlinger brugeren kan lave med hvert element, ved at definere nogle Command-objekter (se afsnit 13.4.2) og tilføje dem med addCommand(Command c) på listen. Derefter skal metoden setCommandListener(CommandListener lytter) kaldes med en lytter, der skal håndtere, hvis brugeren vælger at udføre en handling på et af elementerne i listen.

Klassen List (arver fra Displayable)

static int EXCLUSIVE, MULTIPLE, IMPLICIT listens type

static Command SELECT_COMMAND standardkommando for IMPLICIT-lister

List(String titel, int type) konstruktør til ny liste

List(String titel ,int type,String[] tekster, Image[] billeder) ny liste med tekst og billeder

String getString(int elementnr) giver strengen, der står på elementnr i listen

Image getImage(int elementnr) giver billedet på plads nr. elementnr

void set(int elementnr, String tekst, Image billede) sætter indholdet af listen på plads nr. elementnr

int append(String tekst, Image billede) tilføjer et nyt element i enden af listen

void insert(int elementnr, String tekst, Image billede) indsætter et nyt element lige før elementnr

void delete(int elementnr) sletter elementet på elementnr

void deleteAll() sletter alle elementer i listen

int size() returnerer antallet af elementer

void setFitPolicy(int politik) bestemmer, hvordan elementerne organiseres.
mulighederne er: Choice.TEXT_WRAP_DEFAULT, TEXT_WRAP_ON og TEXT_WRAP_OFF

int getFitPolicy() giver den aktuelle organisering af elementer

void setFont(int, Font) sætter skrifttypen

Font getFont(int) giver den aktuelle skrifttype

boolean isSelected(int) returnerer sand, hvis elementet er valgt

int getSelectedIndex() returnerer index for det valgte element

int getSelectedFlags(boolean[] valgte) giver antal valgte elementer og sætter valg.
(arrayet skal mindst have et antal pladser svarende til antallet af elementer i listen)

void setSelectedIndex(int elementnr, boolean valgt) sæt elementnr til at være valgt

void setSelectedFlags(boolean[] valgteelementer) sætter de valgte elementer i listen

void setSelectCommand(Command) tilsidesætter List.SELECT_COMMAND med en anden kommando (kun for type=List.IMPLICIT)

13.4.6 Form

Med en Form kan man sammensætte billeder, tekstfelter og lister, som man lyster.

En Form er en indtastningsformular, der kan indeholde forskellige felter: (Item-objekter), såsom: ImageItem, StringItem, CustomItem (som man kan arve fra og lave sine egne slags formular-elementer), ChoiceGroup, TextField, DateField, Gauge og Spacer.

En Form kan have et ItemStateListener-objekt tilknyttet, der lytter efter ændringer i formularens felter, og et ItemCommandListener, der lytter efter, om brugeren forsøger at udføre en kommando på et af formularens elementer.

Klassen Form (arver fra Displayable)

Form(String titel) opretter en ny Form med den angivne titel

Form(String,Item[]) opretter en ny Form med den angivne titel og Item-objekter

int append(Item) tilføjer et nyt Item til Form-objektet

int append(String) tilføjer en streng til formularen

int append(Image) tilføjer et billede til formularen

void insert(int itemnr, Item) Indsætter et Item-objekt på pladsen specificeret ved Itemnr

void delete(int itemnr) sletter Item-objektet på den angivne plads

void deleteAll() sletter alle Item-objekter i Formen

void set(int itemnr, Item nytItem) Udskifter Item på plads nr itemnr med det nye Item

Item get(int itemnr) returnerer elementet på plads nr. itemnr

void setItemStateListener(ItemStateListener l) sætter den nye lytter, hvorved den gamle slettes.

int size() returnerer antallet af Items i Formen

int getWidth() giver bredden i punkter af det areal, der er til rådighed i formularen

int getHeight() giver højden i punkter af det areal, der er til rådighed i formularen

13.5 Tilgængelige klasser fra J2SE

De følgende pakker og klasser fra Javas standardbibliotek (J2SE) er også tilgængelige for midletter.

13.5.1 Pakken java.lang

I pakken java.lang findes stort set alle de kendte klasser, man forventer fra J2SE.

Det er, ud over forskellige klasser til at håndtere undtagelser (nedarvinger fra Exception eller Throwable), klasserne:

13.5.2 Pakken java.util

I pakken java.util findes klasserne kendt fra J2SE (undtaget er dog klasserne til samlinger af data, Collections-klasserne, såsom List, ArrayList, Set, Map, ...):

13.5.3 Pakken java.io

I pakken java.io findes de almindelige klasser til IO-håndtering, dog uden dem, der er beregnet til filhåndtering (da man ikke kan gemme filer på en mobiltelefon).

Disse er (excl. klasser til at håndtere undtagelser):

I afsnit 13.6.1 beskrives de ekstra IO-klasser til netværkskommunikation, som er tilgængelige på en mobiltelefon.

Har man brug for at gemme data i telefonen kan man bruge RMS-systemet, der er beskrevet i afsnit 13.7.

13.6 Netværkskommunikation

Midletter kan kommunikere over netværket med en lang række protokoller (præcist hvilke der understøttes afhænger af apparatet), hvoraf den mest udbredte er HTTP-protokollen.

13.6.1 Pakken javax.microedition.io

I pakken javax.microedition.io ligger en række klasser og interfaces til netværkskommunikation, der er specielt rettet mod små apparater som telefoner.

Helt central er klassen Connector, gennem hvilken man kan åbne en række forskellige slags forbindelser, f.eks. en HTTP-forbindelse:

  HttpConnection c = (HttpConnection) Connector.open("http://javabog.dk");

Man får altså et Connection-objekt ud, som man derefter selv må sørge for at typekonvertere til den rigtige slags forbindelse.

Klassen Connector - alle metoderne kan kaste IOException

static int READ, WRITE, READ_WRITE læse/skrive-flag

static Connection open(String url) åbner forbindelse

static Connection open(String url, int læsSkriv) do, med læse/skrive-flag

static Connection open(String url, int læsSkriv, boolean udløb) do, men tjekker for udløbstid

static DataInputStream openDataInputStream(String url)

static DataOutputStream openDataOutputStream(String url)

static InputStream openInputStream(String url)

static OutputStream openOutputStream(String url)

Når Connector.open() kaldes, er det parameteren url, der afgør, hvilken slags forbindelse der bliver returneret:

Connector opretter forbindelser dynamisk ved at slå en protokolklasse op, hvis navn består af navnet på platformen, midletten køres fra, og protokolnavnet (fremfindes ud fra URL'en).

Derudover kan man også angive, om man ønsker at læse, skrive eller både læse og skrive til serveren i open()-metoden:

HttpConnection http = (HttpConnection) Connector.open(URL, Connector.READ_WRITE);

Når man har oprettet forbindelsen, kan man åbne en DataOutputStream. Den tillader, at man skriver en tekststreng direkte til den ved hjælp af metoden writeChars():

DataOutputStream out = http.openDataOutputStream();
out.writeChars("En tekst");

Man kunne også få en binær OutputStream og skrive til den, men så ville man i dette tilfælde være nødt til at pakke den ind i en PrintWriter(), som konverterer tekst til binært format.

13.6.2 Eksempel: Kommunikation med webserver

Nedenstående eksempel illustrerer, hvordan man kan sende og modtage tekst fra en server.

Brugeren kommunikerer med serveren ved at skrive en tekst, som bliver sendt til serveren. På serveren aktiveres JSP-filen modtag_besked.jsp (Java Server Pages, se afsnit 14.2):

<%@ page language = "java" import="java.io.*"%>
<%
  BufferedReader in = request.getReader();
  String besked = in.readLine(); // læs beskeden
  out.print(besked);      // ... og send den tilbage igen!
//<title>modtag besked</title>
%>

JSP-siden på serveren kvitterer for modtagelsen, ved at sende teksten tilbage igen.

import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class BeskedMidlet extends MIDlet
{
  private static BeskedMidlet instance;
  private SendBesked displayable = new SendBesked(this);

  public BeskedMidlet() {
    instance = this;
  }

  public void startApp() {
    Display.getDisplay(this).setCurrent(displayable);
  }

  public void pauseApp() {
  }

  public void destroyApp(boolean unconditional) {
  }

  public static void quitApp() {
    instance.destroyApp(true);
    instance.notifyDestroyed();
    instance = null;
  }
}

Midletten bruger klassen SendBesked (en TextBox), der står for kommunikation mellem bruger og server:

import javax.microedition.lcdui.*;
import java.io.*;
import javax.microedition.io.*;

public class SendBesked extends TextBox implements CommandListener {
  private BeskedMidlet midletten;
  private Alert a;
  public SendBesked(BeskedMidlet midletten)  {
    super("Skriv din besked", "", 50, TextField.ANY);
    this.midletten=midletten;
    setCommandListener(this);
    addCommand(new Command("Exit", Command.EXIT, 1));
    addCommand(new Command("Send", Command.OK,1));
  }

  public void commandAction(Command command, Displayable displayable) {
    if(command.getCommandType() == Command.EXIT)
      BeskedMidlet.quitApp();
    else if(command.getCommandType() == Command.OK) try {
      sendData(getString(),"http://localhost:8080/modtag_besked.jsp");
    } catch(IOException e){System.out.println(e);}
  }

  /**
   * Afsender en tekststreng til den angivne URL.
   * Efter afsendelsen af data kaldes metoden læsData(), som læser svaret fra
   * serveren.
   * @param data den tekst der skal sendes til serveren
   * @param URL URLén til den servlet eller JSP-side, der håndterer anmodningen
   */
  private void sendData(String data, String URL)throws IOException {
    HttpConnection http = null;
    try {
      http = (HttpConnection) Connector.open(URL, Connector.READ_WRITE);
      http.setRequestMethod(HttpConnection.POST);
      DataOutputStream out = http.openDataOutputStream();
      out.writeChars(data);
      out.flush();
      læsData(http.openDataInputStream());
    }
    catch (Exception ex) {
      System.out.println(ex);
    }
    finally {
      http.close();  //husk altid at lukke forbindelsen!
    }
  }
  
 /**
  * Læser en tekststreng fra den angivne InputStream.
  * Herefter vises en alert med indholdet af tekststrengen
  * @param is Indeholder de data, der kommer fra serveren
  */
  private void læsData(InputStream is) throws IOException {
    StringBuffer besked = new StringBuffer();
    try {
      int bogstav = is.read();
      while ( bogstav != -1)
      {
        besked.append ((char) bogstav);
        bogstav = is.read();
      }
      setString("");
      a = new Alert("Besked afsendt!",besked.toString(),null,AlertType.INFO);
      a.setTimeout(5000); //Vis Alerten i 5 sek
      Display.getDisplay(midletten).setCurrent(a);
    }
    catch(Exception e) {
      System.out.println(e);
    } finally {
      if(is!=null) is.close();
    }
  }
}

13.7 Gemme data i telefonen

Vil man gemme data i mobiltelefonen, skal man bruge klassen RecordStore fra pakken javax.microedition.rms (Record Management System). Man kan tænke på det som en begrænset mulighed for at gemme filer i telefonen.

Her er et eksempel på brug:

  // Åbn en 'fil' i telefonen. Opret den hvis den ikke allerede findes.
  RecordStore database = RecordStore.openRecordStore("minFil", true);

  // Skaf data i form af et array af byte
  String strengDerSkalGemmes = "Hej Verden";
  byte[] data =  strengDerSkalGemmes.getBytes();

  // gem data
  database.addRecord( data, 0, data.length );

  // luk 'filen'
  database.closeRecordStore();

Klassen RecordStore - næsten alle metoderne kan kaste RecordStoreException (ikke vist)

static String[] listRecordStores() giver navnene på gemte 'filer' for den aktuelle midlet

static void deleteRecordStore(String navn) sletter en 'fil'

static RecordStore openRecordStore(String navn, boolean opret) åbner og evt. opretter en 'fil'

static RecordStore openRecordStore(String navn, String leverandør, String midletnavn)

static RecordStore openRecordStore(String navn, boolean opret, int adgangsflag, boolean skrivbar)

static int AUTHMODE_PRIVATE, AUTHMODE_ANY adgangsflag

void setMode(int adgangsflag, boolean skrivbar)

String getName() giver navnet på 'filen'

int getVersion() giver versionsnummeret (ændres, når 'filen' ændres)

int getNumRecords() giver antallet af poster i 'filen' (Recordstore-objektet)

int getSize() giver antallet af byte, som 'filen' optager

int getSizeAvailable() giver, hvor meget ledig 'diskplads' der er i telefonen

long getLastModified() giver, hvornår 'filen' sidst var ændret i (i millisekunder)

int getNextRecordID() giver ID for den næste post

int addRecord(byte[] data, int afs, int lgd) tilføjer post med (del af) array af byte, giver et ID retur

void deleteRecord(int ID) sletter post med bestemt ID

int getRecordSize(int ID) giver størrelsen i byte for den givne post

int getRecord(int ID, byte[] data, int afs) gemmer post i et array af byte (afs bestemmer hvor)

byte[] getRecord(int ID) giver bytearray med en kopi af data fra den givne post

void setRecord(int ID, byte[] nyedata, int afs, int lgd) sætter dataene i den givne post

void addRecordListener(RecordListener) tilføjer et objekt, der lytter efter ændringer i 'filen'

void removeRecordListener(RecordListener) fjerner et lytter-objekt

RecordEnumeration enumerateRecords(RecordFilter filter, RecordComparator, boolean synkron)

void closeRecordStore() lukker 'filen'

13.8 Udviklingsværktøjer til midletter

13.8.1 Wireless Toolkit

Sun har udgivet en referenceimplementation af, hvordan midletter skal fungere i forskellige mobiltelefoner og håndholdte apparater. Denne implementation hedder J2ME Wireless Toolkit og kører under Windows, Linux og Sun Solaris.

Den kan hentes på http://java.sun.com/products/j2mewtoolkit.

Den består af et værktøj, hvor man kan oversætte og køre sine midletter.

Det er ikke noget integreret udviklingsmiljø, så faciliteter som integreret redigering, fejlfinding og dokumentation må man undvære.

Med Wireless Toolkit følger en række små eksempelprogrammer. Eksemplerne er uvurderlige til at få en fornemmelse af, hvordan de forskellige klasser benyttes, og hvordan de grafiske komponenter ser ud i forskellige telefoner og apparater.

Når man kører et eksempel, dukker en emulator op med et billede af den pågældende telefon. Man kan navigere enten ved at klikke på telefonens knapper eller med piletasterne, F1, F2, Home og End (ESC afslutter emulatoren).

13.8.2 Sun ONE Studio Mobile Edition

"Sun ONE Studio, Mobile Edition", der inkluderer Wireless Toolkit, er et integreret udviklingsmiljø, der direkte er beregnet til udvikling af midletter.

Det er relativt nemt at installere, kører under Windows, Linux og Sun Solaris og kan hentes gratis på adressen: http://wwws.sun.com/software/sundev/jde.

Når udviklingsmiljøet startes, dukker en række eksempler på midletter op, parat til at køre og med kildetekst, så man kan se hvordan forskellige ting programmeres. Højreklik f.eks. på den, der hedder 'UIDemo', og vælg 'Execute' (som vist på figuren ovenfor), og en telefon dukker op med midletten kørende inden i.

Når du vil lave dine egne midletter, skal du huske at have dem med i en "MIDlet suite", der også skal kende alle supplerende klasser.

13.8.3 Borland JBuilder MobileSet

Et andet udviklingsværktøj til midletter er JBuilder MobileSet. Det kan, som navnet antyder, integreres med JBuilder. JBuilder MobileSet inkluderer Wireless Toolkit.

Efter installationen har man alle de funktionaliteter, som er kendt fra JBuilder. Man kan umiddelbart oversætte og køre sine midletter.

Sammen med JBuilder MobileSet ligger også en pdf-fil, som bl. a. giver en nærmere beskrivelse af, hvordan installationen foregår. Husk, at du skal ind og ændre i stien til JDK'et til der, hvor du har Wireless Toolkit liggende, for at få lov til at lave en midlet.

Når du så skal til at køre dine almindelige programmer igen, skal du sætte stien tilbage til dit almindelige JDK (gratisudgaven af JBuilder tillader kun ét JDK, så her er man nødt til at erstatte det almindelige JDK med J2MEs JDK).

13.9 Opbygningen af J2ME

J2ME består af en lagdelt struktur. Det betyder, at leverandøren af et indlejret system kan vælge den virtuelle maskine og de klasser, der skal være til rådighed for programmøren af en given slags apparat (f.eks. en vaskemaskine eller et fjernsyn), og stadig bruge mindst mulig hukommelse.

På figuren nedenfor er vist opbygningen af J2ME:

Oven på styresystemet haves en virtuel maskine af valgfri størrelse og en konfiguration med et udvalg af klasser fra standardbiblioteket. Oven på dette haves en profil, der afhænger af, hvilken type apparat man har med at gøre.

Den udgave af J2ME, der har afgjort størst interesse og udbredelse, er den, der kan køre i nyere mobiltelefoner. Dens konfiguration kaldes CLDC (Connected Limited Device Configuration) og har en profil der hedder MIDP (MIDlet Profile). Det er den vi har beskæftiget os med i dette kapitel.

En af idéerne i J2ME er, som med J2SE (normal Java) og J2EE (Java til serversystemer), at give mulighed for platformsuafhængighed, og programmer skrevet til MIDP-profilen på CLDC-konfigurationen kører da også umodificeret på mange mobiltelefoner og en lang række andre lignende apparater.

13.9.1 Konfigurationer

En J2ME-konfiguration bestemmer den minimale platform for en bestemt kategori af apparater. Konfigurationen består af en virtuel maskine, et minimalt sæt af biblioteker, klasser og API'er (Application Programming Interface).

Der findes i øjeblikket 2 konfigurationer af J2ME:

CDLC benytter sig af en virtuel maskine, der hedder KVM. KVM kan køres af små apparater, der er beregnet til at være koblet på et netværk som f.eks. mobiltelefoner og små PDA'er.

CDC benytter den almindelige JVM eller evt. CVM (en virtuel maskine i mellemstørrelse) og er beregnet til større PDA'er, der f.eks. kan lave ruteplanlægning eller lignende.

13.9.2 Profiler

En profil er en slags overbygning på konfigurationen, og den giver mulighed for, at man kan tilpasse udviklingsmiljøet til en bestemt type apparater. F.eks. findes der 'Handheld profile' og 'MID profile' (til midletter).

Profilen indeholder yderligere klasser og metoder, som man skal bruge for at udvikle til en bestemt slags apparater. Derudover indeholder profilen oplysninger om konfigurationen af J2ME-platformen.

Det betyder, at hver producent kan vælge at lave sin egen profil til en given slags apparat, som man kan bruge til at lave programmer, der udnytter netop denne slags apparaters specielle muligheder.

13.10 Yderligere læsning

For yderligere information om J2ME og midletter end den, der er givet her, henvises til:

javabog.dk  |  << forrige  |  indhold  |  næste >>  |  programeksempler  |  om bogen
http://javabog.dk/ - af Jacob Nordfalk.
Licens og kopiering under Åben Dokumentlicens (ÅDL) hvor intet andet er nævnt (71% af værket).

Ønsker du at se de sidste 29% af dette værk (362838 tegn) skal du købe bogen. Så får du pæne figurer og layout, stikordsregister og en trykt bog med i købet.