站内搜索: 请输入搜索关键词

当前页面: 开发资料首页J2ME 专题J2ME MIDP 使用技巧

J2ME MIDP 使用技巧

摘要: J2ME MIDP 使用技巧
<tr><td>
http:///tech/article816.html
有效率的使用 RECORD STORES
"Record Management System Basics" 介紹了 Record Management System (RMS), 一個能夠不斷儲存和讀取資料的 Class. RMS 被定義在 Mobile Information Device Profile (MIDP)中. 在你閱讀完本篇文章之後,你會發現產生和使用一個資料儲存中心是如此的容易。這篇文章的焦點在於如何以最快、最有效率的方法,把在資料儲存中心的資料存入或讀出。
在資料儲存中心,資料以 Record 形式被大量的儲存。一個 Record 是一個任意的二進位資料,根本上則一個 Bytes 陣列。你總是使用 RecordStore class 的 getRecord 、 addRecord 和 setRecord 方法去存取出整個 Record,下面是一個通常的型態:
RecordStore rs = null;
byte[] data;
int id = ....; // assign a record ID
try {;
[] rs = RecordStore.openRecordStore( "mydata", false );
data = rs.getRecord( id );

..... // modify the data

rs.setRecord( id, data, 0, data.length );
rs.closeRecordStore();
};
catch( Exception e ){;
// do something here
};
不能只改變 Record 裡一小部分的資料:你必須讀出整個 Record,在記憶體裡更改你的資料,然後寫回整個 Record 到資料儲存中心裡。
這裡有一個關於使用資料儲存中心的好方法:把你對 Record 的讀取和寫入次數減到最小。最低限度,把你的寫入次數減到最少;在一些裝置裡,寫入的代價比讀取來的高。以 palm 裝置為例 ,寫入保護大部分的記憶體去預防應用程式竄改其它的資料。在這個環境裡,你的寫入動作越少,你的應用程式效能就越快。
即使你限制了應用程式寫入資料到資料儲存中心,你會發現讀取資料變成了應用程式的一個瓶頸。尤其是在列舉出資料的時後,一部份的問?};通常存在於過度的物件 "churning" 發生。Churning 起因於在記憶體堆疊裡不停的創造與消滅物件(包括陣列)。創造物件不僅花費時間,而且 churning 也會淹沒廢棄物件回收.
舉個例子來說,有一個方法可以在資料儲存中心的所有資料裡移動:
RecordStore rs = ....; // an open record store
try {;
int lastID = rs.getNextRecordID();
byte[] data;

for( int i = 0; i < lastID; ++i ){;
try {;
data = rs.getRecord( i );
.... // do something with the data
[] };
catch( InvalidRecordIDException e ){;
continue;
[] };
};
};
catch( Exception e ){;
[] // error
};
另一個方法是使用 enumeration:
RecordStore rs = ....; // an open record store
try {;
RecordEnumeration enum = rs.enumerateRecords(
null, null, false );
[] while( enum.hasNextElement() ){;
byte[] data = enum.nextRecord();
[] .... // do something with the data
};
};
catch( Exception e ){;
// error
[]};
這兩種方法的問?};在於,他們都分配了新的陣列給每一筆資料,這是標準的物件 churning 實例。較好的方法是重?};使用陣列,只有在陣列太小時才捨棄不用。舉例來說:
[]RecordStore rs = ....; // an open record store
try {;
RecordEnumeration enum = rs.enumerateRecords(
null, null, false );
byte[] data = new byte[100];
int len = 0;

while( enum.hasNextElement() ){;
int id = enum.nextRecordId();
[] len = rs.getRecordSize( id );
if( len > data.length ){;
// add a growth factor
[] data = new byte[ len + 40 ];
};
rs.getRecord( id, data, 0 );
// do something with the data
};
};
catch( Exception e ){;
[] // error
};
注意這個例子中的陣列是如何使自己適應每一筆資料的大小。當然,如果每一筆資料的大小都一樣,你就不用擔心陣列是否夠大的問?};了。
第二個規則是當讀取資料時,盡可能的重?};使用 Bytes 陣列。相同的,重?};使用 Bytes 陣列規則也適用於寫入資料時。
[]然而,當寫入資料時,物件 churning 並不常是一個問?};,因為資料讀取的次數通常要比寫入的次數多。
通常,重?};使用物件還是並不足以避免物件 churning。這是因為依照這建議的方法去讀取資料使用了一個 DataInputStream 物件,像下面的例子:
byte[] data = .....; // data from a record store
ByteArrayInputStream bin = new ByteArrayInputStream( data );
DataInputStream din = new DataInputStream( bin );
String name = din.readUTF();
boolean isFemale = din.readBoolean();
// etc. etc.
如果你對每一筆資料都產生一個新的輸入資料流,那麼,你將仍然會產生以及消滅太多物件。然而,如果你重?};的使用 Bytes 陣列,你也可以重?};的使用輸入資料流。你可以藉著使用 reset 方法去使得資料流由陣列的開頭重新讀取。舉例來說,考慮到讀取一筆資料, 這筆資料是由一個 boolean 值和兩個整數值所構成:
[]RecordStoreEnumeration enum = ...; // get a record enumeration
byte[] data = new byte[9]; // record size
ByteArrayInputStream bin = new ByteArrayInputStream( data );
DataInputStream din = new DataInputStream( bin );
while( enum.hasNextElement() ){;
int id = enum.nextRecordId();
getRecord( id, data, 0 );
din.reset(); // move stream back to start

boolean first = din.readBoolean();
int second = din.readInt();
[] int third = din.readInt();

// do something here
};
這是一個讀取資料更有效率的方法。所以,有效率的讀取資料第三規則是:無論何時,當你重?};使用 Bytes 陣列的時候,重?};使用輸入資料流。
讓我們藉著寫一個簡單的 Record class 來暖身一下之前討論的內容,你可以藉著這個 class 來讀取資料:
import java.io.*;
import javax.microedition.rms.*;
public class Record implements DataInput {;
private RecordStore _rs;
private byte[] _data;
[] private int _length;
private int _id;
private DataInputStream _din;
public Record( RecordStore rs ){;
this( rs, 100 );
};
public Record(
RecordStore rs, int initialRecordSize ){;
_rs = rs;
_data = new byte[ initialRecordSize ];
[] _din = new DataInputStream(
new ByteArrayInputStream( _data ) );
_length = -1;
};
public byte[] getByteArray() {; return _data; };
public int getLength() {; return _length; };
public byte[] moveTo( int id )
throws RecordStoreNotOpenException,
InvalidRecordIDException,
RecordStoreException,
IOException
{;
_length = _rs.getRecordSize( id );
if( _length > _data.length ){;
_data = new byte[ _length + 40 ];
_din = new DataInputStream(
new ByteArrayInputStream( _data ) );
};
_rs.getRecord( id, _data, 0 );
_id = id;
_din.reset();
return _data;
};
public void readFully(byte b[])
throws IOException {;
[] _din.readFully( b );
};
public void readFully(byte b[], int off, int len)
throws IOException {;
_din.readFully( b, off, len );
};
return _din.skipBytes( n );
};
public boolean readBoolean() throws IOException {;
return _din.readBoolean();
};
[]
public byte readByte() throws IOException {;
return _din.readByte();
[] };
public int readUnsignedByte()
throws IOException {;
return _din.readUnsignedByte();
};
public short readShort() throws IOException {;
return _din.readShort();
};
public int readUnsignedShort()
throws IOException {;
return _din.readUnsignedShort();
};
public char readChar() throws IOException {;
return _din.readChar();
};
[] public int readInt() throws IOException {;
return _din.readInt();
};
public long readLong() throws IOException {;
return _din.readLong();
};
[] public String readUTF() throws IOException {;
return _din.readUTF();
};
};
你可以以下面的方法來使用這個 class :
try {;
rs = RecordStore.openRecordStore( "mydata", true );
[]
[] // Write two records to the record store
[] ByteArrayOutputStream bout =
new ByteArrayOutputStream();
DataOutputStream dout =
new DataOutputStream( bout );
byte[] data;
dout.writeUTF( "this is a test" );
dout.writeInt( 1 );
dout.flush();
data = bout.toByteArray();
rs.addRecord( data, 0, data.length );
bout.reset();
dout.writeUTF( "this is another test" );
dout.writeInt( 99 );
dout.flush();
[] data = bout.toByteArray();
rs.addRecord( data, 0, data.length );

// Now read through the record store
[] Record record = new Record( rs );
int lastID = rs.getNextRecordID();
RecordEnumeration enum = rs.enumerateRecords(
null, null,
while( enum.hasNextElement() ){;
int id = enum.nextRecordId();
record.moveTo( id );
System.out.println( record.readUTF() + " " +
record.readInt() );
};
rs.closeRecordStore();
};
catch( Exception e ){;
// handle error
};
[]你可以非常簡單的延伸這個 Record class 去包含其他的方法,讓它存取你在資料儲存中心的資料,像是 getFirstName 和 getLastName 等。
[]
--------------------------------------------------------------------------------
使用 MIDP LOW-LEVEL 使用者介面 API
在 J2ME, profiles 是負責定義使用者介面的 API。Mobile Information Device Profile (MIDP) 定義了兩種這類的 APIs,high-level 和 low-level APIs。high-level API 要求你使用以任務為方向的抽象型態去定義你的使用者介面。你不能真正的控制畫在螢幕上的元件 -- implementation 選擇了在這個裝置上最佳的方法. high-level API 在所有支援 MIDP 的裝置上是便於轉移的並且是真正適合於商務應用程式。在以後的文章將會介紹更多有關 high-level API。
low-level API 的目標是瞄準遊戲的開發者。不像 high-level API,low-level API 讓你完全的控制螢幕和輸入事件。然而,這些控制有些代價,因為你必須負責畫出所有在螢幕上顯現的元件。你可以在相同的一個應用程式使用 high-level 和 low-level APIs,但不能同時使用。想像應用程式就像一副卡片,一次只能看到一張卡片(更像是 J2SETM 平台上 java.awt.CardLayout class 所提供的行為)。每一個卡片,在 MIDP terminology 被視為一個螢幕,在這個平台上你可以使用 high-level API 或是 low-level API,但不能同時使用。唯一的例外應用是使用 Command 物件,將在本文後面討論。
要使用 MIDlet 裡的 low-level API , 你必須先寫一個繼承 Canvas 物件的 class :
// Simple canvas
import javax.microedition.lcdui.*;
import javax.microedition.midlet.*;
public class MyCanvas extends Canvas {;
private MIDlet midlet;
[] public MyCanvas( MIDlet midlet ){;
[] this.midlet = midlet;
};

protected void paint( Graphics g ){;
g.setColor( 255, 255, 255 );
[] g.fillRect( 0, 0, getWidth(), getHeight() );
g.setColor( 0, 0, 0 );
g.drawString( "Hello there!", getWidth()/2, 0,
g.TOP | g.HCENTER );
};
};
所有的使用者介面 classes 都包含在 javax.microedition.lcdui package 中. 注意!你也必須去載入 javax.microedition.midlet package 因為你將傳遞每一個 canvas 的參照給 MIDlet. 你的 canvas 子 class 必須實現一個被系統所呼叫,用來重繪螢幕的 paint 方法。這個 paint 傳遞了一個 Graphics 物件 ,這個物件定義了所有你會用到的基本繪圖方法,像是 drawArc、drawLine、drawRect和、drawString。 這個 API 也支援 24-bit RGB 色彩模式 ,即使你使用的裝置支援較少的色彩。MyCanvas 例子簡單的靠重繪螢幕,把螢幕變成白色,然後在螢幕上方中間畫出一個黑色的字。.
靠著呼叫 MIDlet 中 Display 物件的 setCurrent 方法,你可以讓 canvas 作用,通常我們會寫在應用程式的 startApp 方法裡:
// Simple MIDlet
import javax.microedition.midlet.*;
public class MyMIDlet extends MIDlet {;
private Display display;
private MyCanvas canvas;

public MyMIDlet(){;
display = Display.getDisplay( this );
canvas = new MyCanvas( this );
[] };

protected void startApp(){;
display.setCurrent( canvas );
};

protected void pauseApp(){;
};

protected void destroyApp( boolean unconditional ){;
};
[]
public void exit(){;
destroyApp( true );
notifyDestroyed();
};
};
儘管 MIDlet 執行中,有一個問?};是: 並沒有一個明顯的方法去結束它。你必須用一些方法去捕捉使用者的輸入。有兩個方法可以做到:使用未處理的輸入事件或 Command 事件。
一個 canvas 靠著本身定義的覆寫適當的事件遞送方法,收到未經處理的輸入事件。 有幾個事件產生方法 :
按下鍵盤裝置(keyPressed, keyRepeated, and keyReleased)
使用 pointer (pointerPressed, pointerDragged and pointerReleased) 如果這裝置支援 pointer
顯示 canvas (showNotify, hideNotify).
舉例來說,你可以定義一個 canvas 上的 keyPressed 事件去結束應用程式:
protected void keyPressed( int keyCode ){;
((MyMIDlet) midlet).exit();
};
在所有的 keypad 事件,按鍵碼(keyCode) 定義了觸動這個事件的按鍵。一個正值代表一個 Unicode 字元,一個負值明顯的不能轉換成Unicode,而不是算出在一個裝置上,哪一個按鍵對應到哪一個,Canvas class 為常被使用的按鍵定義了一個常數值。特別是,它定義了抽象的遊戲功能(上、下、左、又、開火,按鈕 A,按鈕 B、按鈕 C 和按鈕 D) 這些按鍵碼對應可以在遊戲中決定。在它初始化的時候,裝置會呼叫 Canvas.getGameAction 去選擇哪一個鈕對應哪一個動作。
你可以定義一個像這樣的基本 class :
public abstract class GameCanvas extends Canvas {;
protected MIDlet midlet;
protected int fireKey;
protected int leftKey;
protected int rightKey;
protected int upKey;
protected int downKey;
[]
public GameCanvas( MIDlet midlet ){;
this.midlet = midlet;
fireKey = getKeyCode( FIRE );
leftKey = getKeyCode( LEFT );
rightKey = getKeyCode( RIGHT );
upKey = getKeyCode( UP );
downKey = getKeyCode( DOWN );
};
};
And then extend it like this:
public class MyCanvas extends GameCanvas {;
private String message = "Press any key";

public MyCanvas( MIDlet midlet ){;
super( midlet );
[] };

[] protected void paint( Graphics g ){;
g.setColor( 255, 255, 255 );
g.fillRect( 0, 0, getWidth(), getHeight() );
g.setColor( 0, 0, 0 );
g.drawString( message, getWidth()/2, 0,
g.TOP | g.HCENTER );
};

protected void keyPressed( int keyCode ){;
if( keyCode == fireKey ){;
message = "FIRE";
}; else if( keyCode == leftKey ){;
[] message = "LEFT";
}; else if( keyCode == rightKey ){;
message = "RIGHT";
}; else if( keyCode == upKey ){;
message = "UP";
}; else if( keyCode == downKey ){;
[] message = "DOWN";
}; else {;
message = getKeyName( keyCode );
};
repaint();
};
};
Pointer 事件是選擇性的,因為不是所有內含 MIDP 裝置都有 pointer. 每當它有意義,你可以利用 pointer。但是你不應該認為它是有效的。藉著呼叫 Canvas.hasPointerEvents 你可以確認是否 pointer 事件被觸發。 pointer 事件方法傳入 pointer 的水平和垂直位置:
protected void pointerPressed( int x, int y ){;
[] // do something here
};
另外一個取得使用者輸入的方法是去附加一個 Commands 給 canvas。一個 Commands 是一個抽象的動作表現型態。它有一個使用者定義的標籤,一個型態以及優先順序。裝置使用型態去使 command 對應到按鍵或按鈕。舉例來說,如果一個裝置有標準的 OK 按鈕,定義一個 command 的 OK 型態,使得 OK 鈕能夠觸發 Commands。有效的型態有 BACK、CANCEL、EXIT、HELP、ITEM、OK、SCREEN和STOP 其中一些也許對應到相同的按鍵或按鈕。所以,當它們發生衝突時,裝置會使用優先順序來決定哪一個是最適合的。優先順序是一個正的整數。 1 是最高的優先順序。
使用 Command class 去創造一個 Command 物件,就像下列一樣:
Command exitCommand = new Command( "Exit", Command.SCREEN, 1 );
你可以使用 addCommand method 去把 command 附加在 canvas 中:
canvas.addCommand( exitCommand );
你必須使用 setListener 去註冊一個 command 傾聽者。
canvas.setListener( listener );
這個傾聽者必須實現 CommandListener 介面。它通常在主要的 MIDlet class 中去實現 CommandListener,去捕捉結束的命令,例如下列:
// Simple MIDlet
import javax.microedition.midlet.*;
public class MyMIDlet extends MIDlet implements
CommandListener {;
private Display display;
[] private MyCanvas canvas;
private Command exitCommand = new Command(
"Exit", Command.SCREEN, 1 );

public MyMIDlet(){;
display = Display.getDisplay( this );
canvas = new MyCanvas( this );
canvas.addCommand( exitCommand );
[] canvas.setListener( this );
[] };

protected void startApp(){;
display.setCurrent( canvas );
};

protected void pauseApp(){;
};

protected void destroyApp( boolean unconditional ){;
};

[] public void exit(){;
destroyApp( true );
notifyDestroyed();
};
public void commandAction( Command c, Displayable d ){;
if( c == exitCommand ){;
exit();
};
};
};
CommandListener 介面定義了單一的方法 commandAction,當 command 被觸發時就會呼叫這個方法。一個被觸發的 Command 物件參考被傳入,就像是當被觸發時傳入給 display 物件. (相同的 command 能被不同的 canvases 分享,並且,high-level API 也可以). 當然,傾聽者是負責實際傳遞行為的。

http:///tech/article816.html
</td></tr></table></td> </tr> <tr> <td background="/pic/split.gif" height=1></td> </tr> <tr> <td class="tdMargin1">
↑返回目录
前一篇: 理解J2ME平台
后一篇: 如何用Motorola J2 SDK 1来编译Java程序