J2ME 開發最令人抓狂的莫過於「真機無控制台」。模擬器上 System.out.println 好用得很,一上實體手機——沒輸出、無法追蹤、只能靠 Alert 彈窗硬測,多條訊息還會互相覆蓋。參考 LWUIT 設計理念,本框架提供統一入口、多級別、可擴充、可持久化的日誌方案,讓除錯資訊「有地方存、有辦法看、能隨時清」。
public interface Logger {
int FINE = 0, INFO = 10, DEBUG = 20, WARNING = 30, ERROR = 40;
void log(int level, String info);
String getLogContent();
void clearLog();
}
五級別對應 Java SE java.util.logging.Level,便於移植既有代碼。
public interface LogShower {
void showLog(String logContent, BackAction action);
interface BackAction { void back(); }
}
解耦「怎麼記」與「怎麼看」:同一份日誌可用 List、TextArea、Canvas 甚至 HTTP 上傳伺服器呈現。
public final class LogManager {
private static Logger log = new LoggerImpl();
private static LogShower shower = new LogShowerImpl();
public static void install(Logger log) { LogManager.log = log; }
public static void install(LogShower shower) { LogManager.shower = shower; }
public static void info(String msg) { log.log(Logger.INFO, msg); }
public static void debug(String msg) { log.log(Logger.DEBUG, msg); }
public static void error(String msg) { log.log(Logger.ERROR, msg); }
public static void warning(String msg){ log.log(Logger.WARNING, msg); }
public static void fine(String msg) { log.log(Logger.FINE, msg); }
public static void showLog(BackAction back) { shower.showLog(log.getLogContent(), back); }
public static void clearLog() { log.clearLog(); }
// ... 內建預設實作 LoggerImpl / LogShowerImpl
}
static class LoggerImpl implements Logger {
private StringBuffer sb = new StringBuffer(1024);
public void log(int level, String info) {
sb.append('[').append(Thread.currentThread()).append('-')
.append(Util.getCurrentTime()).append("] ")
.append(getLevelName(level)).append(':').append(info).append('\n');
}
private String getLevelName(int level) {
switch(level) {
case FINE: return "FINE";
case INFO: return "INFO";
case DEBUG: return "DEBUG";
case WARNING: return "WARNING";
case ERROR: return "ERROR";
default: return "UNKNOWN";
}
}
public String getLogContent() { return sb.toString(); }
public void clearLog() { sb.delete(0, sb.length()); }
}
static class LogShowerImpl implements LogShower {
public void showLog(String logContent, final BackAction action) {
Form form = new Form("日誌內容");
form.setScrollable(false);
final TextArea ta = new TextArea(logContent, 5, 10);
ta.setEditable(false);
form.addCommand(new Command("返回") {
public void actionPerformed(ActionEvent ae) { action.back(); }
});
form.addCommand(new Command("清除") {
public void actionPerformed(ActionEvent ae) {
LogManager.clearLog(); ta.setText("");
}
});
form.setLayout(new BorderLayout());
form.addComponent(BorderLayout.CENTER, ta);
form.show();
}
}
預設實作僅駐留記憶體,長時間運行易 OOM。自訂 RMSLogger 三步走:
log() 將字串 getBytes() 後 RecordStore.addRecord()getLogContent() 遍歷 RecordEnumeration 還原完整字串clearLog() 呼叫 RecordStore.deleteRecordStore() 徹底清空再 LogManager.install(new RMSLogger()) 即可無縫切換。
WARNING 以上,開發版全開,避免 I/O 爆量。LogShower 實作可改為 HTTP POST 上傳錯誤日誌至伺服器,實現「雲端除錯」。這套框架僅 3 個類、< 2 KB,卻解決了 J2ME 開發 90% 的調試痛點。核心思想——介面隔離變化點、管理器統一入口、預設實作開箱即用——同樣適用於 Android、嵌入式 Linux 等資源受限環境。下次真機跑不動時,別再硬塞 Alert 了,裝上它,睡個好覺。