本文為 J2ME 中文編碼完整攻略 的修訂補充版,針對實戰中易踩的坑補充細節,並增加「編碼自動偵測工具類」、「MIDP 3.0 / Java ME 8 新特性」與「Kotlin/Scala 移植提示」。
public final class EncodingUtil {
private EncodingUtil() {}
/** 偵測裝置支援的編碼列表 */
public static String[] detectSupportedEncodings() {
String[] candidates = {"UTF-8", "GB2312", "GBK", "GB18030", "Big5", "Big5-HKSCS",
"UTF-16LE", "UTF-16BE", "ISO-8859-1", "US-ASCII"};
Vector v = new Vector();
for (String enc : candidates) {
if (isSupported(enc)) v.addElement(enc);
}
String[] r = new String[v.size()];
v.copyInto(r);
return r;
}
public static boolean isSupported(String encoding) {
try { "測試".getBytes(encoding); return true; }
catch (UnsupportedEncodingException e) { return false; }
}
/** 安全轉碼:來源編碼 → 目標編碼,失敗回傳原 byte[] */
public static byte[] transcode(byte[] src, String fromEnc, String toEnc) {
try {
String s = new String(src, fromEnc);
return s.getBytes(toEnc);
} catch (Exception e) { return src; }
}
/** 讀取資源檔自動偵測編碼 (支援 BOM) */
public static String readResourceString(String resPath) throws IOException {
InputStream is = EncodingUtil.class.getResourceAsStream(resPath);
if (is == null) throw new IOException("Resource not found: " + resPath);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[256];
int len;
while ((len = is.read(buf)) != -1) baos.write(buf, 0, len);
is.close();
byte[] data = baos.toByteArray();
// BOM 偵測
String enc = "UTF-8"; // 預設
if (data.length >= 2) {
if (data[0] == (byte)0xFE && data[1] == (byte)0xFF) enc = "UTF-16BE";
else if (data[0] == (byte)0xFF && data[1] == (byte)0xFE) enc = "UTF-16LE";
else if (data.length >= 3 && data[0]==(byte)0xEF && data[1]==(byte)0xBB && data[2]==(byte)0xBF) enc = "UTF-8";
}
// 去除 BOM
int bomLen = (enc.startsWith("UTF-16")) ? 2 : (enc.equals("UTF-8") ? 3 : 0);
if (data.length > bomLen) {
byte[] content = new byte[data.length - bomLen];
System.arraycopy(data, bomLen, content, 0, content.length);
return new String(content, enc);
}
return "";
}
}
| 特性 | MIDP 2.0 | MIDP 3.0 / Java ME 8 |
|---|---|---|
| 標準編碼 | UTF-8, ISO-8859-1 (其餘視實作) | 強制支援:UTF-8, UTF-16BE, UTF-16LE, US-ASCII, ISO-8859-1 |
| BOM 處理 | 無標準 | InputStreamReader 自動識別 BOM |
| 資源檔編碼 | 手動指定 | Class.getResourceAsStream 可搭配 InputStreamReader(IS, "UTF-8") |
| JAD/Manifest | UTF-8 建議 | 強制 UTF-8,且支援 MIDlet-Description- 多語系 |
若目標平台包含 Java ME 8 (如 Nokia Asha 501、部分嵌入式模組),可大幅簡化編碼處理邏輯。
String(byteArray, Charset) / string.toByteArray(Charset) 對應 Java new String(bytes, enc) / s.getBytes(enc)。Charset.forName("GB2312") 會拋 IllegalArgumentException 而非 UnsupportedEncodingException,捕獲時需注意。scala.io.Codec 隱式參數簡化調用。-keepclassmembers class * { static final java.lang.String *; }。| 項目 | 驗證方法 | Through 標準 |
|---|---|---|
| JAD 檔案編碼 | Notepad++ → 編碼欄位顯示 UTF-8 無 BOM | ✅ |
| OTA 下載 Content-Type | 抓包檢查 charset=UTF-8 | ✅ |
| 資源檔 BOM | 十六進位編輯器確認前 2/3 bytes | ✅ |
| RMS 讀寫編碼一致 | 單元測試:寫入 → 讀出 → 比對原字串 | ✅ |
| HTTP 請求參數編碼 | 中文參數 URLEncoder.encode(UTF-8) | ✅ |
| HTTP 回應標頭 charset | 伺服器回應 Content-Type: application/json; charset=UTF-8 | ✅ |
| 裝置不支援編碼降級 | 模擬器關閉 GB2312 測試降級邏輯 | ✅ |
| 異常 | 典型原因 | 修正 |
|---|---|---|
UnsupportedEncodingException | 編碼名稱拼錯 / 裝置真不支援 | 用 EncodingUtil.isSupported() 運行期偵測並降級 |
IllegalArgumentException (Kotlin) | 同左 | 捕獲 IllegalArgumentException 而非 UnsupportedEncodingException |
| 亂碼但無異常 | 編碼不匹配 (GB2312 當 UTF-8 解) | 印出 byte[] 十六進位,對照編碼表確認 |
| 截斷/問號 | DBCS 奇數位元組截斷 / 字型缺字 | 確保 byte[] 長度為偶數;換支援字型或位圖字體 |
中文編碼問題本質是「協議雙方約定不一致」。建立 編碼偵測 → 統一內部 Unicode (String) → 目標編碼輸出 的標準化管線,配合 EncodingUtil 等工具類封裝平台差異,即可在從 MIDP 1.0 到 Java ME 8 的全版本譜系上實現「零亂碼」交付。記住:**UTF-8 是通用橋樑,JAD/HTTP/XML 強制 UTF-8,本地儲存視空間選 GB2312/Big5,運行期偵測裝置能力優雅降級**。