Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
MIDlet Development Virginie . Galtier @int-evry.fr March 2005 Overview • • 1. 2. 3. 4. 5. • • • 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 CLDC CDC no interface java.lang.Comparable no package java.net no package java.awt no class java.math.BigDecimal J2ME Profiles Personal Personal basis RMI CDC CLDC MID 1.0 & 2.0 PDA IM Digital Set Top Box Foundation 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 loaded by device launched for the first time resumeRequest() end requested by MIDlet destroyApp(true) new constructor() resumed by AMS startApp() Active resume requested by MIDlet paused by MIDlet paused by AMS notifyPaused() Paused pauseApp() startApp throws MIDletStateChangeException destroyApp(arg) arg=true? yes no throws MIDletStateChangeException ? Destroyed notifyDestroyed() Terminated no yes 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 1. Download and install the J2ME Wireless Toolkit 2.2: http://java.sun.com/products/j2mewtoolkit/download-2_2.html 2. Launch the KToolbar 3. Create a new project: – Project name: MasterOfScience – MIDlet class name: HelloWorld – Keep the default configuration and finish project creation HelloWorld MIDlet 2/3: code 4. In yourPath\WTK22\apps\MasterOfScience\src, create HelloWorld.java: 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) {} } HelloWorld MIDlet 3/3: execution 5. In the WTK, press the Run button 6. 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 javax.microedition.lcdui setTitle setTicker ... Screen TextBox Canvas List Form Alert javax.microedition. lcdui.game GameCanvas append Image Item String ... ChoiceGroup TextField StringItem DateField Gauge User Interface: Exercise 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 private private private private Form welcomeScreen; TextField userInput; Ticker banner; final Command QUIT = new Command("Exit",Command.EXIT, 2); 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 press “up” 4 times then press OK 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 TextField gaugeValue; screen1.addCommand(cmdQuit); private Gauge bars; 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"); screen1.append(bars); screen2.addCommand(cmdOK); screen2.setCommandListener(this); public void commandAction(Command c, Displayable d) { screen2 = new Form("Screen 2"); private Command cmdOK; if ((c == cmdOK) && (d == screen1)) { Display.getDisplay(this).setCurrent(screen2); gaugeValue.setString(String.valueOf(bars.getValue())); Display.getDisplay(this).setCurrent(screen1); private Command cmdQuit; Compilation and Preverification • Compile: Hello\sources>javac -d ../classes -bootclasspath c:\WTK22\lib\midpapi20.jar;c:\WTK22\lib\cldcapi 11.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\cldcapi 11.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 1. install IBM Workplace Client Technology Micro Edition 2. click on Start / programs / MIDlet HQ 3. enter jad URL Installing and Running MIDlets on PalmOS 1. download IBM WebSphere Micro Environment Toolkit for Palm OS at http://pluggedin.palmone.com/regac/pluggedin/auth/Java1.jsp 2. install IBMWME/prc/ARM/J9JavaVMMidpNG.prc on the Palm device 3. use MIDlet HQ as for the Pocket PC or 3. transform JAR or JAD files to .prc file with the IBM tools jartoprc or jad2prc 4. 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 • The list of active record identifiers can quickly become sparse for (int i = 1; i < store.getNextRecordID(); i++) { try { byte[] data = store.getRecord(i); display(data); } catch (InvalidRecordIDException irie) {} } inefficient • 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) waits for connection establishment requests: public StreamConnection acceptAndOpen() delivery and duplicate protection are not guaranteed: low overhead but not reliable OutputConnection InputConnection file:// StreamConnectionNotifier provide: •XputStream to work with bytes •DataXputStream to work with java data type (X=in or out) ServerSocketConnection socket://:port DatagramConnection reliable streams datagram://[host]:[port] StreamConnection inbound reliable streams logical serial port socket:// CommConnection comm:portID[;option;..;option] UDPDatagramConnection SocketConnection ContentConnection socket://host:port HttpConnection CLDC 1.0 SecureConnection MIDP 1.0 ssl://host:port MIDP 2.0 envisages the exchange of information with defined message boundaries http:// required for all MIDP devices HttpsConnection https:// required for all MIDP 2.0 devices Client Socket Example 1/4 • objective: open a socket connection to a web server and read some data from it import import import import javax.microedition.midlet.*; javax.microedition.lcdui.*; javax.microedition.io.*; 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; 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 } finally { if (socket != null) socket = null; if (is != null) is = null; if (os != null) os = null; } free up resources even if error occurs (helping the garbage collector is specially important on devices with low capabilities) } 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) HTTPS and certificates 1. download the code for the InternetBrowser MIDlet and create a MIDlet suite containing only that MIDlet 2. run the MIDlet and type the URL http://www.google.com 3. run again the MIDlet and type the URL https://www.verisign.com • problem: javax.microedition.pki.CertificateException: “Not a CA” or “Root CA’s public key is expired” • explanation: Public Key Certificates are needed in order to use HTTPS, but no certificates are present in the keystore when the WTK is installed, or the certificates have expired… • to install certificates: – – with the WTK: File / Utilities / Manage Certificates (import J2SE certificates; or open the web page with internet explorer, display the certificate and use the “save to a file” option, then import certificate from that file) by hand: use the MEKeyTool command Sign Exercise 1. using the WTK, create JAR and JAD files for the HelloWorld MIDlet (Project / Package / Create Package) 2. copy the files on a web server and create a web page pointing towards them 3. launch OTA provisioning on the emulator: c:\WTK22\bin\emulator -Xjam 4. launch installation indicating the address of the web page MIDlet appears “untrusted” 5. sign the MIDlet (Project / Sign / Sign MIDlet suite…) 6. copy the new JAR and JAD on the web site and launch OTA the MIDlet doesn’t appear “instrusted” anymore Other Security Issues • Sending user’s money without their permission (downloading information, sending SMS or MMS…) • Disclosing user’s private information (address book…) • Denial of service and other malicious code Security Domains • Assigning a security domain to the certificate, designates the level of trust the certificate holder has to access protected APIs and the level of access to those APIs. • Untrusted: origin and integrity of the JAR file cannot be trusted by the device (for example, unsigned MIDlet suites) • Trusted: JAR file signed with a certificate chain that the device can verify • Minimum: all permissions to protected APIs are denied, including access to push functionality and network protocols • Maximum: Same as trusted; all permissions to access protected APIs for push functionality and network protocols are allowed Optimizations for Execution Speed and Memory Consumption • Obfuscator – Not much memory + limited network connection the smaller the jar file, the better obfuscators appreciated! (“Proguard” is included in the WTK) • Free unused variables and resources – close connections – null references • Loop condition checking – move constant expressions out of the loop – prefer local variables to instance or class variables • • • • Avoid recursion Use array instead of Vector Use record stores instead of a Vector on the heap memory Write a suite of small MIDlets rather than a single multi-purpose MIDlet References • Books at INT library: – J2ME in a Nutshell – J2ME, Applications Java pour terminaux mobiles – Java 2 micro edition application development (also on Safari) • On line: – Sun J2ME web site (http://java.sun.com/j2me/): toolkit, specifications, code examples, links to developer sites… – J2ME FAQ: http://bellsouthpwp.net/m/c/mcpierce/j2mefaq.html – Benhui.net (http://benhui.net): J2ME web sites list, MIDP 2.0 phones, J2ME and Bluetooth… – More: google it in! Quizz • from the Sun web site Quizz – Question 1 A midlet is a Java class that extends the abstract class: A. javax.microedition.MIDlet B. javax.microedition.midlet C. javax.microedition.midlet.MIDlet D. javax.microedition.midlet.midlet Quizz – Question 2 Which one of the following methods of the MIDlet abstract class must be implemented by a midlet: A. initApp, startApp B. startApp, destroyApp C. startApp, pauseApp, destroyApp D. initApp, startApp, pauseApp, destroyApp Quizz – Question 3 After compiling your midlet, you must process it with a command to ensure that it is valid before use by the Kilo virtual machine (KVM). What is the name of that command? A. midp B. javac-bootclasspath C. preverify D. jar Quizz – Question 4 A Java Application Descriptor (JAD) file is a text file that is similar to a manifest, except that it is not packaged in a JAR file. Some of the shared attributes that must have identical values are: MIDlet-Name, MIDlet-Version, and MIDlet-Vendor. If any shared attributes are different, then: A. The ones in the descriptor override those in the manifest B. The ones in the manifest override those in the descriptor Quizz – Question 5 When downloading application descriptors (.jad files) from a web server, the web server must return a MIME type of: A. text/vnd.sun.j2me.midp B. text/vnd.sun.j2me.jad C. text/vnd.sun.j2me.app-descriptor D. text/vnd.sun.j2me.midapp Quizz – Question 6 A midlet suite is a set of midlets that are packed together into a single JAR file. Midlets within the same suite: A. Can share the classes and resources contained in the JAR file B. Cannot share the classes and resources contained in the JAR file Quizz – Question 7 What is the difference between a configuration (such as the CLDC) and a profile (such as the MIDP)? A. a configuration defines the set of class libraries available for a particular domain of devices, and a profile provides I/O and network APIs. B. a configuration defines a vertical set of classes that a profile can use. C. a configuration defines a minimum set of class libraries for a wide range of devices, and a profile defines the set of APIs available for a particular family of devices. D. a profile is the foundation for a configuration. Quizz – Question 8 All MIDP implementations are required to support what image format? A. GIF B. JPG C. BMP D. PNG Quizz – Question 9 If a midlet needs to receive high-level events from the implementation, which interface should it implement? A. ActionListener B. CommandListener C. Windows Listener D. ChoiceListener Quizz – Question 10 All MIDP GUI classes are contained in what package? A. javax.microedition.gui B. javax.microedition.lcd C. javax.microedition.lcdui D. javax.microedition.display Quizz – Question 11 Which class would you use to write applications to access low-level input events or to issue graphics calls for drawing to the display? A. Display B. Command C. Screen D. Canvas Quizz – Question 12 What is the correct syntax, using the Generic Connection framework, to open an HTTP connection: A. B. C. D. Connection c = Connection.open("http://developer.java.sun.com"); Connector c = Connector.open("http://developer.java.sun.com"); Connection c = Connector.open("http://developer.java.sun.com"); Connector c = Connection.open("http://developer.java.sun.com"); Quizz – Question 13 The MIDP provides a mechanism for MIDlets to persistently store data in a platform-dependent file and retrieve it later. This mechanism is: A. Object Serialization B. JDBC C. RMS D. ODBC Quizz – Question 14 Which of the following statements is true about J2ME? A. J2ME is a single specification B. J2ME is a family of related specifications C. J2ME is a platform for creating applications for the wellestablished desktop market D. J2ME is a platform for creating server-side enterprise applications Quizz – Question 15 A J2ME-enabled device: A. Can support only a single profile B. Can support multiple profiles