根因:**真機 GPU/CPU 極弱、無硬件加速、GC 停頓敏感、記憶體碎片嚴重**。paint() 為渲染熱點,**任何低效操作被放大數十倍**。
// ❌ 災難寫法:每幀讀取圖片、創建對象
public void paint(Graphics g) {
try {
Image bg = Image.createImage("/back.png"); // IO + 解碼 + 內存分配
g.drawImage(bg, 0, 0, Graphics.TOP | Graphics.LEFT);
} catch (Exception e) {}
}
// ✅ 正確:預載入,paint 僅引用
private Image bgImage;
public void init() { // 或構造器
try { bgImage = Image.createImage("/back.png"); } catch (Exception e) {}
}
public void paint(Graphics g) {
g.drawImage(bgImage, 0, 0, Graphics.TOP | Graphics.LEFT);
}
drawLine 漸變)替代大圖pngcrush -brute、ImageOptim 無損壓縮
// 方式一:repaint(x,y,w,h) —— 內部自動設置 clip
repaint(10, 10, 50, 30); // 僅重繪 50x30 區域
// 方式二:手動 clip(更精確控制)
public void paint(Graphics g) {
g.setClip(10, 10, 50, 30);
// 僅繪製變化區域
g.drawImage(dirtyImage, 10, 10, Graphics.TOP | Graphics.LEFT);
// 其餘區域保持不變(雙緩衝自動保留)
}
原理:Graphics 維護一個**裁剪矩形**,像素操作僅影響矩形內部。
dirty = true,渲染幀檢查後重置Thread.sleep(1000/目標FPS)
// ❌ paint 中計算座標、字符串拼接
public void paint(Graphics g) {
String msg = "Score: " + score + " Level: " + level; // new String 每幀
int x = (getWidth() - g.getFont().stringWidth(msg)) / 2;
g.drawString(msg, x, 10, Graphics.TOP | Graphics.LEFT);
}
// ✅ 邏輯線程預計算,paint 僅繪製
private String scoreStr;
private int scoreX;
private boolean uiDirty;
public void updateUI() { // 邏輯線程調用,僅分數變化時
scoreStr = "Score: " + score + " Level: " + level;
scoreX = (width - font.stringWidth(scoreStr)) / 2;
uiDirty = true;
}
public void paint(Graphics g) {
if (uiDirty) {
g.drawString(scoreStr, scoreX, 10, Graphics.TOP | Graphics.LEFT);
uiDirty = false;
}
}
// 檢測設備支援
boolean hwDoubleBuffered = isDoubleBuffered(); // Canvas 方法
// 軟件雙緩衝(通用)
private Image offscreen;
private Graphics offG;
public void init() {
offscreen = Image.createImage(getWidth(), getHeight()); // 可變圖像
offG = offscreen.getGraphics();
}
public void renderFrame() { // 邏輯線程調用
// 1. 所有繪製操作導向 offG
offG.setColor(0x000000);
offG.fillRect(0, 0, getWidth(), getHeight());
drawGame(offG);
// 2. paint 僅一行:將離屏圖像貼到屏幕
}
public void paint(Graphics g) {
g.drawImage(offscreen, 0, 0, Graphics.TOP | Graphics.LEFT);
}
優勢:**paint() 極簡、零計算、零 new、零 clip 變更**,繪製延遲穩定在 1-2ms。
| 檢查項 | 工具/方法 | 目標 |
|---|---|---|
| paint() 分配對象 | 代碼審查 / 輕量級 Profiler | 0 次/幀 |
| Image createImage 調用 | 同上 | 0 次/幀 |
| 字符串拼接 | 同上 | 0 次/幀 |
| 完整屏幕重繪比例 | 日誌統計 | < 20% |
| 幀耗時 (真機) | System.currentTimeMillis() 埋點 | < 16ms (60fps) / < 33ms (30fps) |
| GC 頻率 | VM 參數 -verbose:gc / 真機日誌 | 無全局 GC / 幀間無 GC |
paint() 優化本質是**將「計算」與「繪製」徹底分離、將「變」與「不變」徹底隔離**。遵循六大鐵律、建立自動化檢查機制,即可讓同一份代碼在模擬器與真機上**同樣流暢**,消除「模擬器騙我」的痛苦。