首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

J2ME TimerTask 定時器完整指南:從基礎用法到遊戲迴路架構設計

     編輯:Java 相關知識

為什麼 J2ME 需要 TimerTask?

J2ME (CLDC/MIDP) 環境資源極度受限:弱 CPU、少量記憶體 (幾十~百 KB)、無預 emptive 多執行緒支援。傳統 `Thread` + `sleep()` 方式:

  • 設計複雜:需自行管理執行緒生命週期、同步、中斷
  • 資源消耗大:每個執行緒佔用堆疊空間、調度開銷
  • 精度難控:`sleep()` 受系統調度影響,誤差大

`Timer` + `TimerTask` 提供**輕量級排程機制**:單一背景執行緒循序執行所有任務,無同步困擾,API 簡潔,極適合資源受限裝置。

TimerTask 基礎 API

核心類別

  • java.util.Timer:排程器,維護單一背景執行緒
  • java.util.TimerTask:抽象類,實作 `run()` 定義任務邏輯

常用排程方法

Timer timer = new Timer();

// 1. 延遲一次性執行
timer.schedule(task, delayMs);

// 2. 延遲 + 固定間隔重複 (固定延遲:下次執行時間 = 上次完成時間 + period)
timer.schedule(task, delayMs, periodMs);

// 3. 固定頻率重複 (固定頻率:下次執行時間 = 上次開始時間 + period,會追趕延遲)
timer.scheduleAtFixedRate(task, delayMs, periodMs);

// 4. 指定日期一次性執行
timer.schedule(task, date);

// 5. 指定日期 + 固定間隔
timer.schedule(task, date, periodMs);

TimerTask 關鍵方法

  • run():任務邏輯,**必須快速完成**,避免阻塞後續任務
  • cancel():取消自身任務 (在 `run()` 內呼叫可實現條件停止)
  • scheduledExecutionTime():取得理論排程執行時間

基礎範例:倒數計時器

public class CountdownTask extends TimerTask {
    private int counter;
    
    public CountdownTask(int start) {
        this.counter = start;
    }
    
    @Override
    public void run() {
        counter--;
        System.out.println("Counter: " + counter);
        if (counter <= 0) {
            cancel();  // 停止自身
        }
    }
}

// 使用
Timer timer = new Timer();
timer.schedule(new CountdownTask(50), 1000, 2000);  // 1秒後開始,每2秒執行一次

重要特性:循序執行、無並發

  • 所有 `TimerTask` 共用**單一背景執行緒**,**依序執行**
  • 若某任務 `run()` 耗時過長,**後續任務會排隊等待**
  • **不保證精確準時執行**,只保證不早於排程時間
  • **最佳實踐**:`run()` 內**極短時間**完成,耗時操作另闢執行緒或非同步處理

進階架構:遊戲主迴路設計模式

針對遊戲/動畫場景,封裝 **狀態機 + TimerTask 委派模式**,實現清晰的遊戲階段控制。

核心介面:GameObject

public interface GameObject {
    void gameTask();  // 由 TimerTask 呼叫的統一入口
}

委派 Task:GAMETask

import java.util.TimerTask;

public class GAMETask extends TimerTask {
    private GameObject target;
    
    public GAMETask(GameObject object) {
        this.target = object;
    }
    
    @Override
    public void run() {
        target.gameTask();  // 委派給遊戲物件處理
    }
}

Canvas 實作遊戲狀態機

import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.Graphics;
import java.util.Timer;

public class GameCanvas extends GameCanvas implements GameObject {
    private static Timer timer;
    private static byte gameState;  // 0=初始, 1=遊戲中, 2=暫停, 3=結束...
    
    public GameCanvas(MIDlet midlet) {
        super(false);  // 不使用預設按鍵處理
        // 初始化資源...
    }
    
    // GameObject 介面實作:TimerTask 統一入口
    public void gameTask() {
        switch (gameState) {
            case 0: initGameTask(); break;
            case 1: mainGameTask(); break;
            case 2: pauseGameTask(); break;
            case 3: overGameTask(); break;
        }
    }
    
