首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> J2ME

J2ME 最佳網路方案終極指南:HTTP 協定深度優化實戰

     編輯:J2ME

針對無線裝置資源限制 (CPU、記憶體、頻寬、延遲),本文整理出 J2ME 網路開發的最佳實踐方案,涵蓋協定選擇、客戶端/伺服器端優化、多執行緒架構、進度回饋、斷點續傳、Cookie 會話管理等完整體系。

協定選擇鐵律

HTTP/HTTPS 是唯一強制支援的協定 (MIDP 1.0/2.0)。Socket/UDP 屬選配,僅作為效能極致需求的補充。為追求最大相容性與程式碼複用,**首選 HTTP**。

客戶端必遵守準則

#準則實作要點
1設定 User-Agentconn.setRequestProperty("User-Agent", "MyApp/2.1 MIDP/2.0"); 讓伺服器識別版本
2顯示下載進度Gauge 進度條 + 取消命令,callSerially 更新 UI
3資料快取策略記憶體快取熱點資料,RMS 持久化離線資料,設定 TTL 淘汰
4非同步網路執行緒Observer 模式 (HttpListener) 解耦網路層與 UI
5優雅錯誤處理區分網路錯誤/HTTP 錯誤/業務錯誤,友善提示並可重試

伺服器端必遵守準則

#準則實作要點
1輸出 Content-Length讓客戶端預先判斷記憶體需求,避免 OOM
2拒絕 HTML 回應回傳緊湊二進位 (application/octet-stream) 或 JSON
3避免重導向301/302 會導致雙倍連線成本與延遲
4標準 HTTP 狀態碼404/401/500 等,客戶端只讀 Header 即知錯誤
5版本適配邏輯前置依 User-Agent 回傳相容格式,舊版客戶端免更新

核心架構:HttpThread + HttpListener

// 觀察者介面
public interface HttpListener {
    void onSetSize(int totalBytes);      // 開始下載,已知總大小
    void onProgress(int percent);        // 進度更新 0-100
    void onFinish(byte[] data, int size); // 完成,傳回資料
    void onError(int code, String msg);  // 錯誤代碼 + 訊息
}

// 背景下載執行緒
class HttpThread extends Thread {
    private static final int MAX_BUFFER = 20 * 1024; // 20KB 上限
    private final String url;
    private final HttpListener listener;
    private volatile boolean cancelled = false;
    private byte[] buffer;

    public HttpThread(String url, HttpListener listener) {
        this.url = url; this.listener = listener;
    }

    public void cancel() { cancelled = true; }

    public void run() {
        HttpConnection conn = null;
        InputStream in = null;
        try {
            conn = (HttpConnection) Connector.open(url);
            conn.setRequestMethod(HttpConnection.GET);
            conn.setRequestProperty("User-Agent", "MyApp/1.0");

            int rc = conn.getResponseCode();
            if (rc != HttpConnection.HTTP_OK) {
                listener.onError(rc, conn.getResponseMessage());
                return;
            }

            int total = (int) conn.getLength();
            listener.onSetSize(total);
            
            in = conn.openInputStream();
            buffer = (total > 0) ? new byte[total] : new byte[MAX_BUFFER];
            int idx = 0, lastPercent = 0;

            while (!cancelled) {
                int space = buffer.length - idx;
                int readLen = Math.min(space, 128);
                int read = in.read(buffer, idx, readLen);
                if (read <= 0) break;
                idx += read;

                if (total > 0) {
                    int pct = idx * 100 / total;
                    if (pct != lastPercent) {
                        lastPercent = pct;
                        listener.onProgress(pct);
                    }
                }
            }

            if (cancelled) return;
            if (in.available() > 0) { listener.onError(601, "Buffer overflow"); return; }
            if (total > 0 && idx != total) { listener.onError(102, "Length mismatch"); return; }
            
            listener.onFinish(buffer, idx);
        } catch (IOException e) {
            listener.onError(101, "IOException: " + e.getMessage());
        } finally {
            closeQuietly(in);
            closeQuietly(conn);
        }
    }
}

等待畫面 UI 實作

public class HttpWaitScreen extends Form implements CommandListener, HttpListener {
    private final Gauge gauge = new Gauge("進度", false, 100, 0);
    private final Command cancel = new Command("取消", Command.CANCEL, 0);
    private final HttpThread downloader;
    private final Displayable nextScreen;

