830 likes | 991 Views
MIDlet Development. Virginie . Galtier @int-evry.fr March 2005. Overview. Introduction: J2ME target and architecture HelloWorld MIDlet User Interface Deployment Persistent storage Networking Security Optimizations References Quizz. Motivation.
E N D
MIDlet Development Virginie . Galtier @int-evry.fr March 2005
Overview • Introduction: J2ME target and architecture • HelloWorld MIDlet • User Interface • Deployment • Persistent storage • Networking • Security • Optimizations • References • Quizz
Motivation • Sun Mobility Developer Newsletter, December 2004: • 280+ Java handset models available from 32 manufacturers • installed base of Java handsets: 267 million at the beginning of 2004, and is projected to reach 1.5 billion by the end of 2007 • in Japan 50% of all handsets are J2ME-enabled, and have earned operators $1.4 billion • J2ME Wireless Toolkit won JavaPro Best Mobile Development Tool Award
J2ME Configurations • minimum platforms for the targeted range of devices: • a core collection of java classes • + specific classes (generic connection framework : javax.microedition.io) J2SE CDC CLDC • no interface java.lang.Comparable • no package java.net • no package java.awt • no class java.math.BigDecimal
J2ME Profiles Personal RMI Personal basis Digital Set Top Box IM PDA MID 1.0 & 2.0 Foundation CLDC CDC
Other J2ME APIs and Optional Packages • Java API for Bluetooth (JSR 82) • Wireless Messaging API (JSR 120: SMS & JSR 205: MMS) • JDBC Optional Package for CDC Foundation Profile (JSR 169): provides functionality equivalent the java.sql package • Location API (JSR 179): GPS or E-OTD… • SIP API (JSR 180): standard for VoIP and more • Mobile 3D Graphics API (JSR 184) • Mobile Media API (JSR 135): sound and multimedia • Web Service Access (JSR 172): access to web services: XML parsing, XML based RPC communication support • … many more existing or in progress…
MIDlet Lifecycle arg=true? throws MIDletStateChangeException ? loaded by device launched for the first time resume requested by MIDlet resumeRequest() new constructor() resumed by AMS startApp() notifyPaused() paused by MIDlet Active Paused pauseApp() paused by AMS startApp throws MIDletStateChangeException end requested by AMS end requested by MIDlet destroyApp(arg) destroyApp(true) no yes yes no Destroyed notifyDestroyed() Terminated
Skeleton MIDlet Class import javax.microedition.midlet.*; public class MyMIDlet extends MIDlet { // optional constructor, some initializations go here public MyMIDlet() { } // main MIDlet code goes here, some initializations also go here public void startApp() { } // operations to save current state before releasing the screen go here public void pauseApp() { } // operations to release resources before the MIDlet exits go here public void destroyApp(boolean unconditional) { } }
HelloWorld MIDlet 1/3: install • Download and install the J2ME Wireless Toolkit 2.2: http://java.sun.com/products/j2mewtoolkit/download-2_2.html • Launch the KToolbar • Create a new project: • Project name: MasterOfScience • MIDlet class name: HelloWorld • Keep the default configuration and finish project creation
HelloWorld MIDlet 2/3: code import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class HelloWorld extends MIDlet { private Form welcomeScreen; public HelloWorld() { welcomeScreen = new Form("My First MIDlet"); StringItem message = new StringItem("Hello World!","I'm a MIDlet!"); welcomeScreen.append(message); } protected void startApp() throws MIDletStateChangeException { Display.getDisplay(this).setCurrent(welcomeScreen); } protected void pauseApp() {} protected void destroyApp(boolean arg0) {} } • In yourPath\WTK22\apps\MasterOfScience\src, create HelloWorld.java:
HelloWorld MIDlet 3/3: execution • In the WTK, press the Run button • On the phone, press Launch
User Interface: Display • Device screen on which a MIDlet displays its user interface: Display object (package javax.microedition.lcdui) • To obtain a reference to it: displayManager = getDisplay(this) (this is the current MIDlet) • To display something on that screen: displayManager.setCurrent(something) where something is Displayable
User Interface: Displayable Displayable setTitle setTicker ... javax.microedition.lcdui Screen Canvas javax.microedition. lcdui.game List Alert GameCanvas TextBox Form append Image Item String ... ChoiceGroup TextField DateField Gauge StringItem
User Interface: Good Practices Inputting text can be tedious on portable devices, help the user by: • providing pre-filled in fields • providing lists to choose from rather than text input boxes • remembering previously typed in information • organizing easy navigation within the MIDlet screens • keeping screens simple: few elements, short texts so there is no need to scroll down
Interaction and Commands • Only a simple “window” displayed at the time • Need commands to change displayed content according to user input • Command items may be added (addCommand method) to a Displayable to be presented to the user. • Most important commands may be access directly, remaining commands are automatically presented in a menu. • Command behavior is defined in a CommandListener associated to the Displayable. • Most displayable items have set and get methods to change displayed content and get user input.
Interaction and Commands: Example 1/2 import javax.microedition.midlet.*; import javax.microedition.lcdui.*; public class FirstCommands extends MIDlet implements CommandListener { private Form welcomeScreen; private TextField userInput; private Ticker banner; private final Command QUIT = new Command("Exit",Command.EXIT, 2); private final Command CHANGE = new Command("Change",Command.SCREEN,1); public FirstCommands() { welcomeScreen = new Form("First Command"); welcomeScreen.addCommand(QUIT); welcomeScreen.addCommand(CHANGE); welcomeScreen.setCommandListener(this); banner = new Ticker("Welcome here! "); welcomeScreen.setTicker(banner); userInput = new TextField("enter banner text:","",40,TextField.ANY); welcomeScreen.append(userInput); }
Interaction and Commands: Example 2/2 protected void startApp() throws MIDletStateChangeException { Display.getDisplay(this).setCurrent(welcomeScreen); } protected void pauseApp() {} protected void destroyApp(boolean arg0) {} public void commandAction(Command c, Displayable d) { if (c == QUIT) notifyDestroyed(); if (c == CHANGE) banner.setString(userInput.getString()); } }
Interaction and Commands: Exercise 1/2 start on screen 1 with gauge to 0 It displays screen 2, where the text indicates the gauge value then press OK press “up” 4 times Change gauge value with keyboard and press OK It displays screen 1 with gauge value set to the specified value
Interaction and Commands: Exercise 2/2 Reorder magnets and find the missing ones if (c == cmdQuit) screen1.addCommand(cmdOK); bars = new Gauge("",true,4,0); if ((c == cmdOK) && (d == screen2)) { notifyDestroyed(); bars.setValue(Integer.parseInt(gaugeValue.getString())); private Form screen1, screen2; cmdOK = new Command("OK",Command.OK, 1); screen1.setCommandListener(this); protected void startApp() throws MIDletStateChangeException { public class CommandsExercise extends MIDlet implements CommandListener { private Gauge bars; screen1.addCommand(cmdQuit); private TextField gaugeValue; gaugeValue = new TextField("gauge value:","",2,TextField.NUMERIC); public CommandsExercise() { screen2.append(gaugeValue); cmdQuit = new Command("Quit",Command.EXIT, 2); screen1 = new Form("Screen 1"); screen2.addCommand(cmdOK); screen1.append(bars); screen2.setCommandListener(this); public void commandAction(Command c, Displayable d) { screen2 = new Form("Screen 2"); if ((c == cmdOK) && (d == screen1)) { private Command cmdOK; Display.getDisplay(this).setCurrent(screen2); gaugeValue.setString(String.valueOf(bars.getValue())); private Command cmdQuit; Display.getDisplay(this).setCurrent(screen1);
Compilation and Preverification • Compile: Hello\sources>javac -d ../classes -bootclasspath c:\WTK22\lib\midpapi20.jar;c:\WTK22\lib\cldcapi11.jar HelloWorld.java • Preverify (required to load a class in a CLDC-conformant VM): \Hello>c:\WTK22\bin\preverify -classpath c:\WTK22\lib\midpapi20.jar;c:\WTK22\lib\cldcapi11.jar classes (Result placed in output repository) • Test with emulator: \Hello\output>c:\WTK22\bin\emulator -classpath . HelloWorld
Packaging • Create a MANIFEST.MF file: MIDlet-1: Hello MIDlet,,HelloWorld MIDlet-Name: Hello MIDlet MIDlet-Vendor: Virginie Galtier MIDlet-Version: 1.0 MicroEdition-Configuration: CLDC-1.1 MicroEdition-Profile: MIDP-2.0 • \Hello\output>jar cvfm Hello.jar MANIFEST.MF HelloWorld.class • Create a JAD file HelloWorld.jad: MIDlet-1: Hello MIDlet,,HelloWorld MIDlet-Jar-Size: 1130 MIDlet-Jar-URL: Hello.jar MIDlet-Name: Hello MIDlet MIDlet-Vendor: Virginie Galtier MIDlet-Version: 1.0 MicroEdition-Configuration: CLDC-1.1 MicroEdition-Profile: MIDP-2.0 • Test with emulator: \Hello\output>c:\WTK22\bin\emulator -classpath . -Xdescriptor HelloWorld.jad
Accessing JAD and Manifest Information • getAppProperty(String key) method of the MIDlet class, key is the attribute name in the .jad and manifest files • Example: getAppProperty("MicroEdition-Configuration") returns “CLDC 1.1” • Useful for “about” information • Useful to pass parameter to the MIDlet: you can define your own attribute in the jad file. If you decide to change the values of thoses parameters, change only the .jad file, no need to modify the jar file!
Over The Air Deployment Test • Place jar and jad file on a web server • Install and execute: \Hello\output>c:\WTK22\bin\emulator -Xjam:transient=http://localhost/Hello/HelloWorld.jad • List installed MIDlet: \Hello\output>c:\WTK22\bin\emulator -Xjam:list Result: Running with storage root DefaultColorPhone [1] Name: Hello MIDlet Vendor: Virginie Galtier Version: 1.0 Storage name: #Virginie%0020#Galtier_#Hello%0020#M#I#Dlet_ Size: 2K Installed From: http://localhost/Hello/HelloWorld.jad MIDlets: Hello MIDlet • Remove installed MIDlet: \Hello\output>c:\WTK22\bin\emulator -Xjam:remove=1 • Install: \Hello\output>c:\WTK22\bin\emulator -Xjam:install=http://localhost/Hello/HelloWorld.jad • Execute: \Hello\output>c:\WTK22\bin\emulator -Xjam:run=1
Over The Air Deployment • create a hello.html file linking to the jad file: <html> <body> <a href="http://localhost/Hello/HelloWorld.jad">HelloWorld.jad</a> </body> </html> • launch the AMS: \Hello\output>c:\WTK22\bin\emulator -Xjam
Installing and Running MIDlet on PocketPC • install IBM Workplace Client Technology Micro Edition • click on Start / programs / MIDlet HQ • enter jad URL
Installing and Running MIDlets on PalmOS • download IBM WebSphere Micro Environment Toolkit for Palm OS at http://pluggedin.palmone.com/regac/pluggedin/auth/Java1.jsp • install IBMWME/prc/ARM/J9JavaVMMidpNG.prc on the Palm device • use MIDlet HQ as for the Pocket PC or • transform JAR or JAD files to .prc file with the IBM tools jartoprc or jad2prc • install the resulting .prc file on the Palm device
MIDlet Suite • Group of related MIDlets • Packaged and installed as a single entity, can be uninstalled only as a group • If device supports concurrent running of multiple MIDlets, all active MIDlets from a suite run in the same Java VM. • To package a MIDlet suite with the WTK: • Open the project, “settings” tab, “MIDlets” tab, provide the name and class name of each MIDlet in the suite • To package “by hand”: • Add a new line “MIDlet-x” with the name and class name of the MIDlet x in the suite in the JAD and MANIFEST files • Build the JAR including all class files
Image • supported format: PNG • example: Image i = Image.createImage("/rainbow.png"); ImageItem ii = new ImageItem("rainbow", i, ImageItem.LAYOUT_DEFAULT, null); mainScreen.append(ii); • path to the image file: • in WTK, “/” is the “res” subdirectory • when packaging by hand, add the image file to the JAR
MIDlet Icon • PNG image displayed in front of the name of a MIDlet of a suite • With WTK: • Place icon image in res directory • On the KToolBar: “Settings” tab, “MIDlets tab”, edit the MIDlet line and add the icon file name (in this context, “/” = res directory) • “By hand”: • Add the icon file to the JAR • Modify the MIDlet line in the MANIFEST and JAD files: • MIDlet-5: test two,/iconTwo.png,TestIconTwo (in this context, “/” = root of the JAR file)
Exercise • See Exercise 1 on the web site
Persistent Storage • Save information as collections of records • record = an array of bytes with associated integer identifier • record store = a collection of records identified by a name class javax.microedition.rms.RecordStore • record store names are shared by all MIDlets in a MIDlet suite: • allows information sharing • ! caution when accessing a RecordStore with multiple threads!
Record Stores • Create and/or open: • static RecordStore openRecordStore(String name, boolean create) • If no record with the given exists • If create is true: a new one is created • If create is false: a RecordStoreNotFoundException is thrown • Close: • closeRecordStore • If a record store is opened more than once by a MIDlet, it won’t be closed until each open instance is closed • Other operations: • static void deleteRecordStore(String name) • static String[] listRecordStores() • String getName() • long getLastModified() • int getVersion(): incremented each time a record is added, removed or modified • …
Records: creation • int addRecord(byte[] data, int offset, int size) • Returns identifier • Identifiers start at 1, increased by one at each record creation, identifiers of removed records are not reused • Record contains range of bytes from data[offset] to data[offset + size – 1] • Simple way to create a record from class instance fields: ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(baos); dos.writeUTF(secretary.name); dos.writeInt(secretary.age); dos.close(); byte[] data = baos.toByteArray(); int id = recordStrore.addRecord(data,0,data.length);
Records: other operations • Retrieve information from a record store: byte[] data = recordStore.getRecord(recordId); ByteArrayInputStream bais = new ByteArrayInputStream(); DataInputStream dis = new DataInputStream(bais); Person secretary = new Person(); secretary.name = dis.readUTF(); secretary.age = dis.readInt(); dis.close(); • Update: void setRecord(int recordId, byte[] data, int offset, int size) • Remove: void deleteRecord(int recordId)
Record Store events • Changes to the content of a record store are reported as events to objects that implement the RecordListener interface and register with the RecordStore using the addRecordListener() method. • RecordListener interface: • void recordAdded(RecordStore recordStore, int recordId) • void recordChanged(RecordStore recordStore, int recordId) • void recordDeleted(RecordStore recordStore, int recordId)
Record Enumerations for (int i = 1; i < store.getNextRecordID(); i++) { try { byte[] data = store.getRecord(i); display(data); } catch (InvalidRecordIDException irie) {} } inefficient • The list of active record identifiers can quickly become sparse • Use RecordEnumeration: a list of active records RecordEnumeration enum = recordStore.enumerateRecords(null, null, false); while (enum.hasNextElement()) { int id = enum.nextRecordId(); // display } False: static snapshot of the records True: keep the enumeration updated when record store is modified
Record Enumerations • store.getRecord(enum.nextRecordId) ↔ enum.nextRecord() • Convenient for read access to the records • Not suitable to modify the data • Forwards: • hasNextElement(), nextRecordId(), nextRecord() • Backwards: • hasPreviousElement(), previousRecordId, previousRecord() • reset(): restart iteration from the beginning • When finish, release resources used by a RecordEnumeration with destroy()
Record Filters • RecordEnumeration enum = recordStore.enumerateRecords(filter, null, false); • Contains only the records fulfilling a criterion verified by the filter • A filter = object implementing the RecordFilter interface: public boolean matches(byte[] data)
Record Comparators • Impose an order on the records in a RecordEnumeration by supplying an object implementing the RecordComparator interface: public int compare(byte[] dataA, byte[] dataB) • Return value: • RecordComparator.EQUIVALENT • RecordComparator.PRECEDES: “dataA” then “dataB” • RecordComparator.FOLLOWS: “dataB” then “dataA”
Exercise • See Exercise 2 on the web site
Networking no java.net package in the CLDC network functionalities are in Generic Connection Framework (GCF) implemented in javax.microedition.io: • class Connector • interface Connection and its sub-interfaces
Connector • Provides static methods to open a Connection: public static Connection open(String name, int mode, boolean timeout) • name: general form: protocol:address;parameters Examples: • socket://localhost:1122 • http://www.yahoo.com/index.html • comm:0;baudrate=1600 • mode (optional): Connector.READ or WRITE or READ_WRITE • timeout (optional): indicates that application code can make use of timeouts on read or write operations (if supported by implementation)
Connection receive and send Datagram Datagram d = myDatagramConnection.newMessage(messageBytes, length) delivery and duplicate protection are not guaranteed: low overhead but not reliable waits for connection establishment requests: public StreamConnection acceptAndOpen() OutputConnection InputConnection StreamConnectionNotifier file:// • provide: • XputStream to work with bytes • DataXputStream to work with java data type • (X=in or out) ServerSocketConnection socket://:port reliable streams DatagramConnection inbound reliable streams logical serial port StreamConnection datagram://[host]:[port] socket:// CommConnection comm:portID[;option;..;option] UDPDatagramConnection ContentConnection SocketConnection envisages the exchange of information with defined message boundaries socket://host:port HttpConnection CLDC 1.0 http:// SecureConnection required for all MIDP devices ssl://host:port MIDP 1.0 HttpsConnection required for all MIDP 2.0 devices MIDP 2.0 https://
Client Socket Example 1/4 import javax.microedition.midlet.*; import javax.microedition.lcdui.*; import javax.microedition.io.*; import java.io.*; public class WebClientSocket extends MIDlet { private Form welcomeScreen; public WebClientSocket() { welcomeScreen = new Form("Client socket example"); StreamConnection socket = null; OutputStream os = null; InputStream is = null; • objective: open a socket connection to a web server and read some data from it
Client Socket Example 2/4 try { socket = (StreamConnection)Connector.open( "socket://www.google.com:80",Connector.READ_WRITE); // send a message to the server String request = "GET / HTTP/1.0\n\n"; os = socket.openOutputStream(); os.write(request.getBytes()); os.close();
Client Socket Example 3/4 // read the server's reply, up to a maximum of 128 bytes is = socket.openInputStream(); byte[] buf = new byte[128]; int total = 0; while (total < 128) { int count = is.read(buf, total, 128-total); if (count < 0) break; total += count; } is.close(); String reply = new String(buf, 0, total); socket.close(); // display reply StringItem web = new StringItem("web",reply); welcomeScreen.append(web); } catch (Exception e) { System.err.println(e);
Client Socket Example 4/4 free up resources even if error occurs (helping the garbage collector is specially important on devices with low capabilities) } finally { if (socket != null) socket = null; if (is != null) is = null; if (os != null) os = null; } } protected void startApp() throws MIDletStateChangeException { Display.getDisplay(this).setCurrent(welcomeScreen); } protected void pauseApp() {} protected void destroyApp(boolean arg0) {} }
Exercise • See Exercise 3 on the web site
Security • (with presentation extracts from)