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

J2ME 最佳實踐 (四):聯網開發核心模式與 HTTP 協議優化

     編輯:J2ME

J2ME 最佳實踐系列 (四):打造健壯、高效、可復用的 MIDP 聯網模組

無線裝置協議受限 (HTTP 必選,Socket/UDP 可選),為追求最大相容性與伺服器端代碼復用,首選 HTTP/HTTPS。本文涵蓋客戶端/伺服器端協作準則、完整 HTTP 框架實作 (Observer 模式、進度條、取消機制)、GET/POST/Multipart 細節、Cookie Session 維持。

設計原則

客戶端 (MIDP) 應做到:

  1. 請求附加 User-Agent 標頭 (含 MIDP 版本、應用版本),伺服器據此回應差異化內容。
  2. 顯示下載進度條,允許使用者隨時取消連線。
  3. 善用快取 (記憶體/RMS) 減少重複下載。

伺服器端應做到:

  1. 明確回應 Content-Length,客戶端可預判能否處理。
  2. 勿回傳 HTML/XML,改回傳緊湊二進制資料 (application/octet-stream),用 DataOutputStream 寫入,客戶端用 DataInputStream 讀取,極致效能。
  3. 避免重導向 (3xx),增加延遲與流量。
  4. 錯誤直接回傳 HTTP 狀態碼 (404, 401, 500),勿回傳錯誤頁面 HTML。
  5. 版本適配邏輯放伺服器端 (讀取 User-Agent),舊版客戶端無需升級即可繼續使用。

核心框架:HttpThread + HttpListener (Observer 模式)

HttpListener 介面 (觀察者)

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 狀態碼、自訂碼)
}

HttpWaitUI : 實作 HttpListener 的等待畫面 (含進度條、取消鍵)

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 並切換顯示
}

HttpThread : 背景執行緒處理 HTTP 連線

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
        }
    }
}

DataHandler 介面:下一畫面處理資料

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,客戶端對應讀取,零解析開銷。

POST 表單提交 (application/x-www-form-urlencoded)

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,需自行實作或引用輕量實現。

Multipart/Form-Data 檔案上傳

String BOUNDARY = "---------------------------7d4a6d158c9";
hc.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);

// 建構 body:每欄位以 --BOUNDARY 分隔,結尾 --BOUNDARY--
// 範例略,詳見原文完整 StringBuffer 建構代碼

分隔符需足夠複雜避免與檔案內容衝突 (IE 使用隨機長字串)。

Cookie Session 維持

// 回應中解析 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 (難解析)。

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