Java 2 Micro Edition (J2ME) 為資源受限的移動設備提供網絡能力。核心包:
javax.microedition.io — **Generic Connection Framework (GCF)**,統一連接接口java.io — 精簡 I/O 流支持 (J2SE 子集)| 接口 | 功能 | 典型協議 |
|---|---|---|
InputConnection |
基本串行輸入 | 文件、串口 |
OutputConnection |
基本串行輸出 | 文件、串口 |
StreamConnection |
流式雙向通信 | TCP Socket (socket://) |
StreamConnectionNotifier |
服務端監聽 | TCP Server Socket |
DatagramConnection |
無連接數據報 | UDP (datagram://) |
HttpConnection |
HTTP 客戶端 | HTTP/HTTPS (https://, https://) |
HttpsConnection |
HTTPS 安全連接 | SSL/TLS over HTTP |
SecureConnection |
通用安全連接 | SSL/TLS (ssl://) |
CommConnection |
串口通信 | RS-232, Bluetooth (comm:) |
// 核心工廠方法:根據 URL 協議前綴動態加載實現類
Connection conn = Connector.open(String url, int mode, boolean timeout);
// 支持的 URL 模式:
Connector.open("https://www.example.com"); // HTTP GET
Connector.open("socket://192.168.1.100:8080"); // TCP Client
Connector.open("socket://:9000"); // TCP Server (監聽)
Connector.open("datagram://:1234"); // UDP 接收
Connector.open("datagram://10.0.0.1:1234"); // UDP 發送
Connector.open("comm:0;baudrate=9600"); // 串口
Connector.open("file:///data/config.txt"); // 文件 I/O
Connector.open("https://secure.example.com"); // HTTPS
Connector.open("ssl://host:443"); // 原始 SSL
三階段狀態機:
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ Setup │────▶│ Connected │────▶│ Closed │
│ (準備請求) │ │ (發送/接收) │ │ (關閉釋放) │
└─────────────┘ └──────────────┘ └─────────────┘
│ │ │
▼ ▼ ▼
open() openInputStream() close()
setRequestMethod() openOutputStream()
setRequestProperty() read()/write()
String getViaHttpConnection(String url) throws IOException {
HttpConnection conn = null;
InputStream is = null;
StringBuffer response = new StringBuffer();
try {
// 1. 建立連接 (Setup 階段)
conn = (HttpConnection) Connector.open(url);
// 2. 可選:設置請求屬性
conn.setRequestMethod(HttpConnection.GET);
conn.setRequestProperty("User-Agent", "J2ME-Agent/1.0");
conn.setRequestProperty("Accept", "text/html");
// 3. 獲取輸入流 → 自動發送請求、接收響應頭
is = conn.openInputStream();
// 4. 讀取響應體
int ch;
while ((ch = is.read()) != -1) {
response.append((char) ch);
}
// 5. 可選:獲取響應信息
int status = conn.getResponseCode(); // HTTP 狀態碼
String type = conn.getType(); // Content-Type
long len = conn.getLength(); // Content-Length
} finally {
// 6. 必須關閉資源 (順序重要)
if (is != null) is.close();
if (conn != null) conn.close();
}
return response.toString();
}
https://finance.yahoo.com/m5?a=1&s=USD&t=GBP)// 從 HTML 中提取匯率: 搜索 "USDGBP" 模式,定位小數點,提取數字
String parseRate(String html) {
String rate = "";
int idx = html.indexOf("USDGBP");
if (idx != -1) {
html = html.substring(idx);
int dot = html.indexOf(".");
if (dot != -1 && !html.endsWith(".") && Character.isDigit(html.charAt(dot+1))) {
// 向前提取整數部分
String front = "";
for (int i = dot-1; i >= 0 && Character.isDigit(html.charAt(i)); i--) {
front = html.charAt(i) + front;
}
// 向後提取小數部分 (取 2-4 位)
String back = "";
for (int i = dot+1; i < html.length() && Character.isDigit(html.charAt(i)) && back.length() < 4; i++) {
back += html.charAt(i);
}
rate = front + "." + back;
}
}
return rate;
}
// 提取相關信息 (如市場狀態、時間)
String parseMeta(String html) {
String meta = "";
int idx = html.indexOf("U.S. Markets Closed.");
if (idx != -1) {
// 向前取 34 字符作為時間戳
meta = html.substring(Math.max(0, idx-34), idx) + " U.S. Markets Closed";
}
return meta;
}
public class CurrencyExchange extends MIDlet implements CommandListener {
private Display display;
private Form form;
private Command exitCmd;
public CurrencyExchange() {
display = Display.getDisplay(this);
exitCmd = new Command("Exit", Command.SCREEN, 1);
}
protected void startApp() {
form = new Form("Exchange Rate");
form.addCommand(exitCmd);
form.setCommandListener(this);
// 異步執行網絡請求 (避免阻塞 UI 線程)
new Thread(() -> {
try {
String url = "https://finance.yahoo.com/m5?a=1&s=USD&t=GBP";
String html = getViaHttpConnection(url);
String rate = parseRate(html);
String meta = parseMeta(html);
String result = "USD/GBP " + rate + " at " + meta;
// 回到事件線程更新 UI
Display.getDisplay(this).callSerially(() ->
form.append(result)
);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
display.setCurrent(form);
}
protected void pauseApp() {}
protected void destroyApp(boolean unconditional) {}
public void commandAction(Command c, Displayable d) {
if (c == exitCmd) {
destroyApp(false);
notifyDestroyed();
}
}
// ... getViaHttpConnection, parseRate, parseMeta 方法
}
| 挑戰 | J2ME 限制 | 解決方案 |
|---|---|---|
| 內存極小 (100KB-1MB) | 不能載入完整 DOM 解析器 | 流式讀取 + 字符串索引解析,不構建對象樹 |
| 無多線程保障 | 部分實現單線程 | 網絡操作放獨立線程,UI 更新用 callSerially() |
| 連接不穩定 | 移動網絡高延遲、易斷 | 設置超時、重試邏輯、異常捕獲 |
| 安全限制 | 未簽名 MIDlet 不能聯網 | 簽名部署、權限聲明 (MIDlet-Permissions) |
| 字符編碼 | 默認 ISO-8859-1 | 手動處理 UTF-8: new String(bytes, "UTF-8") |
// HTTPS 連接
HttpsConnection conn = (HttpsConnection) Connector.open("https://api.example.com/data");
// 證書驗證 (設備內置根證書庫)
// 若服務器使用自簽名證書,需:
// 1. 獲取 SecurityInfo
SecurityInfo info = conn.getSecurityInfo();
// 2. 獲取證書鏈
Certificate[] certs = info.getServerCertificates();
// 3. 手動驗證或提示用戶信任
// 注意:無法動態添加根證書到設備信任庫
J2ME 的 Generic Connection Framework 是嵌入式 Java 網絡編程的經典設計:統一接口、協議無關、工廠模式動態綁定。雖然智能機時代已結束 J2ME 統治,但其設計思想在 Java ME Embedded、Android NDK 網絡層、IoT 輕量級協議棧 中仍有回響。掌握 Connector.open()、HttpConnection、流式解析、資源管理模式,對理解受限環境下的網絡編程極有價值。