隨著功能手機、早期智慧手機普及,MIDP 2.0 成為移動 Java 開發標準。受限於 CLDC/MIDP 沙箱與有限資源,javax.microedition.io 提供的 Generic Connection Framework (GCF) 是唯一網路 API。本文詳細講解 HttpConnection 實現 GET/POST 請求、資料流處理、MIDlet 完整範例及常見陷阱。
Connection (介面)
├─ StreamConnection
│ ├─ ContentConnection
│ │ └─ HttpConnection ← MIDP 強制實現
│ └─ ...
└─ DatagramConnection
關鍵點:
Connector.open(url) 為統一進入點,返回具體 Connection 子型別HttpConnection 繼承 ContentConnection,支援 GET/POST/HEAD、請求標頭、回應碼、Content-LengthIOException,必須捕獲處理public String sendHttpGet(String url) throws IOException {
HttpConnection conn = null;
DataInputStream dis = null;
StringBuffer response = new StringBuffer();
try {
// 1. 建立連線(預設 GET、READ_ONLY)
conn = (HttpConnection) Connector.open(url);
// 2. 獲取輸入流讀取回應
dis = new DataInputStream(conn.openInputStream());
int ch;
while ((ch = dis.read()) != -1) {
response.append((char) ch);
}
} finally {
// 3. 釋放資源(順序:流 → 連線)
if (dis != null) dis.close();
if (conn != null) conn.close();
}
return response.toString();
}
public String sendHttpPost(String url, String postData) throws IOException {
HttpConnection conn = null;
DataInputStream dis = null;
DataOutputStream dos = null;
StringBuffer response = new StringBuffer();
try {
// 1. 指定 READ_WRITE 模式
conn = (HttpConnection) Connector.open(url, Connector.READ_WRITE);
// 2. 設定 POST 方法
conn.setRequestMethod(HttpConnection.POST);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Content-Length", String.valueOf(postData.getBytes().length));
// 3. 寫入請求體
dos = conn.openDataOutputStream();
dos.write(postData.getBytes());
dos.flush(); // 部分實現需 flush 觸發發送
// 4. 讀取回應
dis = new DataInputStream(conn.openInputStream());
int ch;
while ((ch = dis.read()) != -1) {
response.append((char) ch);
}
} finally {
if (dos != null) dos.close();
if (dis != null) dis.close();
if (conn != null) conn.close();
}
return response.toString();
}
| 步驟 | GET | POST |
|---|---|---|
| Connector.open 模式 | 預設 (READ) | Connector.READ_WRITE |
| 設定請求方法 | 無需(預設 GET) | conn.setRequestMethod(HttpConnection.POST) |
| 發送資料 | 無 | DataOutputStream 寫入 byte[] |
| Content-Type | 通常不需設定 | 必須設定 (如 application/x-www-form-urlencoded) |
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.io.*;
import java.io.*;
public class HttpTestMidlet extends MIDlet implements CommandListener {
private Display display;
private Form inputForm, resultForm;
private TextField urlField;
private StringItem resultField;
private List methodList;
private Command cmdSend, cmdExit, cmdBack;
private static final String DEFAULT_URL = "https://httpbin.org/get"; // 測試端點
public HttpTestMidlet() {
display = Display.getDisplay(this);
// 輸入畫面
inputForm = new Form("HTTP Client");
urlField = new TextField("URL:", DEFAULT_URL, 256, TextField.URL);
inputForm.append(urlField);
cmdSend = new Command("Send", Command.OK, 1);
cmdExit = new Command("Exit", Command.EXIT, 2);
inputForm.addCommand(cmdSend);
inputForm.addCommand(cmdExit);
inputForm.setCommandListener(this);
// 方法選擇
methodList = new List("Select Method", List.IMPLICIT);
methodList.append("GET", null);
methodList.append("POST", null);
methodList.setCommandListener(this);
// 結果畫面
resultForm = new Form("Response");
cmdBack = new Command("Back", Command.BACK, 1);
resultForm.addCommand(cmdBack);
resultForm.setCommandListener(this);
}
public void startApp() {
display.setCurrent(inputForm);
}
public void commandAction(Command c, Displayable d) {
if (c == cmdSend) {
display.setCurrent(methodList);
} else if (c == cmdExit) {
destroyApp(true);
notifyDestroyed();
} else if (c == cmdBack) {
display.setCurrent(inputForm);
} else if (d == methodList && c == List.SELECT_COMMAND) {
String url = urlField.getString();
String result;
if (methodList.getSelectedIndex() == 0) { // GET
result = sendHttpGet(url);
} else { // POST
result = sendHttpPost(url, "param1=value1¶m2=value2");
}
resultForm.deleteAll();
resultForm.append(new StringItem(null, result));
display.setCurrent(resultForm);
}
}
// ... sendHttpGet / sendHttpPost 方法同上 ...
public void pauseApp() {}
public void destroyApp(boolean unconditional) {}
}
| 問題 | 現象 | 解決方案 |
|---|---|---|
| HTTPS 失敗 | IOException: Certificate exception | 手機需匯入 CA 憑證;或使用 HTTP 端點測試 |
| 大資料截斷 | 回應不完整 | 分塊讀取;檢查 Content-Length;避免單次 read() 全量讀取 |
| POST 參數亂碼 | 中文參數伺服端解析錯誤 | URLEncoder.encode(value, "UTF-8") 編碼;設定 Content-Type charset |
| 連線超時 | 長時間無響應 | MIDP 無標準超時 API;需自行執行緒監控或使用 SocketConnection 實現 |
| 重定向處理 | 301/302 無自動跳轉 | 讀取 Location 標頭,手動發起新請求 |
Accept-Encoding: gzip,解壓回應體J2ME/MIDP 歷史使命已終,但其 GCF 設計思想(統一連線介面、工廠模式、資源受限下的流式處理)深深影響後來的 Android HttpURLConnection、OkHttp、iOS NSURLSession。理解 MIDP 網路編程,有助於掌握移動網路通訊的本質約束:頻寬、延遲、電量、記憶體四重限制下的可靠傳輸。