無線裝置協議受限 (HTTP 必選,Socket/UDP 可選),為追求最大相容性與伺服器端代碼復用,首選 HTTP/HTTPS。本文涵蓋客戶端/伺服器端協作準則、完整 HTTP 框架實作 (Observer 模式、進度條、取消機制)、GET/POST/Multipart 細節、Cookie Session 維持。
User-Agent 標頭 (含 MIDP 版本、應用版本),伺服器據此回應差異化內容。Content-Length,客戶端可預判能否處理。application/octet-stream),用 DataOutputStream 寫入,客戶端用 DataInputStream 讀取,極致效能。public interface HttpListener {
void onSetSize(int size); // 取得 Content-Length
void onFinish(byte[] data, int size); // 下載完成
void onProgress(int percent); // 進度更新 0-100
void onError(int code, String msg); // 錯誤 (含 HTTP 狀態碼、自訂碼)
}
public class HttpWaitUI extends Form implements CommandListener, HttpListener {
private Gauge gauge;
private Command cancelCmd;
private HttpThread downloader;
private Displayable nextScreen; // 下載完成後要顯示的畫面 (需實作 DataHandler)
public HttpWaitUI(String url, Displayable nextScreen) {
super("Connecting...");
this.nextScreen = nextScreen;
gauge = new Gauge("Progress", false, 100, 0);
cancelCmd = new Command("Cancel", Command.CANCEL, 0);
append(gauge);
addCommand(cancelCmd);
setCommandListener(this);
downloader = new HttpThread(url, this);
downloader.start();
}
public void commandAction(Command c, Displayable d) {
if (c == cancelCmd) {
downloader.cancel();
ControllerMIDlet.goBack(); // 返回上一畫面
}
}
// 實作 onSetSize, onProgress, onFinish, onError ...
// onFinish 中將資料傳給 nextScreen 並切換顯示
}
class HttpThread extends Thread {
private static final int MAX_LENGTH = 20 * 1024; // 20KB 預設緩衝
private boolean cancel = false;
private String url;
private HttpListener listener;
private byte[] buffer;
public HttpThread(String url, HttpListener listener) { ... }
public void cancel() { cancel = true; }
public void run() {
HttpConnection hc = null;
InputStream in = null;
try {
hc = (HttpConnection) Connector.open(url);
hc.setRequestMethod(HttpConnection.GET);
hc.setRequestProperty("User-Agent", "MyApp/1.0 MIDP/2.0");
int rc = hc.getResponseCode();
if (rc != HttpConnection.HTTP_OK) {
listener.onError(rc, hc.getResponseMessage()); return;
}
int size = (int) hc.getLength(); // -1 表示未知
listener.onSetSize(size);
in = hc.openInputStream();
buffer = (size != -1) ? new byte[size] : new byte[MAX_LENGTH];
int index = 0, read, percent = 0;
while (!cancel) {
int len = Math.min(128, buffer.length - index);
read = in.read(buffer, index, len);
if (read <= 0) break;
index += read;
if (size > 0) {
int p = index * 100 / size;
if (p != percent) { percent = p; listener.onProgress(percent); }
}
}
if (!cancel) {
if (size != -1 && index != size) listener.onError(102, "Length mismatch");
else listener.onFinish(buffer, index);
}
} catch (IOException e) {
listener.onError(101, e.getMessage());
} finally {
// 關閉 in, hc
}
}
}
public interface DataHandler {
void setData(DataInputStream in) throws IOException;
}
HttpWaitUI.onFinish 中將 byte[] 包裝為 ByteArrayInputStream → DataInputStream 傳給 nextScreen.setData(),再切換顯示。
// 使用者點擊閱讀新聞
public void commandAction(Command c, Displayable d) {
HttpWaitUI wait = new HttpWaitUI("https://server/news.do?id=1", new NewsUI());
ControllerMIDlet.forward(wait);
}
// NewsUI 實作 DataHandler
public class NewsUI extends Form implements DataHandler {
public void setData(DataInputStream in) throws IOException {
String title = in.readUTF();
Date date = new Date(in.readLong());
String text = in.readUTF();
append(new StringItem("Title", title));
append(new StringItem("Date", date.toString()));
append(text);
}
}
伺服器端依序寫入 UTF String、long (Date)、UTF String,客戶端對應讀取,零解析開銷。
hc.setRequestMethod(HttpConnection.POST);
byte[] body = "username=admin&password=1234".getBytes(); // 需 URL 編碼
hc.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
hc.setRequestProperty("Content-Length", String.valueOf(body.length));
OutputStream out = hc.openOutputStream();
out.write(body);
// 後續讀取回應同 GET
注意:MIDP 無 URLEncoder,需自行實作或引用輕量實現。
String BOUNDARY = "---------------------------7d4a6d158c9";
hc.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
// 建構 body:每欄位以 --BOUNDARY 分隔,結尾 --BOUNDARY--
// 範例略,詳見原文完整 StringBuffer 建構代碼
分隔符需足夠複雜避免與檔案內容衝突 (IE 使用隨機長字串)。
// 回應中解析 Set-Cookie
String cookie = hc.getHeaderField("Set-Cookie");
if (cookie != null) {
int idx = cookie.indexOf(';');
sessionId = cookie.substring(0, idx); // 取 JSESSIONID=xxx 或 ASPSESSIONID=xxx
}
// 後續請求附加
if (sessionId != null) {
hc.setRequestProperty("Cookie", sessionId);
}
支援 WebLogic、Resin、IIS/ASP.NET 等主流伺服器的 Cookie 格式。不建議使用 URL Rewriting (難解析)。