在J2ME平台上構建你的郵件程序
Jacky Pan
Table of Contents
1. 教程的介紹和程序的安裝
2. 程序的結構
3. 界面的設計
4. 賬戶的管理
5. MIDlet和Servlet的網絡連接
6. Servlet和JavaMail
7. 簡單的XML
8. 小結
1.教程的介紹和程序的安裝
本教程講述了如何在J2ME平台上編寫一個簡單的郵件應用程序,包括界面的設計,郵件的發送/接受,郵件賬戶的創建/修改/刪除,後台Servlet的編寫。
為了運行本教程所帶的演示程序,您需要安裝下列軟件:
1. WTK2.0 (Java.sun.com)
2. Apache Tomcat (www.apache.org)
安裝和運行示例程序的步驟:
1. 從http://groups.yahoo.com/group/OriTec/files/下載MicroMail Beta.zip(包括了源代碼和二進制文件)
2. 解壓MicroMail Beta.zip至$TMP
3. 在$WTK/aPPS下建一新目錄MicroMail
4. 拷貝$TMP/src/clIEnt/* 至 $WTK/aPPS/MicroMail/src/
5. 拷貝$/TMP/bin/server/mail.war至$TOMCAT/webaPPS/
6. 運行Tomcat
7. 運行WTK2.0, “Open Project” 並選中MicroMail
8. 設置URL為MailAgent的地址http://server/mail/MailAgent
2. 程序的結構
采用ClIEnt-Web Server-Mail Server三層架構,如圖1。
MIDlet
(Cell Phone)
Servlet
(Web Server)
Mail Server
My Application
Figure 1
Cell Phone將請求(接受/發送郵件)傳給Web Server,Web Server將這些http請求轉換成對POP3或 SMTP Server的請求。POP3/SMTP Server執行相應的請求,並將相應通過Web Server返回給Cell Phone.
客戶端(PDA/手機)為J2ME平台上的程序。MIDP2提供了一些基本網絡連接的API。利用這些API可以使得J2ME程序可以向遠端發出Http請求,並接受響應,傳遞數據流。
MailAgent 為Servlet,用來接收來自客戶端的請求,並調用Java Mail API,將這些請求轉變成對遠端Mail Server 的請求,同時將Mail Server的響應傳給客戶端。
那麼為什麼要采用這樣的架構了?這是因為MIDP2.0只支持HTTP協議, 而不支持POP3 和 SMTP等其它應用層協議,而J2EE提供了完整的Java Mail的API,所以考慮通過一個Servlet將Http請求轉換成POP3或SMTP請求。另一個原因是,很多運營商可能只提供有限的網絡訪問的能力,而通過一個agent則提供了程序部署的靈活性。
下面簡單介紹一下源碼的結構,在客戶端,ui包中的類定義程序的用戶界面,utility包中的類定義了數據庫的操作,網絡的連接,XML的解析等,mail包中的類定義了郵件賬戶,郵件的頭。
ui package:
AccountForm.Java
AccountsList.Java
Confirm.Java
ConfirmListener.Java
MailMIDlet.Java
MessageList.Java
ProgressForm.Java
SendMessage.Java
WriteContent.Java
utility package:
DBOperator.Java
HeadParser.Java
NetAdapter.Java
Networker.Java
ParserListener.Java
mail package:
MailAccount.Java
MessageHead.Java
服務器端只有一個文件MailAgent.Java,包含了一個servlet,用來做midlet和郵件服務器的橋梁。
3. 界面的設計
MIDP2.0提供了大量的API供開發者創建和控制用戶界面。在包Javax.microedition.lcdui中提供了List, Form, TextBox, Alert等Screen組件,在Form中可以包含一系列Item,如ChoiceGroup, TextFIEld, StringItem等。包中還提供了Command組件,以及相應的Listener。
源文件ui/MailMIDlet.Java定義了程序的入口以及MicroMail的主頁面。如圖2主頁面是一個List,顯示了幾個功能模塊,包括添加賬戶,修改/刪除賬戶,接受郵件,發送郵件。
Figure 2
public MailMIDlet()
{
display = Display.getDisplay(this);
/* 創建List */
mainList = new List("MicroMail0.1", Choice.IMPLICIT);
/* 創建兩個控制按鈕OK和Exit */
cmOK = new Command("OK", Command.OK, 1);
cmExit = new Command("Exit", Command.EXIT, 1);
/* 向mainlist中添加內容 */
mainList.append("Add Account", null);
mainList.append("Edit Account", null);
mainList.append("Receive Message", null);
mainList.append("Send Message", null);
/* 為mainlist添加Command */
mainList.addCommand(cmOK);
mainList.addCommand(cmExit);
/* MailMIDlet實現了CommandListener接口,可以作為監聽器 */
mainList.setCommandListener(this);
……
}
通過AccountForm(源文件ui/AccountForm.Java),用戶可以添加和修改郵件賬戶,如圖3。
Figure 3a Figure 3b
AccountForm包含了6個TextFIEld,分別顯示郵件賬戶的6個屬性。account為賬戶名,也是賬戶的唯一標識,不可重名。address為郵件的地址,如
[email protected]。 user, passWord是在郵件服務商注冊的用戶名和密碼。pop3, smtp是pop3服務器和smtp服務器的名字或地址。
private void setContent(MailAccount Macc)
{
account = new TextField("Account: ", "", 20, TextFIEld.ANY);
address = new TextField("Address; ", "", 40, TextFIEld.EMAILADDR);
user = new TextField("User Name: ", "", 20, TextFIEld.ANY);
password = new TextField("Password: ", "", 20, TextFIEld.PASSWord);
pop3 = new TextField("POP3 Server: ", "", 20, TextFIEld.ANY);
smtp = new TextField("SMTP Server: ", "", 20, TextFIEld.ANY);
if (Macc != null)
{
account.setString(Macc.accountName);
address.setString(Macc.address);
user.setString(Macc.username);
password.setString(Macc.passWord);
pop3.setString(Macc.POP3Server);
smtp.setString(Macc.SMTPServer);
}
append(account);
append(address);
append(user);
append(passWord);
append(pop3);
append(smtp);
}
其它頁面與AccountForm類似,在此不再贅述,請大家參照源代碼和MIDP API的文檔。
4. 賬戶的管理
郵件賬戶的創建,修改,刪除都涉及到對數據記錄的存取。MIDP提供了一種叫做記錄管理系統(Record Management System)的機制來存儲和訪問數據。
Javax.microedition.rms.RecordStore提供了一些API來操作這個系統。靜態方法openRecordStore用來打開或創建一個RecordStore對象。方法addRecord, getRecord, deleteRecord, setRecord分別用來添加,訪問,刪除或修改RecordStore對象中的記錄。
utility/DBOperator.Java中封裝了這些方法,從而實現添加,修改,刪除郵件賬戶的操作。以添加帳戶為例,下面的addRecord方法用來向RecordStore中添加一個記錄,同時把這個記錄所表示的郵件賬戶加到一個accounts中,accounts是一個Vecotr,用來存儲系統中當前的賬戶。參數str包含了賬戶的所有信息,如賬戶名,地址,用戶名,密碼,pop3,smtp等,它們之間用空格隔開。
public void addRecord(String str)
{
int id;
byte[] rec = str.getBytes();
String record = str;
try
{
/* 向RecordStore中添加記錄 */
id = rs.addRecord(rec, 0 , rec.length);
/* 同時把帳戶添加到accounts向量中 */
MailAccount mailacc = MailAccount.createMailAccountFromString(id, str);
accounts.addElement(mailacc);
}
catch(RecordStoreException rse)
{
rse.printStackTrace();
}
}
5. MIDlet和Servlet的網絡連接
MIDP的網絡API在包Javax.microedition.io中定義,其中HttpConnection提供了對HTTP協議的支持。
在文件utility/Networker.Java中通過調用這些網絡API實現了接收當前信箱中郵件列表setMessageList,接受某一郵件的內容receiveMessage,發送郵件sendMessage等功能。以sendMessage為例。
public void sendMessage(final String url, final String formData)//send a message
{
/* 創建新的進程 */
Thread t = new Thread()
{
public void run()
{
HttpConnection http = null;
byte[] data = formData.getBytes();
try
{
/* 打開並返回一個HTTP連接 */
http = (HttpConnection) Connector.open(url);
......
/* 設置HTTP請求頭 */
http.setRequestMethod(HttpConnection.POST);
http.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
http.setRequestProperty("User-Agent",
"Profile/MIDP-1.0 Configuration/CLDC-1.0");
http.setRequestProperty("Content-Language", "en-US");
http.setRequestProperty("Accept", "application/octet-stream");
http.setRequestProperty("Connection", "close"); // optional
http.setRequestProperty("Content-Length", Integer.toString(data.length));
......
/* 打開輸出流 */
OutputStream os = http.openOutputStream();
/* 寫郵件數據 */
os.write(data);
/* 關閉輸出流 */
os.close();
}
catch (IOException ioe)
{
......
}
finally
{
try
{
/* 關閉連接 */
if (http != null) http.close();
}
catch (IOException ignored) {}
}
}
};
/* 啟動進程 */
t.start();
}
MIDlet通過HTTP連接向Servelet發出接受或發送郵件的請求,Servlet根據不同的請求向郵件服務器發出相應的請求,並將返回結果傳給MIDlet。
6. Servlet和JavaMail
J2EE中提供了對郵件相關協議的支持,包javax.mail和包javax.mail.internet 中定義了JavaMail API。下面是MailAgent.Java中Servlet處理接受郵件列表請求的代碼片斷。
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException
{
PrintWriter out = response.getWriter();
String typeStr = request.getParameter("type");
int type = Integer.parseInt(typeStr);
String pop3Server;
String username;
String passWord;
PropertIEs props;
String provider;
switch (type)
{
case RECEIVE_LIST:
/* 提取參數pop3服務器,用戶名,密碼 */
pop3Server = request.getParameter(paramPOP3);
username = request.getParameter(paramName);
passWord = request.getParameter(paramPass);
if (pop3Server == null || username == null || passWord == null)
{
out.print(STATUS_BAD);//XML?
return;
}
props = new PropertIEs();
provider = "pop3";
try
{
/* 以指定的用戶名和密碼連接pop3服務器 */
Session session = Session.getDefaultInstance(props, null);
Store store = session.getStore(provider);
store.connect(pop3Server, username, passWord);
/* 得到INBOX收件箱 */
Folder inbox = store.getFolder("INBOX");
if (inbox == null)
{
out.print(STATUS_BAD);//XML?
return;
}
/* 打開收件箱 */
inbox.open(Folder.READ_ONLY);
/* 得到收件箱中的郵件列表,並寫到 XML中 */
writeXML(inbox.getMessages(), out);
inbox.close(false);
store.close();
}
catch(Exception e)
{
out.println(e.getMessage());
e.printStackTrace();
}
out.close();
break;
case RECEIVE_MESSAGE:
......
break;
case SEND_MESSAGE:
......
break;
}
}
J2EE程序的編譯和部署請大家參照相關的書籍或文檔。http://www.tusc.com.au/tutorial/Html/ 的<<Tutorial for building J2EE Applications using JBOSS and ECLIPSE>>是一篇不錯的文檔。
7. 簡單的XML
在MIDlet接受郵件時,首先向Servlet請求傳送郵箱中當前郵件的列表。列表中包括了郵件的頭部信息,包括發送者的地址,郵件的主題,發送的時間等,如圖4。
Figure 4
在郵件主題中可能包括任何字符,所以沒有辦法用某一特殊字符分隔這些信息,而XML正好適合傳輸這種具有特定格式的信息。在Servlet端,把有用的郵件頭部信息作為XML的元素寫到輸出流中。
private void writeXML(Message[] messages, PrintWriter out)
{
out.println("<?XML version=\"1.0\"?>");
out.println("<mail>");
if (messages == null)
{
out.println("<error>No Mail</error>");
}
try
{
int j = 0;
for (int i = messages.length -1; i >= 0; i--)
{
out.println("<message>");
/* 寫郵件頭 */
out.println("<from><![CDATA[" + InternetAddress.toString(messages[i].getFrom()) + "]]></from>");
out.println("<subject><![CDATA[" + messages[i].getSubject() + "]]></subject>");
out.println("<date>" + messages[i].getSentDate().toLocaleString() + "</date>");
out.println("<index>" + i + "</index>");
out.println("</message>");
j++;
if (j > 9)
{
/* 一次只看10個郵件 */
break;
}
}
}
catch(MessagingException me)
{
out.println("<error>" + me.toString() + "</error>");
}
out.println("</mail>");
}
在MIDlet端再解析這個XML,在J2ME平台上有許多免費的XML Parser,kxml就是其中的一個。可以從 http://kxml.enhydra.org/ 下載kXML 1.21的源代碼,jar文件以及API文檔。
下面是utility/HeadParser.Java中處理XML的代碼片斷
public void parse(InputStream in) throws IOException
{
Reader reader = new InputStreamReader(in);
XmlParser parser = new XMLParser(reader);
ParseEvent pe = null;
parser.skip();
/* 讀一個名字為mail的event */
parser.read(XML.START_TAG, null, "mail");
boolean trucking = true;
boolean first = true;
while (trucking)
{
/* 讀取下一個event */
pe = parser.read();
if (pe.getType() == XML.START_TAG)
{
/* 得到event的名字 */
String name = pe.getName();
if (name.equals("message"))
{
String from = null;
String subject = null;
String date = null;
int index = -1;
while ((pe.getType() != XML.END_TAG) ||
(pe.getName().equals(name) == false))
{
pe = parser.read();
if (pe.getType() == XML.START_TAG &&
pe.getName().equals("subject"))
{
pe = parser.read();
/* 得到event的內容 */
subject = pe.getText();
}
else if (pe.getType() == XML.START_TAG &&
pe.getName().equals("from"))
{
pe = parser.read();
from = pe.getText();
}
else if (pe.getType() == XML.START_TAG &&
pe.getName().equals("date"))
{
pe = parser.read();
date = pe.getText();
}
else if (pe.getType() == XML.START_TAG &&
pe.getName().equals("index"))
{
pe = parser.read();
index = Integer.parseInt(pe.getText());
}
}
/* 把郵件頭交給監聽器處理 */
headListener.itemParsed(from, subject, date, index);
}
else //Non Message block
{
while ((pe.getType() != XML.END_TAG) ||
(pe.getName().equals(name) == false))
pe = parser.read();
}
}
if (pe.getType() == XML.END_TAG &&
pe.getName().equals("mail"))
{
trucking = false;
}
}
具體的API的用法請大家參照kxml的文檔。JSR172 (J2ME Web Services Specification)提出了在J2ME平台上處理XML的規范,有興趣的朋友可以到jcp的網站 (http://www.jcp.org) 上看看,但目前可能還沒有廠商或組織的實現。
8. 小結
本文介紹了J2ME平台上郵件程序的編寫,涉及的知識點有:
1. J2ME的UI
2. Record Store
3. J2ME的網絡連接 / J2ME 和J2EE之間數據的傳遞
4. Parsing XML in J2ME
5. 簡單的Servlet
6. Java Mail APIs
參考資料有:
1. MIDP2.0 Spec, http://www.jcp.org
2. <<Core J2ME>>, http://www.coreJ2ME.com/
3. <<J2ME in Nutshell>>, http://www.oreilly.com
4. Tutorial for building J2EE Applications using JBOSS and ECLIPSE, http://www.tusc.com.au/tutorial/Html/