|
|
|
| 您的位置:首页>>J2ME开发>>创建基于MIDP的应用(下) |
|
|
创建基于MIDP的应用(下)
|
| 2005-06-09 来源:yesky 作者:QQ新人类 |
ChoiceGroup
ShowStockList()方法用来由股票的Vector变量中得到Stock对象。然后它就会创建一个ChoiceGroup对象,该对象带有用户可以售卖的股票的一个列表。ChoiceGroup是你可以加入到一个Form screen的一个项目。它可组合成一个带有数目和选项的列表,不过它不是一个screen类型。在这个屏幕中,list被设置为EXCLUSIVE,因此用户每次只可以选择一个股票出售。
private Item showStockList(Vector stocks) { Stock currentStock = null;
_stockList = new ChoiceGroup("Choose stock", ChoiceGroup.EXCLUSIVE);
for ( int i = 0; i < stocks.size(); i++) { currentStock = (Stock) stocks.elementAt(i); _stockList.append( currentStock.getSymbol(), null ); }
return _stockList; }
| SelectStockScreen的commandAction()方法用来转移屏幕的控制,将用户选择股票售卖的屏幕转移到真正执行股票售卖的屏幕。这个方法由股票列表中接收当前选择的股票,并且将该字符串传送给SellStockScreen。
public void commandAction( Command c, Displayable d) { if ( c == backCommand) { _controller.lastScreen(); } else { // get the selected item int selectedIndex = _stockList.getSelectedIndex();
String selectedStock = _stockList.getString( selectedIndex);
// move on to the SellStockScreen SellStockScreen sellStockScreen = new SellStockScreen(_controller, selectedStock);
_controller.nextScreen( sellStockScreen); } }
| SellStockScreen
SellStockScreen是用来显示一个信息,让用户可确认上次选择的股票买卖。如果用户选择“Next”按钮,该股票就会由数据库中移除。
该对象的构造体将接收一个Controller类的引用,以及一个代表所售股票的String。这个屏幕将会访问StockDatabase对象得到数据库中相应股票数目的信息。然后它将通过使用displayScreen()方法来将该信息显示在屏幕上(我们上面已经讨论过该方法)。
public SellStockScreen(Controller controller, String selectedStock) { super("Sell "+ selectedStock, controller);
append("Selling " + selectedStock);
try { // determine how many shares the user has _sellStock = _stockDB.getStock( selectedStock);
_symbol = _sellStock.getSymbol(); _numShares = _sellStock.getNumShares();
// display screen super.displayScreen(_symbol, _numShares);
this.setCommandListener( this);
} catch (StockException se) { System.out.println("EXCEPTION "+ se.getMessage() ); } }
| CommandAction()方法将用来处理用户的交互。如果用户点击 “Next”按钮。StockDatabase将会售卖相应的股票。在这个例子中,SellStockScreen并不允许用户修改要售卖的股票数目。然后显示将会返回到WelcomeScreen。
public void commandAction( Command c, Displayable d) { if ( c == backCommand) { _controller.lastScreen(); } else { // sell the shares try { _stockDB.sellStock( _sellStock, _numShares); } catch (StockException se) { se.printStackTrace(); }
_controller.nextScreen( new WelcomeScreen( _controller) ); } }
| Stock对象
在这个例子中常常看到的Stock对象,它是一个包含有一个构造体和一套getXYZ()方法的简单对象。和我上面讨论的RecordStore架构一样,主要的方法是:
public Stock(String symbol, int numShares, int price) { _symbol = symbol; _numShares = numShares; _price = price; }
public String getSymbol(){ return _symbol; } public int getNumShares(){ return _numShares; } public int getPrice(){ return _price; }
| RMSStockStore
讨论完该例子的应用层后,我将和你探讨一下数据存储层。在我以前的讨论中,数据存储仅是通过StockDatabase接口访问的。现在我将讨论一下实现该接口的对象。
这个例子中的RMSStockStore对象用来处理所有和RecordStore相关的活动。该对象包含有一个到javax.microedition.rms.RecordStore对象的引用,它是MIDP API的一部分。
public class RMSStockStore implements RecordFilter, RecordComparator, _ StockDatabase | RMSStockStore对象的构造器将会初始化一个指向“共享”data store的RecordStore对象。如果不存在该data store,它将会创建一个。
public RMSStockStore() { try { String fileName = "shares"; recordStore = RecordStore.openRecordStore(fileName, true); } catch (RecordStoreException rse) { System.out.println(rse); rse.printStackTrace(); } }
| addStock()是该对象用来执行真正写入data store的一个内部方法。要注意的是,加入到record store时,要存储的数据必须从一个对象或者一系列的对象转变为一个byte数组。这个转换是通过StockStorage类来完成的,我将在下文继续讨论。转换为byte数组后,就可以调用addRecord()方法,将该数组传送到RecordStore对象中。
private void addStock(int shares, String symbolName, int price) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); DataOutputStream outputStream = new DataOutputStream(baos);
try { Stock outputStock = new Stock( symbolName, shares, price);
StockStorage.writeStock( outputStock, outputStream);
} catch (IOException ioe) { System.out.println(ioe); ioe.printStackTrace(); }
// Extract the byte array byte[] b = baos.toByteArray();
// Add it to the record store try { recordStore.addRecord(b, 0, b.length); } catch (RecordStoreException rse) { System.out.println(rse); rse.printStackTrace(); } }
| getStockHelper()方法用来作为getStock()和getStocks()方法的一个助手。 getStockHelper()方法可列举出RecordStore返回的记录集。这样它就可以读取这些记录,并且将它加入到一个Vector中,然后传送回用户接口对象中。要注意到,StockStorage对象可用来将InputStream (byte array)转换为一个Stock对象。
private Vector getStockHelper(RecordEnumeration re) { Vector stockVector = new Vector();
try { while(re.hasNextElement()) { int id = re.nextRecordId(); ByteArrayInputStream bais = new ByteArrayInputStream(recordStore.getRecord(id)); DataInputStream inputStream = new DataInputStream(bais); try { Stock stock = StockStorage.readStock(inputStream);
stockVector.addElement( stock); } catch (EOFException eofe) { System.out.println(eofe); eofe.printStackTrace(); } } } ...
return stockVector; }
| RecordStore
在上面讨论的代码中,包含有javax.microedition.rms.RecordStore对象的引用。以下就是在这个例子中用到的该对象的方法子集:
openRecordStore(): 用来打开/创建一个record store
addRecord(): 用来在该store中加入一个记录
enumerateRecords(): 用来遍历record store的记录
getRecord(): 用来从record store中得到一个特定的记录
deleteRecord(): 用来从record store中移除一个记录
RecordStore为MIDP平台提供了一个方便而且灵活的存储系统,使用RecordStore,一个应用可存储各种的信息,由原始的数据类型到复杂的串行化对象。
RecordComparator
RMSStockStore对象实现了javax.microedition.rms.RecordComparator接口。在需要将通过store得到的数据进行排序时,该接口将会被传送到RecordStore。实现RecordComparator的对象必须实现其compare()方法,这个方法用来决定数据库中两个记录的排列顺序。
在RMSStockStore对象包含的compare方法的实现中,将数据库中的两个记录作对比,在此是两只股票的记号。两个记录都通过InputStreams由byte数组转换为stock对象。每只股票的记号可以通过getSymbol()方法得到,然后使用String compareTo()方法进行对比。该方法返回三个可能的int,根据结果的不同,分别是代表少于、等于或者大于。
public int compare(byte[] rec1, byte[] rec2) { ... try { // Extract the stocks. Stock stock1 = StockStorage.readStock(inputStream1); Stock stock2 = StockStorage.readStock(inputStream2);
String symbol1 = stock1.getSymbol(); String symbol2 = stock2.getSymbol();
compareValue = symbol1.compareTo(symbol2); } ...
| 根据返回值的不同,它们被返回到RecordStore,以便决定如何组织RecordEnumerator中的行。如果对于不同的查询有不同的标准的话,你可以使用多个RecordComparator对象。
... // Sort by symbol name if (compareValue < 0) { return RecordComparator.PRECEDES; } else if (compareValue > 0) { return RecordComparator.FOLLOWS; } else { return RecordComparator.EQUIVALENT; } }
| RecordFilter
RMSStockStore对象实现javax.microedition.rms.RecordFilter接口。该接口需要实现一个称为matches()的方法。这个方法做为一个过滤器使用,可让RecordStore决定一个给定的字节数组是否符合当前的查询。在这个例子中,RecordFilter是用来从数据库中得到一个单一股票的值。因此。matches()必须用来确保仅有符合该股票的相应记录才被显示。
下面的代码再次讲解了使用StockStorage()方法由InputStream中得到股票记录的方法。股票的名字通过使用getSymbol()方法由对象中得到。要注意的是返回的语句执行对比的处理,将存储为一个类变量的过滤器值和股票的名字作对比。
public boolean matches(byte[] candidate) throws IllegalArgumentException { // If no filter set, nothing can match it. if (this.symbolFilter == null) { return false; }
ByteArrayInputStream bais = new ByteArrayInputStream(candidate); DataInputStream inputStream = new DataInputStream(bais); String name = null;
try {
Stock stock = StockStorage.readStock(inputStream); int shares = stock.getNumShares(); name = stock.getSymbol(); } ... return (this.symbolFilter.equals(name)); }
| 仅有返回值为真的记录才会出现在查找结果集中。
StockDatabase
RMSStockStore对象实现了这个例子中定义的StockDatabase接口。这些方法使用helper方法和StockStorage对象来执行与它们名字相应的操作。
buyStock()方法用来加入一只股票到RecordStore中。当用户加入一只股票时,就会由BuyStockScreen调用这个方法。
public void buyStock(String symbol, String shares, int price) throws StockException { int numShares = 0;
try { numShares = Integer.parseInt( shares); } catch( NumberFormatException nfe) { throw new StockException("Number of shares value is invalid", nfe); }
| buyStock()方法执行一些基本的字段验证,确保值存在而且有着正确的格式。
if ( numShares <= 0 || price <= 0 || symbol.length() == 0) { throw new StockException("Invalid Data passed to RMS", null); }
| 这个例子限制用户只可输入一只股票。为此,buyStock()方法必须查找以决定给定的股票是否已经存在。
// check if there are existing shares Stock stock = getStock( symbol);
| 如果股票不存在,它就会将该股票加入到记录中。
if ( stock == null) { // add the new shares addStock( numShares, symbol, price); }
| 如果该股票存在,第一个项目将会由数据库中移除,并且加入一个新的记录。要注意的是,你可以使用RecordStore.setRecord()方法来执行这两个步骤。我这样做的原因只是为了令例子简化。
else { sellStock( stock, stock.getNumShares() );
addStock( numShares + stock.getNumShares(), symbol, price); }
| sellStock()方法被buyStock()方法和SellStockScreen调用。这个方法是用来由data store中移除一只股票的。
| public void sellStock(Stock stock, int numShares) throws StockException | symbolFilter的值被设置为要出售的股票记号。上文已经提过,RecordComparator使用这个值,在它的matches()方法中来决定哪个记录符合要求。
// delete entry from the database this.symbolFilter = stock.getSymbol();
Stock retrievedStock = null;
| 通过使用RecordFilter接口,RecordStore提供一个查询的机制。要获得记录的一个列表,可传送RecordFilter实例到enumerateRecords()方法中。这个过滤器将会确保只有包含有这个记号的记录才会返回。通过传送RecordComparator,RecordStore将会以要求的排序方式返回记录。在这个例子中,RMSRecordStore实现了RecordComparator和RecordFilter,因此它们被作为this传送。
try { RecordEnumeration re = recordStore.enumerateRecords(this, this, true);
while(re.hasNextElement()) { int id = re.nextRecordId(); ByteArrayInputStream bais = new ByteArrayInputStream(recordStore.getRecord(id)); DataInputStream inputStream = new DataInputStream(bais); try { retrievedStock = StockStorage.readStock(inputStream);
| 要根据记录的id值来移除它
recordStore.deleteRecord( id); ...
| getStocks()方法用来得到用户所有股票的一个列表。该方法将返回股票对象的一个Vector,它被SelectUserScreen用来得到股票的列表。要注意到所传送的RecordFilter的值为null。这是确保所有数据库中的记录都被返回,而无需使用过滤器。
public Vector getStocks() throws StockException { Vector stockVector = new Vector();
try { // Enumerate the records using the comparator implemented // above to sort by game symbol. RecordEnumeration re = recordStore.enumerateRecords(null, this, true); stockVector = getStockHelper(re); } catch (RecordStoreException rse) { System.out.println(rse); rse.printStackTrace(); }
| 如果Vector是空的,就会抛出一个例外,因为如果用户以前没有购买过一只股票,是不能使用这个方法的。
if ( stockVector.size() == 0) throw new StockException("No Stocks found in portfolio.");
return stockVector; }
| StockStorage
StockStorage对象是为了将Stock对象的存储封装到一个类中。所有其它的类都可接收和存储Stock对象到RecordStore中,而无需知道数据的存储格式。RecordStore本身也不知道Stock对象在数据库中的格式。如果你要存储购买股票的日期,你只需要修改StockStorage对象中的字段就可以了(无需对应用的其它部分作修改)。
Stock对象被存储为:
| 字段 |
描述 |
| Number of shares |
Integer字段 |
| Symbol |
UTF |
| Cost |
Integer字段 | 通过分离该对象的存储信息,其余的应用就无需关心最低级的细节。
writeStock()方法将会由Stock对象中接收值,并且传送它们到OutputStream。
public static void writeStock(Stock stock, DataOutputStream outputStream) throws IOException { // Push the number of shares into a byte array. outputStream.writeInt( stock.getNumShares() );
// Then push the stock name. outputStream.writeUTF( stock.getSymbol() );
// push the stock price outputStream.writeInt( stock.getPrice() ); }
| readStock()方法将会由InputStrean中得到值,并且创建一个新的Stock对象,以留给应用操作之用。
public static Stock readStock(DataInputStream inputStream) throws IOException { // read the number of shares int numShares = inputStream.readInt();
// read the stock symbol String symbol = inputStream.readUTF();
// read the stock price int price = inputStream.readInt();
// return a new Stock object return new Stock( symbol, numShares, price); }
| 结论:
本文介绍了在MIDP平台上创建应用时,你可以使用的不同组件。我集中介绍了两个组件:用户界面组件和data store组件。
在这个例子中,我将这两个组件结合在一起,并且讨论了这些组件和其它MIDP平台上的组件的交互。
在用户界面部分,我介绍了在MIDP设备上创建应用时,可使用的大部分UI组件。此外,我还介绍了一些便于进行MIDlet开发的概念,例如MVC方法,使用接口作封装,以及使用继承以作重用。
在数据存储(data store)方面,我介绍了MIDP平台上用作数据存储应用的主要组件。任何基于数据存储的应用将使用一个或者多个RecordStore对象,并且实现一个或者多个RecordFilters 和 RecordComparators。这些都是数据存储应用的基本组件。此外,我还介绍了主要功能的封装---可减少数据存储格式的负担,同时可为UI开发者提供一个干净的API。
对于这个简单的应用,你还可以作很多的改进,简化的原因主要是为了更好地理解其中的关键概念。在本系列的下一部分,我将会扩展这个应用,包括通过HTTP请求来得到股票信息和一个档案跟踪组件。
|
|
|
|