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

J2ME 事件處理機制權威指南:MIDP 2.0 回調序列化與線程安全

     編輯:J2ME

前言:理解事件模型是編寫穩健 MIDlet 的前提

MIDP 事件處理機制看似簡單,實則暗藏線程安全與序列化保證的精妙設計。本文基於 MIDP 2.0 規範文檔,系統闡述四大類 UI 回調、序列化執行保證、serviceRepaint() 特例、定時器線程交互等核心機制。

四大類 UI 回調方法

核心保證:所有 UI 回調嚴格序列化執行

規範原文:所有 UI 回調方法都是序列化的,所以它們永不會並行產生。任何 MIDP 實現都不能在前一個回調未返回的情況下調用另外一個回調。

序列化語義詳細解讀

  1. 互斥執行:任意時刻最多一個 UI 回調在運行
  2. 順序保證:事件隊列 FIFO,前一個返回後才調用下一個
  3. repaint 聚合:多次 repaint() 可能合併為單次 paint() 調用
  4. callSerially 排隊:Runnable.run() 等待所有掛起的 repaint 處理完畢後執行

序列化受保護的回調方法完整清單


Canvas.hideNotify           Canvas.keyPressed
Canvas.keyRepeated          Canvas.keyReleased
Canvas.paint                Canvas.pointerDragged
Canvas.pointerPressed       Canvas.pointerReleased
Canvas.showNotify           Canvas.sizeChanged
CommandListener.commandAction
CustomItem.getMinContentHeight  CustomItem.getMinContentWidth
CustomItem.getPrefContentHeight CustomItem.getPrefContentWidth
CustomItem.hideNotify       CustomItem.keyPressed
CustomItem.keyRepeated      CustomItem.keyReleased
CustomItem.paint            CustomItem.pointerDragged
CustomItem.pointerPressed   CustomItem.pointerReleased
CustomItem.showNotify       CustomItem.sizeChanged
CustomItem.traverse         CustomItem.traverseOut
Displayable.sizeChanged
ItemCommandListener.commandAction
ItemStateListener.itemStateChanged
Runnable.run (via Display.callSerially)

關鍵特例:serviceRepaint() 打破序列化

規範:當 Canvas.serviceRepaint() 被調用時,Canvas.paint() 會被立即同步調用,並等待其返回。即使調用者本身就在一個回調中也一樣。

影響分析

  • 重入風險:paint() 可能在邏輯方法調用棧中被重入調用
  • 狀態不一致:若邏輯代碼修改了繪製依賴的共享狀態,可能導致畫面撕裂
  • 死鎖隱患:若 paint() 內部嘗試獲取邏輯線程持有的鎖

防禦性編程模式


// 邏輯線程中
synchronized(stateLock) {
    updateGameState(); // 修改共享狀態
    // 危險!若此處調用 serviceRepaint(),paint() 會嘗試獲取 stateLock
    // canvas.serviceRepaint(); 
}

// 安全模式:分離狀態更新與繫製觸發
synchronized(stateLock) {
    updateGameState();
    // 僅複製繪製所需數據到不可變快照
    renderSnapshot = createRenderSnapshot();
}
canvas.repaint(); // 異步請求,不會重入

// paint() 中
public void paint(Graphics g) {
    RenderSnapshot snap;
    synchronized(stateLock) { // 快速獲取鎖
        snap = renderSnapshot; // 只讀快照
    }
    drawFromSnapshot(g, snap); // 無鎖繪製
}

定時器線程:並發的「特區」

規範:定時器事件並非 UI 事件,其回調方法可以和 UI 事件回調同時調用。同一 Timer 對象中的多個 TimerTask 則是序列化調用的。

並發風險場景


// 定時器線程
timer.schedule(new TimerTask() {
    public void run() {
        // 危險!與 UI 線程並發訪問 sharedData
        sharedData.update(); 
    }
}, 0, 100);

// UI 線程 (paint/keyPressed 等)
public void paint(Graphics g) {
    // 並發讀取 sharedData → 數據競爭
    g.drawString(sharedData.getValue(), 0, 0, 0);
}

三種線程安全方案

  1. Display.callSerially() 統一到 UI 線程(推薦)
    
    timer.schedule(new TimerTask() {
        public void run() {
            Display.getDisplay(midlet).callSerially(new Runnable() {
                public void run() {
                    sharedData.update(); // 安全:在 UI 線程執行
                    canvas.repaint();
                }
            });
        }
    }, 0, 100);
    
  2. volatile + 原子操作:適用於單一基本類型變量
  3. synchronized 細粒度鎖:鎖定時間極短,避免阻塞 UI 線程導致 ANR

最佳實踐總結

  1. UI 狀態單線程原則:所有 UI 相關狀態只在 UI 事件線程讀寫
  2. 定時器/網絡/IO 線程:僅負責數據準備,通過 callSerially() 將更新委派給 UI 線程
  3. 避免 serviceRepaint():除非極少數極高幀率遊戲場景,否則統一使用 repaint()
  4. 共享數據不可變快照模式:邏輯線程構建快照,UI 線程只讀渲染
  5. 鎖粒度最小化:synchronized 僅保護極小臨界區,嚴禁在鎖內調用系統 API

精通事件模型與線程交互,是從「能跑」到「穩健」的分水嶺。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
類別代表方法觸發源屬性
高層命令CommandListener.commandAction()用戶選擇軟鍵/菜單項高級 API 專用
低層按鍵/指針Canvas.keyPressed/Released/Repeated()
Canvas.pointerPressed/Dragged/Released()
物理按鍵/觸摸屏Canvas/CustomItem
繪製回調Canvas.paint()
CustomItem.paint()
repaint() 請求由系統調度
序列化執行Runnable.run() (via Display.callSerially())應用程序主動請求保證在事件隊列中執行