J2ME (MIDP) 應用面臨嚴苛的體積限制:眾多功能機僅允許 50KB-100KB 的 JAR 包。本文以一個簡單的「文本框輸入播放聲音」 MIDlet 為例,演示四大優化維度:功能裁剪、類結構精簡、庫複用、繼承鏈扁平化,將體積從 4KB 進一步壓縮。
package com.j2medeveloper.techtips;
import javax.microedition.lcdui.*;
public class BeforeSizeOptimization extends BasicMIDlet {
public static final Command exitCommand = new Command("Exit", Command.EXIT, 1);
protected void initMIDlet() {
getDisplay().setCurrent(new MainForm());
}
public class MainForm extends Form {
public MainForm() {
super("MainForm");
addCommand(exitCommand);
append(textField);
setCommandListener(new CommandListener() { // 匿名內部類 #1
public void commandAction(Command c, Displayable d) {
if (c == exitCommand) exitMIDlet();
}
});
setItemStateListener(new ItemStateListener() { // 匿名內部類 #2
public void itemStateChanged(Item item) {
if (item == textField) AlertType.INFO.playSound(getDisplay());
}
});
}
private TextField textField = new TextField("Type anything", null, 20, 0);
}
}
// 輔助基類
abstract class BasicMIDlet extends MIDlet {
private Display display;
protected abstract void initMIDlet();
protected void startApp() throws MIDletStateChangeException {
if (display == null) { display = Display.getDisplay(this); initMIDlet(); }
}
protected void pauseApp() {}
protected void destroyApp(boolean unconditional) throws MIDletStateChangeException { exitMIDlet(); }
public void exitMIDlet() { notifyDestroyed(); }
public Display getDisplay() { return display; }
}
優化前:兩個匿名類 (CommandListener、ItemStateListener) → 兩個 .class 檔案
優化後:主類實作介面,複用單一實例
public class OptimizedMIDlet extends MIDlet
implements CommandListener, ItemStateListener {
private Display display;
private Form mainForm;
private TextField textField;
private Command exitCommand;
protected void startApp() throws MIDletStateChangeException {
if (display == null) {
display = Display.getDisplay(this);
exitCommand = new Command("Exit", Command.EXIT, 1);
textField = new TextField("Type anything", null, 20, 0);
mainForm = new Form("Main");
mainForm.addCommand(exitCommand);
mainForm.append(textField);
mainForm.setCommandListener(this);
mainForm.setItemStateListener(this);
}
display.setCurrent(mainForm);
}
public void commandAction(Command c, Displayable d) {
if (c == exitCommand) notifyDestroyed();
}
public void itemStateChanged(Item item) {
if (item == textField) AlertType.INFO.playSound(display);
}
// pauseApp/destroyApp 保持空實作
}
效果:減少 2 個 .class 檔案,體積顯著下降。
問題:BasicMIDlet 僅被繼承一次,抽象層增加類載入開銷與方法分派間接性。
對策:將基類邏輯內聯到主類,或直接實作 MIDlet 介面。
原則:繼承深度 > 2 且子類數量 < 3 時,考慮扁平化。
Vector、Hashtable(CLDC 內建),勿自寫 LinkedList/HashMapnew Form()、new TextField(),勿為簡單佈局寫子類System.arraycopy、StringBuffer 等原生 API 優於手寫迴圈| 技巧 | 工具/配置 | 效果 |
|---|---|---|
| 代碼混淆 | ProGuard / WTK 內建 Obfuscator | 類/方法/欄位重命名為 a/b/c,移除調試資訊,體積 -30%-50% |
| 資源壓縮 | PNG 壓縮、移除元數據、音頻降採樣 | 資源佔比大時效果顯著 |
| 移除未用代碼 | ProGuard -deadcodeelimination | 清理庫中未引用的類/方法 |
| 預驗證優化 | WTK preverify 選項 | 減少類驗證資訊 |
| 版本 | JAR 大小 | .class 數量 | 關鍵變更 |
|---|---|---|---|
| 基準版 | ~4.0 KB | 5 (MIDlet, BasicMIDlet, MainForm, 2匿名類) | 標準結構 |
| 消除內部類 | ~3.2 KB | 3 (MIDlet, MainForm*, BasicMIDlet) | 主類實作 Listener |
| 扁平化繼承 | ~2.8 KB | 2 (MIDlet, MainForm*) | 移除 BasicMIDlet |
| ProGuard 混淆 | ~1.9 KB | 2 | 重命名、移除調試 |
*MainForm 可進一步內聯為匿名實例或直接在 MIDlet 中構建 UI
SourceFile、LineNumberTableJ2ME 體積優化的核心哲學:少即是多。從代碼層面消除匿名類、扁平化繼承、複用平台類;構建層面混淆、壓縮、死代碼消除。這套方法論在現代 Android App Bundle (動態分發)、WebAssembly (體積預算)、微控制器固件 等受限環境同樣適用。記住:每一個 byte 都要為用戶價值服務。