A Beginning Programmer's Guide to J2ME
Monday, 5 November 2012
Friday, 27 July 2012
Friday, 28 October 2011
Diffrence between CLDC and MIDP
Connected Limited Device Configuration
Typical requirements
CLDC is designed for devices that have limited processing power, memory, and graphical capability. Devices typically have the following features:- 16-bit or 32-bit CPU with a clock speed of 16 MHz or higher
- At least 160 KB ROM allocated for the CLDC libraries and virtual machine
- At least 192 KB total RAM available to the Java platform
- Low power consumption, often operating on battery power
- Connectivity to some kind of network, often with a wireless, intermittent connection and limited bandwidth
Noteworthy limitations
Compared to the Java SE environment, several APIs are absent entirely, and some APIs are altered such that code requires explicit changes to support CLDC. In particular, certain changes aren't just the absence of classes or interfaces, but actually change the signatures of existing classes in the base class library. An example of this is the absence of theSerializable
interface, which does not appear in the base class library due to restrictions on reflection usage. All java.lang.*
classes which normally implement Serializable
do not, therefore, implement this tagging interface.Other examples of limitations depend on the version being used, as some features were re-introduced with version 1.1 of CLDC.
CLDC 1.0 and 1.1
- The
Serializable
interface is not supported. - Parts of the reflection capabilities of the Java standard edition:
- The
java.lang.reflect
package and any of its classes not supported. - Methods on
java.lang.Class
which obtain Constructors or Methods or Fields.
- The
- No finalization. CLDC does not include the
Object.finalize()
method. - Limited error handling. Non-runtime errors are handled by terminating the application or resetting the device.
- No Java Native Interface (JNI)
- No user-defined class loaders
- No thread groups or daemon threads.
Profiles
A profile is a set of APIs that support devices with different capabilities and resources within the CLDC framework to provide a complete Java application environment. There are specific profiles for devices ranging from vending machines to set-top boxes, with the mobile phone profile MIDP being the most prevalent.Mobile Information Device Profile
The Mobile Information Device Profile is a profile designed for cell phones. There are two versions of MIDP available, specified in JSR 37 (MIDP 1.0) and JSR 118 (MIDP 2.0). Both versions provide an LCD oriented GUI API, with MIDP 2.0 including a basic 2D gaming API. Applications written to use this profile are called MIDlets. Many cell phones come with a MIDP implementation, and it is a popular platform for downloadable cell phone games.Information Module Profile
The Information Module Profile is specified in JSR 195and is designed for vending machines, network cards, routers, telephone boxes and other systems with either simple or no display and some form of limited two way network access. Only APIs for application creation, storage, and network access are defined. These are a subset of the javax.microedition.io, rms and midlet packages in MIDP. Siemens mobile and Nokia put forward this specification to the JCP.DoJa Profile
The DoJa profile was designed for DoCoMo's i-mode mobile phone by NTT DoCoMo.Digital Set Top Box Profile
The Digital Set Top Box profile, specified in JSR 242, is designed for the cable market. Also referred to as OnRamp, this profile is based on a subset of the Open Cable Application Platform (OCAP), which defines a set of APIs for the development of applications for set-top boxes and similar devices. The profile consists of subsets from the CDC Personal Basis Profile including support for AWT, Xlet, file access, and network APIs, as well as several media-related interfaces from OCAP. The whole profile encompassed 31 Java packages and approximately 1500 APIs.Optional Packages
The PDA Optional Packages are specified in JSR-75 and are designed for PDAs such as Palm or Windows CE devices. The specification defines two independent packages that represent important features found on many PDAs and other mobile devices. These packages are:- Personal Information Management (PIM) which gives devices access to personal information management data contained in address books, calendars, and to-do lists.
- FileConnection (FC) which allows access to file systems and removable storage devices, such as external memory cards.
General APIs
java.io
- A streamlined version of the java.io package found in the standard edition for doing Input/Output operations.
java.lang
- Contains classes that are essential to the Java language. This package contains standard java types like Integers and Strings as well as basic exceptions, math functions, system functions, threading and security functions.
java.util
- A streamlined version of the
java.util
collection library. This package contains the collection classes like Vector and Hashtable. It also contains calendar and date class.
Tuesday, 25 October 2011
Java Collections Framework
Java Collections Framework
A Collections Framework mainly contains the following 3 parts
A Collections Framework is defined by a set of interfaces, concrete class implementations for most of the interfaces and a set of standard utility methods and algorithms. In addition, the framework also provides several abstract implementations, which are designed to make it easier for you to create new and different implementations for handling collections of data.Core Collection Interfaces
The core interfaces that define common functionality and allow collections to be manipulated independent of their implementation.The 6 core Interfaces used in the Collection framework are:
- Collection
- Set
- List
- Iterator (Not a part of the Collections Framework)
- SortedSet
- Map
- SortedMap
Collection Interface
Map Interface
Concrete Classes
The concrete classes that are specific implementations of the core interfaces, providing data structures that a java program can use.Standard utility methods and algorithms
Standard utility methods and algorithms
that can be used to perform various operations on collections, such as sorting, searching or creating customized collections.
How are Collections Used
- The collections stores object references, rather than objects themselves. Hence primitive values cannot be stored in a collection directly. They need to be encapsulated (using wrapper classes) into an Object prior to storing them into a Collection (such as HashSet, HashMap etc).
- The references are always stored as type Object. Thus, when you retrieve an element from a collection, you get an Object rather then the actual type of the collection stored in the database. Hence we need to downcast it to the Actual Type while retrieving an element from a collection.
- One of the capabilities of the Collection Framework is to create a new Collection object and populate it with the contents of an existing Collection object of a same or different actual type.
Below is an example program showing the storing and retrieving of a few Collection Types
import java.util.*; public class CollectionsDemo { public static void main(String[] args) { List a1 = new ArrayList(); a1.add(”Beginner”); a1.add(”Java”); a1.add(”tutorial”); System.out.println(” ArrayList Elements”); System.out.print(”\t” + a1); List l1 = new LinkedList(); l1.add(”Beginner”); l1.add(”Java”); l1.add(”tutorial”); System.out.println(); System.out.println(” LinkedList Elements”); System.out.print(”\t” + l1); Set s1 = new HashSet(); // or new TreeSet() will order the elements; s1.add(”Beginner”); s1.add(”Java”); s1.add(”Java”); s1.add(”tutorial”); System.out.println(); System.out.println(” Set Elements”); System.out.print(”\t” + s1); Map m1 = new HashMap(); // or new TreeMap() will order based on keys m1.put(”Windows”, “98″); m1.put(”Win”, “XP”); m1.put(”Beginner”, “Java”); m1.put(”Tutorial”, “Site”); System.out.println(); System.out.println(” Map Elements”); System.out.print(”\t” + m1); } } |
Output
ArrayList Elements[Beginner, Java, tutorial]
LinkedList Elements
[Beginner, Java, tutorial]
Set Elements
[tutorial, Beginner, Java]
Map Elements
{Tutorial=Site, Windows=98, Win=XP, Beginner=Java}
Monday, 24 October 2011
J2ME: Using TextFields and Alerts(User Registration)
Introduction
User Input and Notification Messages are one of the most important things in Interactive Applications, and mobile applications is one type of these applications. In this article, you will learn how to use the text field and alert UI components to create the "User Registration" J2ME application. The text field is an editable text component that is used to accept input from user. On the other hand, the alert is a displayable screen that is used to present various kinds of information to the user for a certain period of time before proceeding to the next displayable.The "User Registration" application is a simple application that asks for user information and displays an alert notification about registration completion. In future articles, you will learn how to validate user input and save this information for later use. The application consists of a main form that contains five text field items. These text field items allow the user to enter the required registration fields which are email, password, name, mobile and website. The form also has two commands, OK and Exit commands. OK command displays a registration complete notification Alert for five seconds, it also uses the entered name to thank the user.
Following is a screenshot of how the final application will look like:
Figure: Application screenshot
Creating Form Components
To create your form components, you will start by creating the text field items. The text field item can be created using an instance ofTextField
class where you need to specify four values:- The label that is a
string
value used to describe the accepted user input. - The text which is the initial content of the field and can be
null
for empty content. - The maximum size that specifies the maximum allowed number of characters the user can enter in the field.
- And the input constraints which are used to restrict the user's input by a variety of ways. For example, if you pass the
NUMERIC
constraint, the application will allow the user to enter only numeric characters.
TextField
data members for email, password, name, mobile and website fields. public class UserRegistration extends MIDlet implements CommandListener {
//...
private TextField emailTxt;
private TextField passwordTxt;
private TextField nameTxt;
private TextField mobileTxt;
private TextField urlTxt;
private Command okCmd;
private Command exitCmd;
//...
}
The next step is to create an instance of TextField
class for each TextField
data member and pass the four parameters (label, text, maximum size and input constraints). The first text field item uses EMAILADDR
constraint to allow the user to enter email address and the second item uses PASSWORD
which indicates that the text entered is confidential data and should not be displayed. The name text field item uses ANY
constraint to indicate that this field has no constraints, the mobile item uses PHONENUMBER
constraint which allow only numeric characters in addition to some characters like + and finally the website item uses URL
to allow the user to enter a website address.public UserRegistration() {
//...
emailTxt = new TextField("Email:", "", 100, TextField.EMAILADDR);
passwordTxt = new TextField("Password:", "", 16, TextField.PASSWORD);
nameTxt = new TextField("Name:", "", 50, TextField.ANY);
mobileTxt = new TextField("Mobile:", "", 15, TextField.PHONENUMBER);
urlTxt = new TextField("Website:", "", 100, TextField.URL);
}
Create Form and Add Components
As we discussed in the last article, there are two ways for adding components to a form, the first using theForm
class append()
method which can be used in case of a small number of components or when you need to add components after form creation. The second way is using the constructor by passing an array of type Item
class that contains all the items to be added.As you have five items to be added to the form, you will use the constructor by creating an array of type
Item
that contains all five items and then pass it to the constructor.public UserRegistration() {
//...
registrationFrm = new Form("User Registration", new Item[]
{emailTxt, passwordTxt, nameTxt, mobileTxt, urlTxt});
}
Create the Alert
One of the methods used to alert the user in J2ME is theAlert
displayable, which can be displayed after specifying four values:- The title that must be passed when creating the alert as a
string
. - The text which is the message that is displayed to the user.
- The image that provides an indication of the alert type.
- The type that provides an indication of the nature of alert. It can be one of five values:
CONFIRMATION
(a hint to confirm user actions)ERROR
(a hint to alert the user to an erroneous operation)INFO
(provide a information to the user)WARNING
(a hint to warn the user of a potentially dangerous operation)ALARM
(a hint to alert the user to an event for which the user has previously requested to be notified)
Alert
to your MIDlet
class.public class UserRegistration extends MIDlet implements CommandListener {
//...
private Alert messageAlert;
//...
}
Then, you will create an instance of the Alert
class and passing the title of your alert to that class, which will be "Registration Complete".public UserRegistration() {
//...
messageAlert = new Alert("Registration Complete");
}
As the alert will be displayed for a certain period of time, you will now specify this period in milliseconds using the setTimeout()
method and the type of alert which will be CONFIRMATION
.public UserRegistration() {
//...
messageAlert.setTimeout(5000);
messageAlert.setType(AlertType.CONFIRMATION);
}
Read TextField Contents
You can read the text field content entered by the user at any time using theTextField
getString()
method and you can also change it using setString()
method, but first you will handle the command action for the OK and Exit commands.public void commandAction(Command c, Displayable d) {
if (d == registrationFrm) {
if (c == okCmd) {
//Show Message command action
}
//Exit command action
//...
}
}
The next step is you will build a simple thanks message after reading the name from the name text field using the getString()
method.if (c == okCmd) {
String messageContent;
messageContent = "Thanks " + nameTxt.getString() + "\nRegistration Complete.";
}
Display Alert and The Contents
Each displayable can be displayed using theDisplay
object setCurrent()
method, but this time you need to pass the next displayable parameter to this method. As the alert will timeout after a period of the time, the next displayable will be displayed directly after that.The final step will be to set the alert text using the
setString()
and display it using the setCurrent()
method with the registration form as the next displayable.if (c == okCmd) {
//...
messageAlert.setString(messageContent);
display.setCurrent(messageAlert, registrationFrm);
}
Conclusion
In this article, you learn how to user J2ME to accept input from the user using the text field item, and how J2ME uses different input constraints as a simple data validation. You also learned how to user the notification alert to alert the user about specific events in your application like alarms, errors, warnings, and other types of messages for a specific period of time.Following is the complete application code, downloadable .java file and the final application result as a .jar file.
Do not forget to share the article and leave a comment if you have any questions.
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
public class UserRegistrationMIDlet extends MIDlet implements CommandListener {
private Display display;
private Form registrationFrm;
private Alert messageAlert;
private TextField emailTxt;
private TextField passwordTxt;
private TextField nameTxt;
private TextField mobileTxt;
private TextField urlTxt;
private Command okCmd;
private Command exitCmd;
public UserRegistrationMIDlet() {
emailTxt = new TextField("Email:", "", 100, TextField.EMAILADDR);
passwordTxt = new TextField("Password:", "", 16, TextField.PASSWORD);
nameTxt = new TextField("Name:", "", 50, TextField.ANY);
mobileTxt = new TextField("Mobile:", "", 15, TextField.PHONENUMBER);
urlTxt = new TextField("Website:", "", 100, TextField.URL);
registrationFrm = new Form("User Registration",
new Item[] {emailTxt, passwordTxt, nameTxt, mobileTxt, urlTxt});
messageAlert = new Alert("Registration Complete");
messageAlert.setTimeout(5000);
messageAlert.setType(AlertType.CONFIRMATION);
okCmd = new Command("OK", Command.OK, 0);
exitCmd = new Command("Exit", Command.EXIT, 1);
registrationFrm.addCommand(okCmd);
registrationFrm.addCommand(exitCmd);
registrationFrm.setCommandListener(this);
}
protected void startApp() {
display = Display.getDisplay(this);
display.setCurrent(registrationFrm);
}
public void commandAction(Command c, Displayable d) {
if (d == registrationFrm) {
if (c == okCmd) {
String messageContent;
messageContent = "Thanks " + nameTxt.getString() +
"\nRegistration Complete.";
messageAlert.setString(messageContent);
display.setCurrent(messageAlert, registrationFrm);
} else if (c == exitCmd) {
notifyDestroyed();
}
}
}
protected void pauseApp() {
}
protected void destroyApp(boolean unconditional) {
}
}
Friday, 21 October 2011
Persistent Storage in J2ME(Record Management System)
The Mobile Information Device Profile provides a mechanism for MIDlets to persistently store data and later retrieve it. This persistent storage mechanism is modeled after a simple record oriented database and is called the Record Management System.
Record stores are created in platform-dependent locations, which are not exposed to MIDlets. The naming space for record stores is controlled at the MIDlet suite granularity. MIDlets within a MIDlet suite are allowed to create multiple record stores, as long as they are each given different names. When a MIDlet suite is removed from a platform, all record stores associated with its MIDlets MUST also be removed. MIDlets within a MIDlet suite can access one another's record stores directly. New APIs in MIDP 2.0 allow for the explicit sharing of record stores if the MIDlet creating the RecordStore chooses to give such permission.
Sharing is accomplished through the ability to name a RecordStore in another MIDlet suite and by defining the accessibilty rules related to the Authentication of the two MIDlet suites.
RecordStores are uniquely named using the unique name of the MIDlet suite plus the name of the RecordStore. MIDlet suites are identified by the MIDlet-Vendor and MIDlet-Name attributes from the application descriptor.
Access controls are defined when RecordStores to be shared are created. Access controls are enforced when RecordStores are opened. The access modes allow private use or shareable with any other MIDlet suite.
Record store names are case sensitive and may consist of any combination of up to 32 Unicode characters. Record store names MUST be unique within the scope of a given MIDlet suite. In other words, MIDlets within a MIDlet suite are not allowed to create more than one record store with the same name; however, a MIDlet in one MIDlet suite is allowed to have a record store with the same name as a MIDlet in another MIDlet suite. In that case, the record stores are still distinct and separate.
No locking operations are provided in this API. Record store implementations ensure that all individual record store operations are atomic, synchronous, and serialized so that no corruption occurs with multiple accesses. However, if a MIDlet uses multiple threads to access a record store, it is the MIDlet's responsibility to coordinate this access, or unintended consequences may result. For example, if two threads in a MIDlet both call
This record store API uses long integers for time/date stamps, in the format used by
Records are uniquely identified within a given record store by their
Persistent Storage
The MIDP provides a mechanism for MIDlets to persistently store data and retrieve it later. This persistent storage mechanism, called the Record Management System (RMS), is modeled after a simple record-oriented database.Record Store
A record store consists of a collection of records that will remain persistent across multiple invocations of a MIDlet. The platform is responsible for making its best effort to maintain the integrity of the MIDlet's record stores throughout the normal use of the platform, including reboots, battery changes, etc.Record stores are created in platform-dependent locations, which are not exposed to MIDlets. The naming space for record stores is controlled at the MIDlet suite granularity. MIDlets within a MIDlet suite are allowed to create multiple record stores, as long as they are each given different names. When a MIDlet suite is removed from a platform, all record stores associated with its MIDlets MUST also be removed. MIDlets within a MIDlet suite can access one another's record stores directly. New APIs in MIDP 2.0 allow for the explicit sharing of record stores if the MIDlet creating the RecordStore chooses to give such permission.
Sharing is accomplished through the ability to name a RecordStore in another MIDlet suite and by defining the accessibilty rules related to the Authentication of the two MIDlet suites.
RecordStores are uniquely named using the unique name of the MIDlet suite plus the name of the RecordStore. MIDlet suites are identified by the MIDlet-Vendor and MIDlet-Name attributes from the application descriptor.
Access controls are defined when RecordStores to be shared are created. Access controls are enforced when RecordStores are opened. The access modes allow private use or shareable with any other MIDlet suite.
Record store names are case sensitive and may consist of any combination of up to 32 Unicode characters. Record store names MUST be unique within the scope of a given MIDlet suite. In other words, MIDlets within a MIDlet suite are not allowed to create more than one record store with the same name; however, a MIDlet in one MIDlet suite is allowed to have a record store with the same name as a MIDlet in another MIDlet suite. In that case, the record stores are still distinct and separate.
No locking operations are provided in this API. Record store implementations ensure that all individual record store operations are atomic, synchronous, and serialized so that no corruption occurs with multiple accesses. However, if a MIDlet uses multiple threads to access a record store, it is the MIDlet's responsibility to coordinate this access, or unintended consequences may result. For example, if two threads in a MIDlet both call
RecordStore.setRecord()
concurrently on the same record, the record store will serialize these calls properly, and no database corruption will occur as a result. However, one of the writes will be subsequently overwritten by the other, which may cause problems within the MIDlet. Similarly, if a platform performs transparent synchronization of a record store or other access from below, it is the platform's responsibility to enforce exclusive access to the record store between the MIDlets and synchronization engine.This record store API uses long integers for time/date stamps, in the format used by
System.currentTimeMillis()
. The record store is time stamped with the last time it was modified. The record store also maintains a version, which is an integer that is incremented for each operation that modifies the contents of the record store. These are useful for synchronization engines as well as applications.Records
Records are arrays of bytes. Developers can useDataInputStream
and DataOutputStream
as well as ByteArrayInputStream
and ByteArrayOutputStream
to pack and unpack different data types into and out of the byte arrays. Records are uniquely identified within a given record store by their
recordId
, which is an integer value. This recordId
is used as the primary key for the records. The first record created in a record store will have recordId
equal to 1, and each subsequent recordId
will monotonically increase by one. For example, if two records are added to a record store, and the first has a recordId
of 'n', the next will have a recordId
of (n+1). MIDlets can create other indices by using the RecordEnumeration
class.Example:
The example uses the Record Management System to store and retrieve high scores for a game. In the example, high scores are stored in separate records, and sorted when necessary using a RecordEnumeration.import javax.microedition.rms.*; import java.io.DataOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.EOFException; /** * A class used for storing and showing game scores. */ public class RMSGameScores implements RecordFilter, RecordComparator { /* * The RecordStore used for storing the game scores. */ private RecordStore recordStore = null; /* * The player name to use when filtering. */ public static String playerNameFilter = null; /* * Part of the RecordFilter interface. */ public boolean matches(byte[] candidate) throws IllegalArgumentException { // If no filter set, nothing can match it. if (this.playerNameFilter == null) { return false; } ByteArrayInputStream bais = new ByteArrayInputStream(candidate); DataInputStream inputStream = new DataInputStream(bais); String name = null; try { int score = inputStream.readInt(); name = inputStream.readUTF(); } catch (EOFException eofe) { System.out.println(eofe); eofe.printStackTrace(); } catch (IOException eofe) { System.out.println(eofe); eofe.printStackTrace(); } return (this.playerNameFilter.equals(name)); } /* * Part of the RecordComparator interface. */ public int compare(byte[] rec1, byte[] rec2) { // Construct DataInputStreams for extracting the scores from // the records. ByteArrayInputStream bais1 = new ByteArrayInputStream(rec1); DataInputStream inputStream1 = new DataInputStream(bais1); ByteArrayInputStream bais2 = new ByteArrayInputStream(rec2); DataInputStream inputStream2 = new DataInputStream(bais2); int score1 = 0; int score2 = 0; try { // Extract the scores. score1 = inputStream1.readInt(); score2 = inputStream2.readInt(); } catch (EOFException eofe) { System.out.println(eofe); eofe.printStackTrace(); } catch (IOException eofe) { System.out.println(eofe); eofe.printStackTrace(); } // Sort by score if (score1 < score2) { return RecordComparator.PRECEDES; } else if (score1 > score2) { return RecordComparator.FOLLOWS; } else { return RecordComparator.EQUIVALENT; } } /** * The constructor opens the underlying record store, * creating it if necessary. */ public RMSGameScores() { // // Create a new record store for this example // try { recordStore = RecordStore.openRecordStore("scores", true); } catch (RecordStoreException rse) { System.out.println(rse); rse.printStackTrace(); } } /** * Add a new score to the storage. * * @param score the score to store. * @param playerName the name of the play achieving this score. */ public void addScore(int score, String playerName) { // // Each score is stored in a separate record, formatted with // the score, followed by the player name. // int recId; // returned by addRecord but not used ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream outputStream = new DataOutputStream(baos); try { // Push the score into a byte array. outputStream.writeInt(score); // Then push the player name. outputStream.writeUTF(playerName); } catch (IOException ioe) { System.out.println(ioe); ioe.printStackTrace(); } // Extract the byte array byte[] b = baos.toByteArray(); // Add it to the record store try { recId = recordStore.addRecord(b, 0, b.length); } catch (RecordStoreException rse) { System.out.println(rse); rse.printStackTrace(); } } /** * A helper method for the printScores methods. */ private void printScoresHelper(RecordEnumeration re) { try { while(re.hasNextElement()) { int id = re.nextRecordId(); ByteArrayInputStream bais = new ByteArrayInputStream(recordStore.getRecord(id)); DataInputStream inputStream = new DataInputStream(bais); try { int score = inputStream.readInt(); String playerName = inputStream.readUTF(); System.out.println(playerName + " = " + score); } catch (EOFException eofe) { System.out.println(eofe); eofe.printStackTrace(); } } } catch (RecordStoreException rse) { System.out.println(rse); rse.printStackTrace(); } catch (IOException ioe) { System.out.println(ioe); ioe.printStackTrace(); } } /** * This method prints all of the scores sorted by game score. */ public void printScores() { try { // Enumerate the records using the comparator implemented // above to sort by game score. RecordEnumeration re = recordStore.enumerateRecords(null, this, true); printScoresHelper(re); } catch (RecordStoreException rse) { System.out.println(rse); rse.printStackTrace(); } } /** * This method prints all of the scores for a given player, * sorted by game score. */ public void printScores(String playerName) { try { // Enumerate the records using the comparator and filter // implemented above to sort by game score. RecordEnumeration re = recordStore.enumerateRecords(this, this, true); printScoresHelper(re); } catch (RecordStoreException rse) { System.out.println(rse); rse.printStackTrace(); } } public static void main(String[] args) { RMSGameScores rmsgs = new RMSGameScores(); rmsgs.addScore(100, "Alice"); rmsgs.addScore(120, "Bill"); rmsgs.addScore(80, "Candice"); rmsgs.addScore(40, "Dean"); rmsgs.addScore(200, "Ethel"); rmsgs.addScore(110, "Farnsworth"); rmsgs.addScore(220, "Farnsworth"); System.out.println("All scores"); rmsgs.printScores(); System.out.println("Farnsworth's scores"); RMSGameScores.playerNameFilter = "Farnsworth"; rmsgs.printScores("Farnsworth"); } }
J2ME Programming (Part I)
J2ME Overview
J2ME (Java 2 Micro Edition) is a family of APIs that specify application platforms that are typically implemented by embedded or handheld devices. Handheld devices include PDAs (Personal Digital Assistants) and high-end mobile phones. These platforms are typically limited in memory capacity and I/O capabilities, although they do have the ability to transfer data over low bandwidth, intermittent wireless connections.
J2ME Stacks
CLDC Requirements:
monochrome 96x54 pixel display
2-way wireless network
input device (keypad, touch screen)
128 Kb for CLDC/MIDP classes + 32 Kb for KVM
CLDC Classes
Interfaces and exceptions not shown.
java.lang
Object
Boolean, Character
Integer, Long, Short, Byte
Class
Math
Runtime, System
String, StringBuffer
Thread
Throwable
Boolean, Character
Integer, Long, Short, Byte
Class
Math
Runtime, System
String, StringBuffer
Thread
Throwable
java.io
InputStream
ByteArrayInputStream
DataInputStream
OutputStream
ByteArrayOutputStream
DataOutputStream
PrintStream
Reader
InputStreamReader
Writer
OutputStreamWriter
ByteArrayInputStream
DataInputStream
OutputStream
ByteArrayOutputStream
DataOutputStream
PrintStream
Reader
InputStreamReader
Writer
OutputStreamWriter
java.util
Calendar
Date
HashTable
Random
Timer
TimerTask
TimeZone
Vector
Stack
Date
HashTable
Random
Timer
TimerTask
TimeZone
Vector
Stack
MIDP Packages
javax.microedition.io
javax.microedition.lcdui (user interface)
javax.microedition.lcdui.game
javax.microedition.media (media player)
javax.microedition.media.control
javax.microedition.midlet
javax.microedition.pki (certification)
javax.microedition.rms (persistence)
javax.microedition.lcdui (user interface)
javax.microedition.lcdui.game
javax.microedition.media (media player)
javax.microedition.media.control
javax.microedition.midlet
javax.microedition.pki (certification)
javax.microedition.rms (persistence)
J2ME SDKs
Motorola SDK
RIM SDK
Java SDK
Competing Technologies
WAP/WML
I-Mode
Windows CE
Quick Start: "Hello World"
We will be using the Sun/Java J2ME SDK. This SDK includes the J2ME Wireless Toolkit. Here's its GUI (called K Toolbar):
Creating a New Project and Midlet
Applications that run on a MIDP/CLDC platform are called midlets. By pressing the "New Project" button we create a new project named Examples containing a midlet defined in a file named HelloWorld.class:
The console window informs us that it has created a project directory named Examples containing src, res, and lib subdirectories:
Other subdirectories have been created as well. We must save our .java files in the Examples\src subdirectory.
HelloWorld.java
The file Examples\src\HelloWorld.java is created using an ordinary text editor. It begins by importing several J2ME packages:
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
import javax.microedition.midlet.*;
The declaration of the HelloWorld class follows:
public class HelloWorld extends MIDlet {
// Device's display window and main screen:
private Display theDisplay;
private Screen mainScreen;
public HelloWorld() {
theDisplay = Display.getDisplay(this);
mainScreen = new TextBox(
"Example 1", "Hello World", 50, TextField.ANY);
}
// Lifecycle methods:
protected void startApp() {
theDisplay.setCurrent(mainScreen);
}
protected void destroyApp(boolean unconditional) { }
protected void pauseApp() { }
}
// Device's display window and main screen:
private Display theDisplay;
private Screen mainScreen;
public HelloWorld() {
theDisplay = Display.getDisplay(this);
mainScreen = new TextBox(
"Example 1", "Hello World", 50, TextField.ANY);
}
// Lifecycle methods:
protected void startApp() {
theDisplay.setCurrent(mainScreen);
}
protected void destroyApp(boolean unconditional) { }
protected void pauseApp() { }
}
Notice that HelloWorld extends MIDlet. Two instance variables are declared and subsequently initialized by the constructor. theDisplay represents the display window of the PDA or mobile phone. mainScreen represents the screen that theDisplay is supposed to display. This is accomplished in startApp() method by the line:
theDisplay.setCurrent(mainScreen);
startApp() is one of three lifecycle methods every midlet must define. In most of our examples the other two have empty bodies. We can think of theDisplay as a mini web browser and mainWindow as a type of web page. In our example, mainPage is a text box titled "Example 1" and containing the text "Hello World".
Running HelloWorld
Pressing the Build button on the toolbar causes the Wireless Toolkit to compile all .java files in the current project's src subdirectory. The corresponding .class files are placed in the classes subdirectory. Next, the .class files are pre-verified. If a .class file passes various security checks, it is tagged as verified. All of the project's verified files are placed in an archive file called Examples.jad. This file is placed in the bin subdirectory.
Pressing the Run button on the toolbar causes the Wireless Toolkit to start a mobile phone simulator. The Examples application suite is "downloaded" to the "phone," which displays the suite's midlets in its window. Currently, HelloWorld is the only midlet. Launching this midlet causes the window to display the midlet's main page. Use the red "hang up" button to terminate the midlet.
J2ME APIs
HelloWorld extended the MIDlet class and contains references to a Display object (theDisplay) and a TextBox object (mainScreen). TextBox extends the Screen class, which is the base class for all high-level GUIs. Low-level displays, where the programmer must define his own graphics, extend the Canvas class.
Typically, a midlet creates a collection of displayable objects-- screens and/or canvases:
Displayable[] displayables = new Displayable[N];
for (int i = 0; i < N; i++) {
// create displayables[i]
}
for (int i = 0; i < N; i++) {
// create displayables[i]
}
The midlet responds to user input commands by selecting a displayable object from this collection, then asking the display to display it with the command:
theDisplay.setCurrent(displayables[next]);
Here's a more comprehensive class diagram showing the lcdui package:
First notice that in addition to text boxes, there are three other types of screens: alerts (a message dialog that flashes on the display), forms (a control panel containing control items such as labels, text fields, and buttons), and lists.
A displayable can fire and handle commands. A command is fired when the user presses keypad buttons when the displayable is the current window. When a command is fired, a command object is passed to the commandAction() method of each registered command listener. Typically, the midlet is the command listener.
Form items also fire commands. When an item command is fired, a command object is passed to the commandAction() method of each registered item command listener. Typically, the midlet or the parent form is the item command listener.
The J2ME Application Manager
The Application manager manages the lifecycles of midlets:
Commands and The Midlet as a Command Processor
To demonstrate commands and command processing, let's add a new midlet to the Examples project. Pressing the Settings button on the Wireless Toolkit toolbar displays the Settings dialog. Pressing the Add button at the bottom of this dialog allows us to add a new project called navigator contained in the CommandProcessor.class file:
Running the Command Processor
Here are some screen shots of the Navigator running on the simulator. Notice that the opening screen now shows the Examples suite contains two midlets. Selecting the navigator midlet causes the midlet's first screen to appear. The screen is titled "Screen 0" and displays the question "Shall we go to screen 1?". That's all Navigator allows users to do: to navigate from screen i to screen j.
Notice that the phone's soft buttons are labeled Back and Menu. The Back button will allow us to return to the midlet's previous screen. The menu button displays a list of commands the current screen can fire:
Here's what the menu items do:
cmmd_0: displays an unimplemented warning screen
OK: displays suggested screen (screen 1)
Help: displays special help screen"
Exit: terminates midlet
Cancel: Return to previous screen
OK: displays suggested screen (screen 1)
Help: displays special help screen"
Exit: terminates midlet
Cancel: Return to previous screen
Here are some shots of these other screens:
CommandProcessor.java
The command processor midlet has much in common with HelloWorld. Instead of a single screen, the constructor will create an array of five screens plus a help screen. The startApp() method will display screen[0] in theDisplay.
public class CommandProcessor extends MIDlet
implements CommandListener {
// pre-defined commands:
private final static Command CMD_EXIT
= new Command("Exit", Command.EXIT, 1);
private final static Command CMD_OK
= new Command("OK", Command.OK, 1);
private final static Command CMD_CANCEL
= new Command("Cancel", Command.CANCEL, 1);
private final static Command CMD_BACK
= new Command("Back", Command.BACK, 1);
private final static Command CMD_HELP
= new Command("Help", Command.HELP, 1);
// Device display window:
private Display theDisplay;
// screens:
private Screen helpScreen;
private static final int MAX_SCREENS = 5;
private int currentScreen = 0;
private Screen[] screens = new Screen[MAX_SCREENS];
// Screen factory method:
private Screen makeScreen(int i) { ... }
// create screens:
public CommandProcessor() { ... }
// Lifecycle methods:
protected void destroyApp(boolean unconditional) { }
protected void pauseApp() { }
protected void startApp() {
theDisplay.setCurrent(screens[currentScreen]);
}
// Command handler method:
public void commandAction(Command c, Displayable d) { ... }
} // CommandProcessor
implements CommandListener {
// pre-defined commands:
private final static Command CMD_EXIT
= new Command("Exit", Command.EXIT, 1);
private final static Command CMD_OK
= new Command("OK", Command.OK, 1);
private final static Command CMD_CANCEL
= new Command("Cancel", Command.CANCEL, 1);
private final static Command CMD_BACK
= new Command("Back", Command.BACK, 1);
private final static Command CMD_HELP
= new Command("Help", Command.HELP, 1);
// Device display window:
private Display theDisplay;
// screens:
private Screen helpScreen;
private static final int MAX_SCREENS = 5;
private int currentScreen = 0;
private Screen[] screens = new Screen[MAX_SCREENS];
// Screen factory method:
private Screen makeScreen(int i) { ... }
// create screens:
public CommandProcessor() { ... }
// Lifecycle methods:
protected void destroyApp(boolean unconditional) { }
protected void pauseApp() { }
protected void startApp() {
theDisplay.setCurrent(screens[currentScreen]);
}
// Command handler method:
public void commandAction(Command c, Displayable d) { ... }
} // CommandProcessor
Creating Screens
Each screen is created in the constructor by calls to the makeScreen() helper method.
CommandProcessor() {
theDisplay = Display.getDisplay(this);
for(int i = 0; i < MAX_SCREENS; i++)
screens[i] = makeScreen(i);
String helpText = "Command processors navigate between screens";
helpScreen = new Alert("Help", helpText, null, AlertType.INFO);
((Alert)helpScreen).setTimeout(Alert.FOREVER);
helpScreen.addCommand(CMD_BACK);
helpScreen.setCommandListener(this);
}
theDisplay = Display.getDisplay(this);
for(int i = 0; i < MAX_SCREENS; i++)
screens[i] = makeScreen(i);
String helpText = "Command processors navigate between screens";
helpScreen = new Alert("Help", helpText, null, AlertType.INFO);
((Alert)helpScreen).setTimeout(Alert.FOREVER);
helpScreen.addCommand(CMD_BACK);
helpScreen.setCommandListener(this);
}
The makeScreen() method creates a type of screen called an Alert. An alert is a warning or confirmation that flashes on the display, only in our case we are setting the duration of the flash to be forever (users will have to get rid of the alert by navigating to another screen). Each screen is declared capable of firing five pre-defined commands (OK, CANCEL, BACK, HELP, and EXIT) as well as one custom command (cmmd_0, for example). Finally, the midlet is registered as the screens listener:
Screen makeScreen(int i) {
String title = "Screen " + i;
String text
= "Shall we go to screen " + (i + 1) % MAX_SCREENS + "?";
// Screen s = new TextBox(title, text, 50, TextField.ANY);
Screen s = new Alert(title, text, null, AlertType.INFO);
((Alert)s).setTimeout(Alert.FOREVER);
s.addCommand(CMD_OK);
s.addCommand(CMD_CANCEL);
s.addCommand(CMD_BACK);
s.addCommand(CMD_HELP);
s.addCommand(CMD_EXIT);
s.addCommand(new Command("cmmd_" + i, Command.SCREEN, 1));
s.setCommandListener(this);
return s;
}
String title = "Screen " + i;
String text
= "Shall we go to screen " + (i + 1) % MAX_SCREENS + "?";
// Screen s = new TextBox(title, text, 50, TextField.ANY);
Screen s = new Alert(title, text, null, AlertType.INFO);
((Alert)s).setTimeout(Alert.FOREVER);
s.addCommand(CMD_OK);
s.addCommand(CMD_CANCEL);
s.addCommand(CMD_BACK);
s.addCommand(CMD_HELP);
s.addCommand(CMD_EXIT);
s.addCommand(new Command("cmmd_" + i, Command.SCREEN, 1));
s.setCommandListener(this);
return s;
}
Handling Commands
The CommandProcessor implements the CommandListener interface. This means that it must implement a commandAction() method and that it can subsequently be registered as a listener for the screens. A typical commandAction() implementation uses a switch statement that dispatches to a sub-handler based on the type of the input command. In most cases, a new screen is selected and is set as theDisplay's current displayable:
void commandAction(Command c, Displayable d) {
switch(c.getCommandType()) {
case Command.BACK: // return to "previous" screen
if (0 < currentScreen) {
theDisplay.setCurrent(screens[--currentScreen]);
} else {
theDisplay.setCurrent(screens[currentScreen]);
}
break;
case Command.OK: // go to "next" screen
if (currentScreen < MAX_SCREENS - 1) {
theDisplay.setCurrent(screens[++currentScreen]);
} else {
currentScreen = 0;
theDisplay.setCurrent(screens[currentScreen]);
}
break;
case Command.CANCEL: // return to main screen
currentScreen = 0;
theDisplay.setCurrent(screens[currentScreen]);
break;
case Command.HELP: // display help screen
theDisplay.setCurrent(helpScreen);
break;
case Command.ITEM: // handle form item command
case Command.SCREEN: // handle screen-specific command
String gripe
= "Sorry, " + c.getLabel() + " not implemented";
Alert a
= new Alert("Warning", gripe, null, AlertType.WARNING);
theDisplay.setCurrent(a);
break;
case Command.EXIT: // terminate midlet
destroyApp(false);
notifyDestroyed();
break;
default: // how did we get here?
gripe = "Unrecognized command: " + c.getLabel();
a = new Alert("Error", gripe, null, AlertType.ERROR);
theDisplay.setCurrent(a);
} // switch
}
switch(c.getCommandType()) {
case Command.BACK: // return to "previous" screen
if (0 < currentScreen) {
theDisplay.setCurrent(screens[--currentScreen]);
} else {
theDisplay.setCurrent(screens[currentScreen]);
}
break;
case Command.OK: // go to "next" screen
if (currentScreen < MAX_SCREENS - 1) {
theDisplay.setCurrent(screens[++currentScreen]);
} else {
currentScreen = 0;
theDisplay.setCurrent(screens[currentScreen]);
}
break;
case Command.CANCEL: // return to main screen
currentScreen = 0;
theDisplay.setCurrent(screens[currentScreen]);
break;
case Command.HELP: // display help screen
theDisplay.setCurrent(helpScreen);
break;
case Command.ITEM: // handle form item command
case Command.SCREEN: // handle screen-specific command
String gripe
= "Sorry, " + c.getLabel() + " not implemented";
Alert a
= new Alert("Warning", gripe, null, AlertType.WARNING);
theDisplay.setCurrent(a);
break;
case Command.EXIT: // terminate midlet
destroyApp(false);
notifyDestroyed();
break;
default: // how did we get here?
gripe = "Unrecognized command: " + c.getLabel();
a = new Alert("Error", gripe, null, AlertType.ERROR);
theDisplay.setCurrent(a);
} // switch
}
Menus
Simple Menu
MIDP provides a screen and an i9tem for making choices. Both implement the Choice interface, which defines three different choice styles: Choice.IMPLICIT, Choice.MULTIPLE, and Choice.EXPLICIT:
Here's a simple example of an implicit choice list titled "Main":
Using the arrows, the user can highlight an item, then press the select button or the soft button labeled "Main". An alert appears momentarily that displays the index of the item selected.
Implementation
The main menu is created out of a list of string items. The main menu fires a command called CMD_MAIN. The command handler gets the index of the selected item from the main menu.
public class Cascade extends MIDlet
implements CommandListener {
// main menu + items:
private Display theDisplay;
String[] items = {"Item 1", "Item 2", "Item 3"};
private List mainMenu
= new List("Main", Choice.IMPLICIT, items, null);
// pre-defined commands:
private final static Command CMD_EXIT
= new Command("Exit", Command.EXIT, 1);
private final static Command CMD_MAIN
= new Command("Main", Command.ITEM, 1);
public Cascade() {
theDisplay = Display.getDisplay(this);
mainMenu = new List("Main", Choice.IMPLICIT, items, null);
mainMenu.setSelectCommand(CMD_MAIN);
mainMenu.addCommand(CMD_EXIT);
mainMenu.setCommandListener(this);
}
// lifecycle methods:
protected void destroyApp(boolean unconditional) { }
protected void pauseApp() { }
protected void startApp() {
theDisplay.setCurrent(mainMenu);
}
// Command handler method:
public void commandAction(Command c, Displayable d) {
switch(c.getCommandType()) {
case Command.ITEM:
String text = "Item selected = ";
selection += items[mainMenu.getSelectedIndex()];
Alert a =
new Alert("Warning", text, null, AlertType.INFO);
theDisplay.setCurrent(a);
break;
case Command.EXIT:
destroyApp(false);
notifyDestroyed();
break;
} // switch
} // commandAction()
}
implements CommandListener {
// main menu + items:
private Display theDisplay;
String[] items = {"Item 1", "Item 2", "Item 3"};
private List mainMenu
= new List("Main", Choice.IMPLICIT, items, null);
// pre-defined commands:
private final static Command CMD_EXIT
= new Command("Exit", Command.EXIT, 1);
private final static Command CMD_MAIN
= new Command("Main", Command.ITEM, 1);
public Cascade() {
theDisplay = Display.getDisplay(this);
mainMenu = new List("Main", Choice.IMPLICIT, items, null);
mainMenu.setSelectCommand(CMD_MAIN);
mainMenu.addCommand(CMD_EXIT);
mainMenu.setCommandListener(this);
}
// lifecycle methods:
protected void destroyApp(boolean unconditional) { }
protected void pauseApp() { }
protected void startApp() {
theDisplay.setCurrent(mainMenu);
}
// Command handler method:
public void commandAction(Command c, Displayable d) {
switch(c.getCommandType()) {
case Command.ITEM:
String text = "Item selected = ";
selection += items[mainMenu.getSelectedIndex()];
Alert a =
new Alert("Warning", text, null, AlertType.INFO);
theDisplay.setCurrent(a);
break;
case Command.EXIT:
destroyApp(false);
notifyDestroyed();
break;
} // switch
} // commandAction()
}
Subscribe to:
Posts (Atom)