本文為 MIDP 系列最後篇,深入探討 javax.microedition.io 套件如何讓 MIDlet 與外部系統(Web 伺服器、Servlet、JSP)進行資料交換。以完整的投票系統為例,展示 HTTP GET/POST、XML 解析、MVC 架構在資源受限環境下的實作。
所有連線介面繼承自 Connection:
HttpConnection — HTTP/HTTPS 協定(MIDP 強制支援)StreamConnection — TCP Socket 串流DatagramConnection — UDP 資料包CommConnection — 序列埠FileConnection — 檔案系統 (JSR 75)// 請求設定
void setRequestMethod(String method); // GET/POST
void setRequestProperty(String key, String value);
// 回應取得
int getResponseCode();
String getResponseMessage();
String getHeaderField(String name);
long getHeaderFieldDate(String name, long def);
int getHeaderFieldInt(String name, int def);
long getLength(); // Content-Length
String getType(); // Content-Type
String getURL();
// 串流存取
InputStream openInputStream();
OutputStream openOutputStream();
根據 URL 協定前綴自動建立對應連線:
| 協定 | URL 範例 | 回傳介面 |
|---|---|---|
| HTTP | https://example.com/api | HttpConnection |
| HTTPS | https://example.com/api | HttpsConnection |
| Socket | socket://host:port | StreamConnection |
| Datagram (客戶端) | datagram://host:port | DatagramConnection |
| Datagram (伺服器) | datagram://:port | DatagramConnection |
VoteSummary (投票統計)、Vote (單一選項)VoteResults 繼承 Form 顯示結果VoteResults 實作 CommandListener 處理使用者投票ResourceUtility 封裝 HTTP 通訊與 XML 解析public class ResourceUtility {
private static final String VOTE_URL = "https://server/vote.jsp";
public static VoteSummary getVoteSummary() throws IOException {
String xml = backendComms(VOTE_URL, "");
return XMLUtil.parseVoteSummary(xml);
}
public static VoteSummary submitVote(Vote vote) throws IOException {
String params = "vote=" + vote.getValue();
String xml = backendComms(VOTE_URL, params);
return XMLUtil.parseVoteSummary(xml);
}
private static String backendComms(String url, String params) throws IOException {
HttpConnection conn = null;
DataInputStream dis = null;
try {
String fullUrl = url + (params.isEmpty() ? "" : "?" + params);
conn = (HttpConnection) Connector.open(fullUrl, Connector.READ_WRITE);
conn.setRequestMethod(HttpConnection.GET);
dis = new DataInputStream(conn.openInputStream());
StringBuffer sb = new StringBuffer();
int ch;
long len = conn.getLength();
if (len > 0) {
for (int i = 0; i < len; i++) {
if ((ch = dis.read()) != -1) sb.append((char)ch);
}
} else {
while ((ch = dis.read()) != -1) sb.append((char)ch);
}
return sb.toString();
} finally {
closeQuietly(dis);
closeQuietly(conn);
}
}
}
public class XMLUtil {
public static VoteSummary parseVoteSummary(String xml) throws IOException {
InputStreamReader reader = new InputStreamReader(
new ByteArrayInputStream(xml.getBytes()));
XmlParser parser = new XmlParser(reader);
Document doc = new Document();
doc.parse(parser);
Element root = doc.getElement("vote-example");
Element votes = root.getElement("votes");
String total = getText(votes, "number");
String avg = getText(votes, "average");
Element choices = votes.getElement("choices");
Vector list = new Vector<>();
for (int i = 0; i < choices.getChildCount(); i++) {
if (choices.getType(i) == Xml.ELEMENT) {
Element choice = choices.getElement(i);
String val = getText(choice, "value");
String name = getText(choice, "name");
String cnt = getText(choice, "number");
list.addElement(new Vote(val, name, cnt));
}
}
return new VoteSummary(total, avg, list);
}
private static String getText(Element parent, String name) {
return parent.getElement(name).getText();
}
}
<%@ page contentType="text/xml; charset=UTF-8" %>
<%
// 簡易記憶體儲存,實際應用請用資料庫
Map votes = (Map) application.getAttribute("votes");
if (votes == null) { votes = new HashMap<>(); application.setAttribute("votes", votes); }
String v = request.getParameter("vote");
if (v != null) votes.put(v, votes.getOrDefault(v, 0) + 1);
int total = votes.values().stream().mapToInt(Integer::intValue).sum();
double avg = choices.stream().mapToInt(c -> c.value * votes.getOrDefault(c.key,0)).average().orElse(0);
%>
<%= total %>
<%= avg %>
1 Option A <%= votes.getOrDefault("1",0) %>
2 Option B <%= votes.getOrDefault("2",0) %>
public class VoterMIDlet extends MIDlet implements ScreenCallback {
private VoteResults voteScreen;
protected void startApp() {
if (voteScreen == null) {
voteScreen = new VoteResults(this);
new Thread() {
public void run() {
try {
VoteSummary summary = ResourceUtility.getVoteSummary();
voteScreen.update(summary);
} catch (IOException e) {
voteScreen.showError("載入失敗: " + e.getMessage());
}
}
}.start();
}
Display.getDisplay(this).setCurrent(voteScreen);
}
// ... ScreenCallback.exit() 實作
}
MIDP 應用可在無網路覆蓋時離線運作(讀取本地 RMS 資料),恢復連線後同步資料至伺服器。此特性優於 WAP,適合郵件用戶端、離線地圖、待辦清單等應用。
早期 WTK 支援 HTTPS,但正式版曾移除。現代 Java ME 平台多已恢復支援,可實現金融交易、安全登入等需求。
VoteSummary) 純資料類別,無 UI 依賴VoteResults) 減少類別數量ResourceUtility) 獨立封裝,可複用XMLUtil) 隔離協定細節ScreenCallback) 解耦 MIDlet 與 UI這套架構展示了在嚴格資源限制下,如何建構清晰、可維護、可擴展的網路應用程式。