許多開發者從零開始學習 J2ME,卻發現市面上大多數 Java 基礎教材都以 J2SE 為主,導致學習路徑偏離。本文系統梳理 J2ME 開發必備的核心知識、平台限制、跨平台挑戰、職業方向、設計原則、測試策略、SDK 選擇、常用技術實作(IMEI 獲取、網絡優化、字符串分割、長文本顯示、RMS 存儲、加密、音頻播放、3D 開發、CMWAP 代理、時間/隨機數處理、字體色彩應用、手機號獲取、短信發送、MIDlet 生命週期、OTA 發布),助你少走彎路,直擊實戰。
javac/java 命令行操作javax.microedition.* 等核心包)建議:直接研讀 J2ME 官方 API 文檔,建立精準知識體系。
了解邊界,避免無效探索:
J2ME 源自 Java,理應具備「一次編寫,到處運行」特性,但現實骨感:
這造就了「支持機型列表」與「遊戲移植工程師」的獨特生態。
| 崗位 | 核心職責 | 關鍵技能棧 |
|---|---|---|
| J2ME 遊戲開發 | 按策劃文檔在特定機型(多為 S40/S60)開發遊戲 | 高/低級 UI、線程、網絡編程(聯網遊戲) |
| J2ME 應用開發 | 移動定位、移動商務等業務應用 | 高級 UI、線程、網絡編程 |
| J2ME 遊戲移植 | 將遊戲從一平台遷移至另一平台(如 S40→S60、索愛 T618) | 屏幕座標控制、API 替換、平台差異參數(屏幕尺寸、JAR 上限等) |
模擬器存在實現 Bug,且無法模擬真機內存、CPU、按鍵手感等物理特性。
下載地址參考:Nokia/三星/索愛等官方 SDK 彙總
IMEI(國際移動設備識別碼)15 位,全球唯一,適合作用戶標識。
// Moto 系列
String imei = System.getProperty("IMEI");
// Siemens 系列
String imei = System.getProperty("com.siemens.IMEI");
其他品牌需查閱對應技術文檔,無統一標準 API。
現象:公告頁先顯背景圖,後載文字,體驗極差。
Display.setCurrent()。Display.setCurrent() 切換目標界面。String.split()?自造分割工具類MIDP 2.0 仍無 split(),實戰常需分割字符串。下方提供生產級實現:
private static String[] split(String original, String regex) {
int startIndex = 0, index = 0;
Vector v = new Vector();
startIndex = original.indexOf(regex);
while (startIndex < original.length() && startIndex != -1) {
v.addElement(original.substring(index, startIndex));
index = startIndex + regex.length();
startIndex = original.indexOf(regex, index);
}
v.addElement(original.substring(index));
String[] result = new String[v.size()];
for (int i = 0; i < result.length; i++) result[i] = (String) v.elementAt(i);
return result;
}
場景:遊戲說明、公告、關於頁面,文本長度不固定。
String[]。paint() 中循環繪製數組每一行。優勢:修改內容只改字符串;調整行寬只改切割邏輯;支援動態長度文本。
MIDP 無文件系統,持久化依賴 RMS。核心操作如下:
RecordStore rs = RecordStore.openRecordStore("username", true);
// true: 不存在則創建;false: 不創建拋出 RecordStoreNotFoundException
// 新增
byte[] data = {1,2,3};
int id = rs.addRecord(data, 0, data.length); // id 從 1 開始
// 修改
rs.setRecord(1, data, 0, data.length); // 覆蓋 id=1 記錄
byte[] data = rs.getRecord(1);
int count = rs.getNumRecords(); // 總記錄數
rs.deleteRecord(id),ID 不復用(如刪 3,剩 1,2,4,5)使用 RecordEnumeration,詳見 官方教程。
RecordStore.deleteRecordStore("username");
數據存儲/傳輸需加密,推薦開源 Bouncy Castle:
關鍵提示:庫體積較大,發布前必須移除未用類,控制 JAR 體積。
支持格式:WAV、MID、MPG(具體以手機文檔為準)。
InputStream is = getClass().getResourceAsStream("/Autorun.wav");Player player = Manager.createPlayer(is, "audio/x-wav");player.start(); 可用 setLoopCount(n) 設置循環次數。package sound;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.media.*;
import java.io.*;
public class SoundMIDlet extends MIDlet {
private Player player = null;
public SoundMIDlet() {
try {
InputStream is = getClass().getResourceAsStream("/Autorun.wav");
player = Manager.createPlayer(is, "audio/x-wav");
} catch (Exception e) { e.printStackTrace(); }
}
public void startApp() {
if (player != null) try { player.start(); } catch (MediaException e) { e.printStackTrace(); }
}
public void pauseApp() {}
public void destroyApp(boolean unconditional) {}
}
隨硬件提升,3D 遊戲成主流。核心資源:
基於 WTK 2.2 Demo 改寫,雙文件結構:
First3DMIDlet.java:主入口,顯示 CanvasFirst3DCanvas.java:核心渲染邏輯,含 World、Camera、Graphics3D、動畫線程first3d.First3DMIDletfirst3d 目錄apps/Demo3D/escom/superscape/m3g/wtk/samples/retainedmode/content/swerve.m3g 到項目 res 目錄核心渲染循環:animate() 更新世界時間 → render() 繪製 → repaint() 觸發下一幀。
中國移動兩大 APN:
HttpConnection http = (HttpConnection) Connector.open("https://10.0.0.172/" + url);
http.setRequestProperty("X-Online-Host", serverName);
目標:https://www.test.com/login/loginServlet
HttpConnection http = (HttpConnection) Connector.open("https://10.0.0.172/login/loginServlet");
http.setRequestProperty("X-Online-Host", "www.test.com");
通用模板:將域名填入 X-Online-Host,路徑拼接至代理地址後。
Date/Calendar):適合日期分量操作| 類 | 獲取方式 | 返回類型 |
|---|---|---|
| System | System.currentTimeMillis() | long (毫秒數) |
| Date | new Date() | Date 對象 |
| Calendar | Calendar.getInstance() | Calendar 對象 |
// long → Date
Date d = new Date(System.currentTimeMillis());
// Date → Calendar
Calendar c = Calendar.getInstance();
c.setTime(date);
// 取日期分量(注意:月份 0=1月, 11=12月)
int day = c.get(Calendar.DATE);
int month = c.get(Calendar.MONTH); // 0-based
int year = c.get(Calendar.YEAR);
無 Math.random(),唯一依賴 java.util.Random。
Random r = new Random(); // 種子 = 當前時間
Random r = new Random(10010010); // 固定種子,可復現序列
int i = r.nextInt();
long l = r.nextLong();
// 0-9
int n = Math.abs(r.nextInt() % 10);
// 10-19
int n = Math.abs(r.nextInt() % 10) + 10;
// 通用 [min, max)
int n = min + Math.abs(r.nextInt() % (max - min));
受限於設備,僅能使用系統預置字體組合。
Font font = Font.getFont(Font.FACE_SYSTEM, Font.STYLE_BOLD, Font.SIZE_MEDIUM);
| 屬性 | 常量 | 含義 |
|---|---|---|
| face (外觀) | FACE_MONOSPACE | 等寬 |
| FACE_PROPORTIONAL | 比例 | |
| FACE_SYSTEM | 系統默認 | |
| style (樣式) | STYLE_PLAIN | 普通 |
| STYLE_BOLD | 粗體 | |
| STYLE_ITALIC | 斜體 | |
| STYLE_UNDERLINED | 下劃線 | |
| STYLE_BOLD | STYLE_ITALIC | 粗斜體 | |
| STYLE_UNDERLINED | STYLE_BOLD | 下劃線粗體 | |
| STYLE_UNDERLINED | STYLE_ITALIC | 下劃線斜體 | |
| STYLE_UNDERLINED | STYLE_ITALIC | STYLE_BOLD | 下劃線粗斜體 | |
| size (大小) | SIZE_SMALL | 小 |
| SIZE_MEDIUM | 中 | |
| SIZE_LARGE | 大 |
// 獲取默認字體
Font f = Font.getDefaultFont();
// paint(Graphics g) 中獲取/設置當前字體
Font cur = g.getFont();
g.setFont(myFont);
// MIDP 2.0 List 指定行字體
list.setFont(0, myFont); // 第 0 行
無專用 Color 類,直接用 24 位 RGB 整數或三分量。
0x00FF0000 / (255,0,0)0x0000FF00 / (0,255,0)0x000000FF / (0,0,255) (原文藍色值有誤,已修正)0x00FFFFFF / (255,255,255)0x00000000 / (0,0,0)int c = g.getColor(); // 獲取當前色 (0x00RRGGBB)
g.setColor(0x00FF0000); // 設置色 (十六進制)
g.setColor(255, 0, 0); // 設置色 (三分量)
設置後後續繪製操作自動應用該顏色。
中國移動網關將手機號放入 HTTP 頭 x-up-calling-line-id:
String userPhone = http.getHeader("x-up-calling-line-id");
// http 為 HttpConnection 實例
標準 J2ME 庫無直接 API,此為運營商定制能力。
三種發送方式:網關發送、PC 連手機 AT 指令、手機端程序直發 (WMA)。
MIDP 2.0 內建 WMA;MIDP 1.0 需廠商擴展包。
package my.util;
import javax.wireless.messaging.*;
import javax.microedition.io.*;
public class SMSUtil {
public static boolean send(String content, String phoneNumber) {
try {
String addr = "sms://+" + phoneNumber;
MessageConnection conn = (MessageConnection) Connector.open(addr);
TextMessage msg = (TextMessage) conn.newMessage(MessageConnection.TEXT_MESSAGE);
msg.setPayloadText(content);
conn.send(msg);
return true;
} catch (Exception e) {
return false;
}
}
}
生命週期本質:規定各方法被系統調用的時機。在每個方法打印日誌即可驗證順序。
public class LifeCircleMIDlet extends MIDlet {
public LifeCircleMIDlet() { System.out.println("構造方法"); }
public void startApp() { System.out.println("startApp"); }
public void pauseApp() { System.out.println("pauseApp"); }
public void destroyApp(boolean b) { System.out.println("destroyApp"); }
}
WTK 中用「MIDlet」菜單的「暫停/恢復」模擬生命週期事件。
OTA (Over The Air) 是主流發布方式(如各大百寶箱)。
| 副檔名 | MIME Type |
|---|---|
| .jad | text/vnd.sun.j2me.app-descriptor |
| .jar | application/java-archive |
download.wml)<?xml version="1.0"?>
<!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.3//EN" "https://www.wapforum.org/DTD/wml13.dtd">
<wml>
<card id="c1" title="Download Midlet">
<a href="test.jad">安裝應用</a>
</card>
</wml>
MIDlet-Jar-URL: https://yourdomain.com/path/to/test.jar
用戶在手機瀏覽器輸入 WML 頁面 URL 即可一鍵下載安裝。