    // 各狀態具體邏輯
    private void mainGameTask() {
        // 遊戲邏輯更新:碰撞檢測、移動、分數...
        // 畫面重繪觸發
        repaint();  
        
        // 續排下一幀 (約 20 FPS = 50ms)
        timer.schedule(new GAMETask(this), 50);
    }
    
    private void pauseGameTask() {
        // 閃爍效果等
        flash = !flash;
        repaint();
        timer.schedule(new GAMETask(this), 500);  // 較低頻率
    }
    
    // 按鍵處理驅動狀態轉換
    protected void keyPressed(int keyCode) {
        switch (gameState) {
            case 0: handleStartInput(keyCode); break;
            case 1: handleGameInput(keyCode); break;
            case 2: handlePauseInput(keyCode); break;
        }
    }
    
    private void handleStartInput(int keyCode) {
        if (keyCode == Canvas.KEY_STAR) {  // *鍵開始
            timer.cancel();  // 取消前一任務
            gameState = 1;
            repaint();
            new Timer().schedule(new GAMETask(this), 50);  // 新 Timer 啟動主迴路
        }
    }
    
    // 繪製邏輯同樣按狀態分派
    protected void paint(Graphics g) {
        switch (gameState) {
            case 0: drawStartScreen(g); break;
            case 1: drawGameScreen(g); break;
            case 2: drawPauseScreen(g); break;
            case 3: drawGameOver(g); break;
        }
    }
}

關鍵設計要點總結

設計點說明
單一 Timer 實例全遊戲共用一個 `Timer`,避免多執行緒同步問題
狀態機驅動`gameState` 變數控制邏輯分派,結構清晰易擴展
TimerTask 輕量委派`GAMETask` 僅負責呼叫 `gameTask()`,不包含業務邏輯
遞歸排程每次 `run()` 結尾 `schedule` 下一次,實現遊戲迴路
狀態切換取消舊任務`timer.cancel()` 確保無殘留任務干擾新狀態
repaint() 非阻塞只請求重繪,實際繪製在事件執行緒,不阻塞 Timer 執行緒

常見坑與避雷指南

| 坑點 | 後果 | 解決方案 | |------|------|----------| | `run()` 執行過久 | 後續任務延遲、畫面卡頓、按鍵無回應 | `run()` 僅做邏輯更新 + `repaint()`,耗時操作另闢執行緒 | | 狀態切換未 `cancel()` | 舊任務繼續執行干擾新狀態 | 每次狀態切換必呼叫 `timer.cancel()` | | 多處 `new Timer()` | 多執行緒競爭、資源洩漏 | 全域單一 `Timer` 實例,或確保舊 Timer 已 `cancel()` | | `scheduleAtFixedRate` 用於遊戲迴路 | 系統忙碌時嘗試「追趕」導致連續爆發執行 | 遊戲迴路**必須用 `schedule` (fixed-delay)** | | 忽略 `Canvas.hasRepeatedEvents()` | 長按按鍵行為在不同手機不一致 | 長按處理前必檢查 `hasRepeatedEvents()` |

效能優化建議

  1. 物件池化: `GAMETask`、子彈、敵人等頻繁建立/銷毀物件使用物件池
  2. 避免記憶體分配: `run()` 內避免 `new` 物件、字串拼接,預先分配復用
  3. 幀率自適應: 根據裝置效能動態調整 `schedule` 間隔 (20-60 FPS)
  4. 分離邏輯與繪製: `gameTask()` 只更新狀態,`paint()` 只負責繪製,互不阻塞

總結

J2ME `TimerTask` 是資源受限環境下的**最佳排程選擇**:單執行緒循序執行、無同步負擔、API 極簡。遊戲開發中,結合**狀態機 + 遞歸排程 + 委派模式**,可構建清晰、高效、可維護的主迴路架構。核心原則:**TimerTask 做調度,GameObject 做邏輯,paint() 做繪製,三者分離,各司其職。**

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved