J2ME 通用聯網框架 (Generic Connection Framework, GCF) 以 javax.microedition.io.Connector 為唯一入口,透過協議字串動態建立連線:
// HTTP 連線
HttpConnection http = (HttpConnection) Connector.open("https://example.com/api");
// Socket 連線 (MIDP 2.0 選配)
SocketConnection socket = (SocketConnection) Connector.open("socket://host:port");
// Datagram (UDP) 連線 (MIDP 2.0 選配)
DatagramConnection dgram = (DatagramConnection) Connector.open("datagram://:port");
void postViaHttpConnection(String url) throws IOException {
HttpConnection conn = null;
InputStream is = null;
OutputStream os = null;
try {
conn = (HttpConnection) Connector.open(url);
// 1. 設定請求屬性
conn.setRequestMethod(HttpConnection.POST);
conn.setRequestProperty("User-Agent", "Profile/MIDP-2.0 Configuration/CLDC-1.1");
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Accept", "text/plain");
// 2. 發送請求體
os = conn.openOutputStream();
String payload = "action=login&user=test&pass=1234";
os.write(payload.getBytes("UTF-8")); // 明確指定編碼
os.flush();
// 3. 取得回應碼 (觸發實際連線與請求發送)
int rc = conn.getResponseCode();
if (rc != HttpConnection.HTTP_OK) {
throw new IOException("HTTP Error: " + rc);
}
// 4. 處理回應
is = conn.openInputStream();
String type = conn.getType(); // Content-Type
int len = (int) conn.getLength();
if (len > 0) {
byte[] data = new byte[len];
int read = 0, actual;
while (read < len && (actual = is.read(data, read, len - read)) != -1) {
read += actual;
}
processResponse(data);
} else {
// Chunked 或未知長度:流式讀取
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int n;
while ((n = is.read(buf)) != -1) baos.write(buf, 0, n);
processResponse(baos.toByteArray());
}
} catch (ClassCastException e) {
throw new IllegalArgumentException("Non-HTTP URL");
} finally {
// 5. 關閉資源 (逆序關閉)
if (is != null) try { is.close(); } catch (IOException ignored) {}
if (os != null) try { os.close(); } catch (IOException ignored) {}
if (conn != null) try { conn.close(); } catch (IOException ignored) {}
}
}
鐵律:任何網絡 I/O (openConnection, read, write) 絕不可在事件分發執行緒 (EDT) 執行,否則 UI 凍結、甚至被 AMS 強制終止。
public class NetworkTask implements Runnable {
private final String url;
private final byte[] payload;
private final NetworkListener listener;
private final Display display;
private final Displayable waitScreen; // 等待動畫畫面
public NetworkTask(String url, byte[] payload, NetworkListener listener,
Display display, Displayable waitScreen) {
this.url = url;
this.payload = payload;
this.listener = listener;
this.display = display;
this.waitScreen = waitScreen;
}
public void run() {
// 1. 顯示等待畫面 (需在 EDT 執行)
display.callSerially(() -> display.setCurrent(waitScreen));
try {
byte[] response = doPost(url, payload);
// 2. 回調結果 (在 EDT 執行)
display.callSerially(() -> listener.onSuccess(response));
} catch (IOException e) {
display.callSerially(() -> listener.onError(e));
} finally {
// 3. 恢復原畫面 (可選)
// display.callSerially(() -> display.setCurrent(previousScreen));
}
}
private byte[] doPost(String url, byte[] payload) throws IOException {
// 上述 HttpConnection 實作...
return responseBytes;
}
}
interface NetworkListener {
void onSuccess(byte[] data);
void onError(IOException e);
}
// 在命令處理或遊戲邏輯中
new Thread(new NetworkTask(url, payload, listener, display, waitCanvas)).start();
客戶端與伺服器必須嚴格對應讀寫順序與類型:
// 客戶端發送
DataOutputStream dos = new DataOutputStream(os);
dos.writeUTF("login"); // String
dos.writeInt(userId); // int
dos.writeBoolean(true); // boolean
dos.writeLong(timestamp); // long
dos.flush();
// 伺服器接收 (順序、類型完全一致)
DataInputStream dis = new DataInputStream(is);
String action = dis.readUTF();
int userId = dis.readInt();
boolean flag = dis.readBoolean();
long ts = dis.readLong();
手機 → WAP 閘道 (運營商) → Internet Web Server。閘道處理協議轉換 (WSP↔HTTP)、壓縮、快取。開發者無需關心閘道,但需注意:
Content-Length 不可靠。| 錯誤類型 | 處理策略 |
|---|---|
| 連線超時 / 拒絕 | 指數退避重試 (1s, 2s, 4s...),最多 3 次,記錄離線隊列 |
| HTTP 4xx | 客戶端錯誤 (參數/認證),提示用戶修正,不重試 |
| HTTP 5xx | 伺服器錯誤,延遲重試,可切換備用 IP |
| IO 錯誤 (斷網) | 偵測網絡狀態變化 (JSR 172/205),恢復後自動重發隊列 |