    public HttpWaitScreen(String url, Displayable nextScreen) {
        super("連線中...");
        this.nextScreen = nextScreen;
        append(gauge);
        addCommand(cancel);
        setCommandListener(this);
        downloader = new HttpThread(url, this);
        downloader.start();
    }

    public void commandAction(Command c, Displayable d) {
        if (c == cancel) {
            downloader.cancel();
            Navigator.goBack(); // 返回上一畫面
        }
    }

    public void onSetSize(int size) { /* 可選:顯示總大小 */ }
    public void onProgress(int pct) { gauge.setValue(pct); }
    
    public void onFinish(byte[] data, int size) {
        byte[] payload = (size == data.length) ? data : Arrays.copyOf(data, size);
        try (DataInputStream dis = new DataInputStream(
                new ByteArrayInputStream(payload))) {
            if (nextScreen instanceof DataHandler) {
                ((DataHandler) nextScreen).setData(dis);
            }
            Navigator.forward(nextScreen);
        } catch (IOException e) {
            onError(103, "Data parse error");
        }
    }

    public void onError(int code, String msg) {
        Alert alert = new Alert("錯誤", msg, null, AlertType.ERROR);
        alert.setTimeout(3000);
        Display.getDisplay(MIDletContext.get()).setCurrent(alert, Navigator.getPrevious());
    }
}

// 下一畫面實作此介面接收資料
public interface DataHandler {
    void setData(DataInputStream in) throws IOException;
}

實際使用範例:新聞閱讀

// 使用者點選新聞標題
public void commandAction(Command c, Displayable d) {
    if (c == readCmd) {
        String url = "https://api.example.com/news.do?id=" + newsId;
        HttpWaitScreen wait = new HttpWaitScreen(url, new NewsDetailScreen());
        Navigator.forward(wait);
    }
}

// 詳細畫面解析資料
public class NewsDetailScreen extends Form implements DataHandler {
    public void setData(DataInputStream in) throws IOException {
        String title = in.readUTF();
        long time = in.readLong();
        String body = in.readUTF();
        
        append(new StringItem("標題", title));
        append(new StringItem("時間", new Date(time).toString()));
        append(new StringItem(null, body));
    }
}

POST 表單提交與檔案上傳

application/x-www-form-urlencoded

byte[] body = "user=admin&pass=1234".getBytes();
conn.setRequestMethod(HttpConnection.POST);
conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
conn.setRequestProperty("Content-Length", String.valueOf(body.length));
OutputStream os = conn.openOutputStream();
os.write(body); os.flush();

multipart/form-data 檔案上傳


String BOUNDARY = "----MobileFormBoundary" + System.currentTimeMillis();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);

// 文字欄位
writeField(dos, "title", "My Photo");
// 檔案欄位
writeFile(dos, "photo", "image.jpg", "image/jpeg", imageBytes);

byte[] header = baos.toByteArray();
byte[] footer = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();

conn.setRequestProperty("Content-Type", 
    "multipart/form-data; boundary=" + BOUNDARY);
conn.setRequestProperty("Content-Length", 
    String.valueOf(header.length + footer.length));

OutputStream os = conn.openOutputStream();
os.write(header); os.write(footer); os.flush();

Cookie 會話管理


String sessionId = null;

// 每次請求前附加 Cookie
if (sessionId != null) {
    conn.setRequestProperty("Cookie", sessionId);
}

// 每次回應檢查 Set-Cookie
String cookie = conn.getHeaderField("Set-Cookie");
if (cookie != null) {
    int semi = cookie.indexOf(';');
    sessionId = (semi > 0) ? cookie.substring(0, semi) : cookie;
    // 可選:持久化至 RMS 供下次啟動使用
}

常見 Session ID 格式:JSESSIONID=xxx; path=/ (Tomcat/Jetty)、ASPSESSIONID=xxx (IIS)。

總結:黃金法則

  1. 只用 HTTP/HTTPS — 最大相容、伺服器碼可複用
  2. 二進位協定 — DataOutputStream 寫入,避免 XML/JSON 解析開銷
  3. 非同步 + 進度 + 可取消 — HttpThread + HttpListener 模式
  4. 快取策略 — 記憶體 + RMS,ETag/Last-Modified 條件請求
  5. 版本協商 — User-Agent 傳版本,伺服器回相容格式
  6. 資源嚴謹釋放 — finally 區塊關閉所有連線/串流
  7. 真機實測 — 模擬器無法反映真實延遲、記憶體、電池壓力

遵循這套方案,您將能開發出在 2G/3G/早期智慧型手機上都能流暢、穩定、省電運作的 J2ME 網路應用程式。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved