J2ME 裝置 CPU 慢、記憶體少,手動管理執行緒池、同步鎖、睡眠喚醒極易出錯。java.util.Timer + TimerTask 提供單執行緒排程,任務依序執行、無需同步、資源佔用極低,是 MIDP 遊戲主循環的首選。
Timer timer = new Timer();
// 一次性執行:500ms 後執行
timer.schedule(new MyTask(), 500);
// 週期性執行:首次 1s 後,之後每 20s
timer.schedule(new MyTask(), 1000, 20000);
// 固定頻率執行:以 scheduled time 為基準(適合計時器)
timer.scheduleAtFixedRate(task, delay, period);
// 取消單一任務
task.cancel();
// 取消整個 Timer(會清除佇列所有任務)
timer.cancel();
關鍵特性:所有任務共用同一背景執行緒;若某任務 run() 耗時過長,後續任務會排隊延遲;不保證精確觸發時間,僅保證順序。
public class GameTask extends TimerTask {
private final GameObject target;
public GameTask(GameObject obj) { this.target = obj; }
public void run() { target.gameTask(); }
}
public interface GameObject {
void gameTask(); // 由 TimerTask 呼叫
}
public class GameCanvas extends FullCanvas implements GameObject {
private static Timer timer;
private static byte state; // 0=開場, 1=主選單, 2=遊戲中...
public void gameTask() {
switch (state) {
case 0: introTask(); break;
case 1: menuTask(); break;
case 2: playTask(); break;
}
}
// 例:閃爍效果每 500ms 切換
private void menuTask() {
flash = !flash;
repaint();
timer.schedule(new GameTask(this), 500L); // 遞迴排程
}
// 按鍵切換狀態
protected void keyPressed(int keyCode) {
switch (state) {
case 1: menuKey(keyCode); break;
case 2: playKey(keyCode); break;
}
}
private void menuKey(int keyCode) {
if (keyCode == -7) { // 確認鍵
timer.cancel();
state = 2;
repaint();
new Timer().schedule(new GameTask(this), 500L);
}
}
protected void paint(Graphics g) {
switch (state) {
case 0: drawIntro(g); break;
case 1: drawMenu(g); break;
case 2: drawPlay(g); break;
}
}
}
遞迴 schedule 易產生累積誤差。專業遊戲採用固定時間步長 + 積累器:
public class FixedStepGameLoop extends TimerTask {
private static final long STEP_MS = 16; // ~60 FPS
private long accumulator = 0;
private long lastTime = System.currentTimeMillis();
public void run() {
long now = System.currentTimeMillis();
long delta = now - lastTime;
lastTime = now;
accumulator += delta;
while (accumulator >= STEP_MS) {
update(STEP_MS); // 邏輯更新
accumulator -= STEP_MS;
}
render(); // 渲染(可變幀率)
// 下一幀排程
timer.schedule(this, STEP_MS);
}
}
| 坑點 | 症狀 | 對策 |
|---|---|---|
| run() 耗時 > period | 幀率崩塌、輸入延遲 | 邏輯/渲染分離;耗時操作放入獨立執行緒 |
| 多 Timer 競爭 | 同步問題、CPU 爭搶 | 全程式單一 Timer;任務內部用狀態機分流 |
| cancel() 後重用 Timer | IllegalStateException | 新建 Timer 實例;或用 purge() 清理已取消任務 |
| 裝置睡眠/來電中斷 | Timer 暫停、時間跳躍 | 在 pauseApp() 記錄時間戳,startApp() 校正 accumulator |
TimerTask 是 J2ME 遊戲主循環的基石。掌握「狀態機驅動 + 遞迴排程」或「固定時間步長 + 積累器」兩大模式,配合單 Timer 原則與耗時操作異步化,即可在 200 KB 記憶體、100 MHz CPU 的功能機上跑出流暢 20+ FPS 遊戲體驗。