Enterprise J2ME Developing Mobile Java Applications [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Enterprise J2ME Developing Mobile Java Applications [Electronic resources] - نسخه متنی

Michael Juntao Yuan

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید



3.4 Implementation Walk Through


In this section, we use sample code snippets to illustrate the overall structure of the implementation. We focus on the J2ME smart client.


3.4.1 The Call Model


The MIDP application contains multiple screens interconnected by menu selection and soft button actions. I decided to make each screen an independent functional unit with its own event handlers, transparent data models, and UI view generator. Each screen is represented by a class derived from abstract class MVCComponent (Listing 3.1). The relationship of classes in the UI model is illustrated in Figure 3.3.


Figure 3.3. UML model for the UI component.


Listing 3.1. The MVCComponent abstract class


public abstract class MVCComponent implements CommandListener {
// Set from outside at beginning
public static Display display;
// Returns the screen object from the derived class
public abstract Displayable getScreen();
public Displayable prepareScreen () throws Exception {
if ( getScreen() == null ) {
initModel();
createView();
} else {
updateView();
}
getScreen().setCommandListener ( (CommandListener) this );
return getScreen ();
}
public void showScreen() {
try {
display.setCurrent( prepareScreen() );
} catch (Exception e) {
e.printStackTrace();
Alert a = new Alert("Error");
a.setTimeout(Alert.FOREVER);
display.setCurrent(a);
}
}
// Initialize. If a data member is not backed by RMS,
// make sure it is null before you put in values.
protected abstract void initModel () throws Exception;
protected abstract void createView () throws Exception;
protected abstract void updateView () throws Exception;
public abstract void commandAction(Command c, Displayable s);
}

Using the UpdateToken object as an example, code Listing 3.2 illustrates how to make a new screen object from the MVCComponent class.

Listing 3.2. Example implementation of MVCComponent



public class UpdateToken extends MVCComponent {
// MIDP UI components: Commands, TextFields etc.
// ... ...
// Model parameters
private static String username;
private static String password;
private static String token;
private static String endPointURL;
// Bean-style methods to access data members
// Event handler
public void commandAction(Command c, Displayable s) {
try {
if (c == backCommand) {
(new MainMenu()).showScreen();
} else if (c == updateCommand) {
username = usernameField.getString();
password = passwordField.getString();
UpdateTokenTask t =
new UpdateTokenTask(endPointURL, display);
t.go();
}
} catch (Exception e) {
// Show some alerts
}
}
public Displayable getScreen () {
return screen;
}
// In initModel(), first check whether the
// the data model is persistently saved.
//
// The use of persistent store is transparent
// to this class's users.
protected void initModel() throws Exception {
try {
securityInfoStore =
RecordStore.openRecordStore("securityInfo", true);
RecordEnumeration re =
securityInfoStore.enumerateRecords(null, null, false);
ByteArrayInputStream bais =
new ByteArrayInputStream( re.nextRecord() );
DataInputStream din = new DataInputStream(bais);
username = din.readUTF();
password = din.readUTF();
token = din.readUTF();
din.close();
bais.close();
securityInfoStore.closeRecordStore();
} catch (Exception e) {
username = Start.UsernameSugg;
password = Start.PasswordSugg;
token = "-1:-1";
}
}
// createView() creates a view from the current
// model into the screen object
protected void createView() throws Exception {
backCommand = new Command("MAIN", Command.SCREEN, 2);
updateCommand = new Command("UPDATE", Command.SCREEN, 1);
usernameField = new TextField("Username:",
username, 40, TextField.ANY);
passwordField = new TextField("Password:",
password, 40, TextField.ANY);
screen = new Form("Security Info");
((Form) screen).append( usernameField );
((Form) screen).append( passwordField );
((Form) screen).append( "Current Token is " + token );
screen.addCommand( backCommand );
screen.addCommand( updateCommand );
}
// updateView() updates the screen object when
// the model changes.
protected void updateView() throws Exception {
// display updated username and password
usernameField.setString( username );
passwordField.setString( password );
// Deletes the old token display
((Form) screen).delete(2);
if ( "-1:-1".equals(token) ) {
((Form) screen).append("The current token is not valid");
} else {
((Form) screen).append("Current Token is token");
}
}
}

Using the UpdateToken screen is very simple. All you need to do is set up the model data and then call the showScreen() method.


3.4.2 The Threading Model


An important feature of iFeedBack is its use of worker threads during lengthy network operations to improve the perceived performance. The threading model of iFeedBack includes a worker thread, an animation thread, and a UI frontend thread (see Figure 3.4).


Figure 3.4. Execution flow: iFeedBack's worker threads.


All worker threads are derived from abstract class BackgroundTask (Listing 3.3; see Figure 3.5).


Figure 3.5. Classes in the iFeedBack thread model.


Listing 3.3. The BackgroundTask is the base for all thread tasks


public abstract class BackgroundTask extends TimerTask {
private Thread th;
private boolean stopped;
// Could be set in derived class
// If the task is successfully completed
protected Displayable nextScreen;
// If the task is aborted
protected Displayable prevScreen;
// Display to draw-on
protected Display display;
// The gauge screen title
protected String title;
// Do we need to display an alert before
// moving to the nextScreen?
protected boolean needAlert = false;
protected Alert alertScreen;
public BackgroundTask (Display d) {
display = d;
th = new Thread(this);
}
public void go () {
stopped = false;
th.start();
}
public void stop () {
stopped = true;
th.setPriority(Thread.MIN_PRIORITY);
}
public void run() {
ProgressGauge pg = null;
try {
pg = new ProgressGauge(this, title, display, prevScreen);
runTask ();
} catch (Exception e) {
// elaborate error handling using alerts
} finally {
// Since pg could callback and reset
// "stopped" when its
// Cancel key is pressed on Gauge.
if (!stopped) {
if ( needAlert ) {
pg.setNextScreen(alertScreen, nextScreen);
} else {
pg.setNextScreen(nextScreen);
}
pg.stop(); // notify progress guage to quit
}
}
}
// template method.
public abstract void runTask () throws Exception;
}

The ProgressGauge object starts a foreground thread that displays an animated guage. It features a Cancel button. If the Cancel button is pressed, the ProgressGuage displays the prevScreen screen, instructs the BackgroundTask not to update the display when finished, and stops. For more information, please refer to the ProgressGauge source code.

In your derived class for a specific task, you only need to implement the runTask(). Upon completion of the task, the runTask() updates the nextScreen variable. Listing 3.4 shows the runTask() method in the Update_ Token Task class.

Listing 3.4. The UpdateTokenTask implementation



public class UpdateTokenTask
extends BackgroundTask {
private String endPointURL;
public UpdateTokenTask (String url, Display d) throws Exception {
super (d);
endPointURL = url;
prevScreen =
(new UpdateToken()).prepareScreen();
}
public void runTask () throws Exception {
String username = UpdateToken.getUsername ();
String password = UpdateToken.getPassword ();
String token = getToken(username, password);
if ( "-1:-1".equals(token) ) {
needAlert = true;
alertScreen = new Alert("Cannot update token");
alertScreen.setString(
"Authentication failed!");
alertScreen.setTimeout(Alert.FOREVER);
nextScreen = prevScreen;
} else {
UpdateToken.setAll( username, password, token );
needAlert = true;
alertScreen = new Alert("Success!");
alertScreen.setString("The new token is token");
alertScreen.setTimeout(Alert.FOREVER);
nextScreen = (new MainMenu()).prepareScreen();
}
}
// ... ...
}

To invoke the worker thread, you only need to call its go() method (Chapter 5).


3.4.3 Data Exchange


The MIDP client communicates with the class survey server via a pre-agreed binary format over the HTTP. In the wired world, generic binary data are passed through TCP/IP sockets. Higher-level protocols such as HTTP are used to transport application specific data, which require additional semantic structures (e.g., HTTP headers). However, in the J2ME world, access to raw TCP/IP sockets is not ubiquitous. In fact, HTTP is the only protocol mandated by the MIDP v1.0 specification. Therefore, J2ME developers often use HTTP to transport generic binary data.

Convenience I/O methods in the DataInputStream and DataOutputStream classes are extensively used to pack or unpack application data to or from the stream. For example,

The writeUTF() method writes a data string with leading bytes indicating the string length and encoding into the stream.

The readUTF() method at the other end of the stream reads the length and encoding bytes first, and then reads the specified number of bytes from the stream and reconstructs the correctly encoded string.


There is a pair of convenience read/write methods for every primitive Java data type. Those methods have greatly simplified our work. Chapter 12 for advanced tools and techniques that build structured and easy-to-manage storage structures on top of the RMS.

Listing 3.5. The binary I/O in the SubmitAllTask class


Public class SubmitAllTask extends BackgroundTask {
public SubmitAllTask (Display d) throws Exception {
// Set up screens
}
public void runTask () throws Exception {
RecordStore answerStore =
RecordStore.openRecordStore("answer", true);
RecordEnumeration re =
answerStore.enumerateRecords(null, null, false);
int recordNum = 0;
while ( re.hasNextElement() ) {
recordNum++;
int recordid = re.nextRecordId();
ByteArrayInputStream bais =
new ByteArrayInputStream( answerStore.getRecord(recordid) );
DataInputStream din = new DataInputStream(bais);
String url = din.readUTF();
int qid = din.readInt();
long timestamp = din.readLong();
String answer = din.readUTF();
String comment = din.readUTF();
HttpConnection conn = null;
DataInputStream hdin = null;
DataOutputStream hdout = null;
try {
conn = (HttpConnection) Connector.open( url );
conn.setRequestMethod(HttpConnection.POST);
hdout = conn.openDataOutputStream();
hdout.writeInt(1); // Submit opcode
hdout.writeUTF( UpdateToken.getToken () );
hdout.writeLong( timestamp );
hdout.writeInt( qid );
hdout.writeUTF( answer );
hdout.writeUTF( comment );
hdout.flush();
hdin = conn.openDataInputStream();
boolean authsucc = hdin.readBoolean();
if (authsucc) {
boolean updatesucc = hdin.readBoolean();
if ( updatesucc ) {
answerStore.deleteRecord( recordid );
} else {
// Server error. Show alert
}
nextScreen = prevScreen;
} else {
// Auth error. Show Auth token screen
}
} finally {
// Close all connections
}
}
re.destroy();
answerStore.closeRecordStore();
// Setup the next screen and show the number of
// records submitted
}
}

The binary data exchange formats are efficient, but they are proprietary and result in tightly coupled data producers and consumers. For services that require open interfaces, such as the authentication server, binary protocols are not sufficient. We implemented the iFeedBack authentication server using Sun's Java Web Service Developer Pack (v1.0). The server exposes its services through the SOAP (Simple Object Access Protocol) XML API and publishes the API specification in an open WSDL (Web Services Definition Language) document. On the mobile client side, we use the kSOAP library to assemble and parse the SOAP data. For more information on kSOAP and mobile Web Services, please refer to Part V of this book.


/ 204