J2ME I/O 與聯網:概覽
Java 2 平台,袖珍版(Java 2 Platform,Micro Edition (J2ME))提供了把網絡 上可用的資源擴展到移動空間中的聯網功能。現在,在移動電話或掌上電腦獲取實時股票 報價或最新貸幣匯率是可能的。
javax.microedition.io 中的各個類和接口處理移動信息設備框架(Mobile Information Device Profile,MIDP)的聯網功能,MIDP 則是一個開發移動設備應用程 序的平台。(想了解更多有關 MIDP 的信息,請訪問下面的 參考資料部分,鏈接到我先 前已發表在 developerWorks上的關於這個主題的文章。)
另一方面,java.io 包給 MIDP 提供了輸入/輸出(input/output(I/O))功能。它 的各個類和接口為數據流提供了系統輸入和輸出。這個 J2ME 包是 Java 2 平台,標准版 (Java 2 Platform,Standard Edition(J2SE)) java.io 包的一個子集,它處理低級 別的數據 I/O。
J2ME 網絡連接性最關鍵的方面是移動設備與 Web 服務器間的通信。這種通信本質上 是客戶機/服務器機制,其中移動設備充當 Web 客戶機的角色並有能力與企業系統、數 據庫、公司內部網和因特網建立接口。
J2ME 聯網活動可以按照通信協議分為許多種類別。我們將在以下幾部分中依次討論每 一種類別。
低級別的 IP 聯網
這一類別涉及到套接字、數據報、串口和文件 I/O 通信。基於套接字的通信遵循面向 連接的 TCP/IP 協議。另一方面,基於數據報的通信遵循無連接的 UDP/IP 協議。UDP 為 應用程序提供了不必建立連接就能發送經過封裝的原始 IP 數據報的方法。面向連接的協 議需要源地址和目的地址,與此不同,數據報只需要目的地址。下面是數據報連接用來在 某端口接受數據報的一個 URI:
datagram://:1234
這裡是數據報連接用來在某端口將數據報發送到服務器的一個 URI:
datagram://123.456.789.12:1234
低級別的 IP 聯網還可以處理文件 I/O 並且能夠允許 MIDlet 注冊對本地串口進行網 絡訪問。
安全聯網
J2ME 中的安全聯網涉及到一些為了與基於 Web 的網絡服務進行安全通信而提供的額 外接口。這些安全接口由 IP 網絡上的 HTTPS 和 SSL/TLS 協議訪問提供。
HTTP 聯網
移動設備與 Web 服務器之間基於 HTTP(Hypertext Transfer Protocol,超文本傳輸 協議)進行通信。HTTP 是一個面向連接的請求-響應(request-response)協議,在這個 協議中,必須在發送請求之前設置請求的各參數。
圖 1 說明了移動設備與 Web 服務器間的通信機制。
圖 1. 移動設備與 Web 服務器間的連接機制
連接框架
J2ME 聯網旨在處理移動設備的廣泛頻譜不同的需要。同時,聯網系統必須是特定於設 備的。為了應付這些要求,J2ME 聯網引入了 通用連接框架(generic connection framework)的概念。
通用連接框架的設想是以 Java 接口的形式定義一些能夠覆蓋聯網和文件 I/O 的通用 方面的抽象。這個體系結構廣泛支持各種手持設備,而將這些接口的實際實現留給了各個 設備制造商。設備制造商可以根據其設備的實際功能選擇要在它的特定 MIDP 中實現哪個 接口。
由 Java 接口定義的通用方面分為以下幾種形式的基本通信類型:
基本串行輸入(由 javax.microedition.io.InputConnection 定義)
基本串行輸出(由 javax.microedition.io.OutputConnection 定義)
數據報通信(由 javax.microedition.io.DatagramConnection 定義)
用於客戶機-服務器(client-server)通信的套接字通信通知機制(由 javax.microedition.io.StreamConnectionNotifier 定義)
與 Web 服務器進行的基本 HTTP 通信(由 javax.microedition.io.HttpConnection 定義)
J2ME 中的 URL 處理
J2ME 中的 URL 處理涉及到從移動設備打開一個到 Web 服務器的連接並處理移動設備 到 Web 服務器間的數據 I/O。這個過程發生在下面的階段:
建立(Setup),此時尚未建立到服務器的連接。移動設備准備一堆請求參數並准備接 受和解釋隨後的響應。
已連接(Connected),此時連接已經被建立,請求參數已經被發送並在期待響應。
已關閉(Closed),此時連接已經被關閉。
J2ME 定義了 javax.microedition.io.Connector 類,這個類包含了用於創建所有連 接對象的各個靜態(static)方法。這一任務是通過根據平台名稱和所請求連接的協議動 態地查找一個類來完成的。
在 URL 處理中,Connector.open() 用來打開 URL;它返回一個 HttpConnection 對 象。Connector.open() 方法的字符串(string)參數是一個有效的 URL。URL 字符串由 於通信協議的不同而不同,下面的清單 1 到清單 5 演示了這一點。
清單 1. 調用基於 HTTP 的通信
Connection conn = Connector.open("http://www.yahoo.com");
清單 2. 調用基於流的套接字通信
Connection conn = Connector.open ("socket://localhost:9000");
清單 3. 調用基於數據報的套接字通信
Connection conn = Connector.open("datagram://:9000");
清單 4. 調用串口通信
Connection conn = Connector.open("comm:0;baudrate=9000");
清單 5. 調用文件 I/O 通信
Connection conn = Connector.open("file://myfile.dat");
Connector.open() 方法還可以接受訪問模式(值為 READ、WRITE 和 READ_WRITE ) 以及一個用來表示調用者想要得到超時通知的標志。
在安全的聯網中,當 https:// 連接字符串被訪問時,Connector.open() 就會返回 HttpsConnection 。當 ssl:// 連接字符串被訪問時,Connector.open() 就會返回 SecureConnection 。
無論使用哪一種類型的 URL,調用 Connector.open() 都會打開一個從 Connection 到 java.io.InputStream 的字節輸入流。這個方法用來讀取文件的每一個字符,一直讀 到文件末尾(以 -1 為標志)。如果拋出一個異常,連接和流就會被關閉。
與此相似,為了進行輸出,代表字節輸出流的 java.io.OutputStream 將被從 Connection 打開。
InputStream 和 OutputStream 分別與 java.io.DataInputStream 和 java.io.DataOutputStream 相對應。DataInputStream 讓應用程序用與機器無關的方式 從底層輸入流讀取基本的 Java 數據類型。java.io.DataOutputStream 讓應用程序用可 移植的方式把基本的 Java 數據類型寫到輸出流。
清單 6 說明了如何使用 HttpConnection 從 URL 輸入數據。
清單 6. 使用 HttpConnection 從 URL 輸入數據
String getViaHttpConnection(String url) throws IOException {
HttpConnection c = null;
InputStream is = null;
StringBuffer str = new StringBuffer();
try {
c = (HttpConnection)Connector.open(url);
// Getting the InputStream will open the connection
// and read the HTTP headers. They are stored until
// requested.
is = c.openInputStream();
// Get the length and process the data
int len = (int)c.getLength();
int ch;
while ((ch = is.read()) != -1) {
str.append((char)ch);
}
} finally {
if (is != null)
is.close();
if (c != null)
c.close();
}
一個貸幣兌換應用程序
我們將通過一個貸幣兌換應用程序來說明迄今為止所概述過的概念,這個應用程序將 會顯示美元(U.S. dollar,USD)和英鎊(British pound,GBP)之間最新的匯率。這個 應用程序還會顯示任意與當前日期和當前時間相關的信息。
這個應用程序的 UI 由一個表單( Form )和一個退出(exit)命令組成,該表單嵌 入了代表一個只顯示字符串的 StringItem ,退出命令用於完成調用時讓應用程序退出。
一旦啟動應用程序,URL 請求便已准備就緒。基本的貨幣符號被提供給請求。接下來 ,我們需要打開一個移動設備與 Web 服務器間的 URL 連接。打開一個 HttpConnection 並為數據輸入建立一個 InputStream 。所獲得的數據是一個字符流,這個多字符流附加 在 String 中。產生的 String 代表 HTML 輸出。由於我們的移動設備上的浏覽器不能顯 示 HTML,因而我們將解析 HTML String 來獲取貨幣值以及任何相關的信息。
我們的程序將在 HTML String 中搜索一個特定模式,即 USDGBP 。一旦確定了這個模 式的位置,搜索便查找十進制值。當獲得了小數點的位置後,各個數字值便會被檢索並以 適當的順序排列,從而獲取貨幣值。清單 7 說明了如何獲取貨幣值。
清單 7. 檢索貨幣值
String retVal = "";
int dec = 0;
int index = str.indexOf("USDGBP");
if (index != -1)
str = str.substring(index, str.length());
if ( (( dec = str.indexOf(".")) != -1) && (! (str.endsWith(".")))
&& Character.isDigit(str.charAt(dec+1)) )
{
String front = "";
int find = dec-1;
while (Character.isDigit(str.charAt(find)))
{
front += str.charAt(find);
find--;
}
retVal += new StringBuffer(front).reverse().toString();
retVal += ".";
String back = "";
int bind = dec+4;
while (Character.isDigit(str.charAt(bind)))
{
back += str.charAt(bind);
bind--;
}
retVal += new StringBuffer(back).reverse().toString();
}
相關信息也是通過查找某些特定的字符串模式來獲取的。一旦確定了數據在 HTML String 中的位置,一個偏移量便會被應用來獲取日期和時間。這個信息被與原先用於搜 索的字符串模式附加在一起。清單 8 說明了如何獲取相關信息。
清單 8. 檢索相關信息
// Get the time of Currency Exchange and Related information
int timeIndex = str.indexOf("U.S. Markets Closed.");
String retTime = "";
if (timeIndex != -1)
{
retTime = str.substring(timeIndex-34, timeIndex);
retTime += " U.S. Markets Closed ";
}
清單 9 包括貨幣兌換應用程序的全部代碼。
清單 9. 完整的貨幣兌換示例
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.io.*;
import java.io.*;
import java.lang.*;
import java.util.*;
//A first MIDlet with simple text and a few commands.
public class CurrencyExchange extends MIDlet
implements CommandListener {
//The exit commands
private Command exitCommand;
//The display for this MIDlet
private Display display;
Form displayForm;
public CurrencyExchange() {
display = Display.getDisplay(this);
exitCommand =
new Command("Exit", Command.SCREEN, 1);
}
// Start the MIDlet by creating the Form and
// associating the exit command and listener.
public void startApp() {
displayForm = new Form("Exchange Rate");
displayForm.addCommand(exitCommand);
displayForm.setCommandListener(this);
try
{
String result = getViaHttpConnection
("http://finance.yahoo.com/m5?a=1&s=USD&t=GBP");
displayForm.append(" " + result);
}
catch (Exception exc)
{
exc.printStackTrace();
}
display.setCurrent(displayForm);
}
// Pause is a no-op because there is no background
// activities or record stores to be closed.
public void pauseApp() { }
// Destroy must cleanup everything not handled
// by the garbage collector.
// In this case there is nothing to cleanup.
public void destroyApp(boolean unconditional) { }
// Respond to commands. Here we are only implementing
// the exit command. In the exit command, cleanup and
// notify that the MIDlet has been destroyed.
public void commandAction(
Command c, Displayable s) {
if (c == exitCommand) {
destroyApp(false);
notifyDestroyed();
}
}
String parse(String str)
{
// Get the time of Currency Exchange and Related information
int timeIndex = str.indexOf("U.S. Markets Closed.");
String retTime = "";
if (timeIndex != -1)
{
retTime = str.substring(timeIndex-34, timeIndex);
retTime += " U.S. Markets Closed ";
}
String retVal = "";
int dec = 0;
int index = str.indexOf("USDGBP");
if (index != -1)
str = str.substring(index, str.length());
if ( (( dec = str.indexOf(".")) != -1) && (!(str.endsWith(".")))
&& Character.isDigit(str.charAt(dec+1)) )
{
String front = "";
int find = dec-1;
while (Character.isDigit(str.charAt(find)))
{
front += str.charAt(find);
find--;
}
retVal += new StringBuffer(front).reverse().toString();
retVal += ".";
String back = "";
int bind = dec+4;
while (Character.isDigit(str.charAt(bind)))
{
back += str.charAt(bind);
bind--;
}
retVal += new StringBuffer(back).reverse().toString();
}
System.out.println(retVal);
return "USD/GBP " + retVal + " at " + retTime ;
}
String getViaHttpConnection(String url) throws IOException {
HttpConnection c = null;
InputStream is = null;
StringBuffer str = new StringBuffer();
try {
c = (HttpConnection)Connector.open(url);
// Get the ContentType
String type = c.getType();
// Getting the InputStream will open the connection
// and read the HTTP headers. They are stored until
// requested.
is = c.openInputStream();
// Get the length and process the data
int len = (int)c.getLength();
int ch;
while ((ch = is.read()) != -1) {
str.append((char)ch);
}
} finally {
if (is != null)
is.close();
if (c != null)
c.close();
}
// Before returning, the resultant String should be parsed to get Exchange Rate
String val = parse(str.toString());
System.out.println(val);
return val;
}
}
貨幣值和其它相關信息不久便出現在移動設備的用戶界面上。圖 2 說明了顯示的結果 。
圖 2. 在手持設備上運行的貨幣兌換應用程序
結束語
J2ME 有一種獨特的在受約束環境中處理數據輸入/輸出的精簡方式。有了這種功能, 支持 Java 的移動配件就不再是簡單的通信設備了,而是能夠充當微型浏覽器的設備,它 可以在移動中提供重要的信息。