前言
從實戰項目中萃取的硬核經驗:IO 讀取優化陷阱、JAR 壓縮反直覺真相、跨機型移植適配矩陣、鍵盤事件差異深度分析。每條結論都經過真機 (Nokia 3100/3300/7210/6600/7610, Moto V 系列, SE K700, 三星 S100/S200/C100) 驗證,而非理論推導。
一、IO 讀取優化:實測數據打臉理論
場景:地圖文件讀取 (含圖片切割)
| 方案 | 耗時 (Nokia 3100) | 結論 |
| DataInputStream 包裝 InputStream | 1242 ms | 基準線 |
| 直接用 InputStream + read(byte) | 1065 ms | ✅ **快 14%**,DataInputStream 方法調用開銷大 |
| available() 獲取長度 → 分配 byte[] → read(byte[],0,len) 一次性讀完 | 1154 ms | ❌ **慢 8%**,KVM 內部實現導致大塊讀取反而慢 |
| 封裝 readByte() 為 private static 方法,分散 try-catch | 1089 ms | ⚠️ 略慢,類文件略小但速度優先 |
核心心法
- 簡單流優於複雜流:只讀 byte 用
InputStream.read(),別裝飾 DataInputStream
- 不要臆測 KVM 行為:
available() 常返回 -1,大塊讀取不一定快,**唯有真機實測**
- 速度 > 空間:JAR 包體已壓縮,Class 文件大小微優化無意義
- Nokia 3300 更快:同代碼約 800ms,CPU 頻率/實現差異巨大
二、資源壓縮:JAR 自帶壓縮下的反直覺
實驗:行程編碼 (RLE) 壓縮地圖文件
- 原始地圖:2.23 KB → RLE 後:900 字節 (**壓縮 60%**) ✅
- 打入 JAR 後:**JAR 體積反而變大** ❌
原因分析 (WinRAR 解包對比)
| 文件 | 原始大小 | JAR 內壓縮後大小 |
| 未壓縮地圖 | 2.23 KB | 185 字節 |
| RLE 壓縮地圖 | 900 字節 | 216 字節 |
結論:JAR 使用 DEFLATE (LZ77+Huffman) 壓縮,**已壓縮數據熵較高,二次壓縮效果差甚至負增長**。
最佳實踐
- 直接存原始數據:讓 JAR 壓縮器發揮最大效果
- PNG 優化工具慎用:某些工具 (如 pngcrush 某模式) 優化後 JAR 內體積反增,因破壞了 DEFLATE 友好的數值分布
- 驗證指標:
jar tf -v 查看各文件 JAR 內壓縮大小,而非原始大小
三、移植適配:一套代碼六大版本矩陣
六大目標平台版本
| 版本 | 目標設備 | API 基線 | 關鍵差異 |
| Nokia S40 | 7210, 3100, 3300... | MIDP 1.0 + Nokia UI API | 全屏模式、圖片旋轉、DirectGraphics |
| Nokia S60 | 6600, 7610... | MIDP 2.0 | Nokia UI API 有 Bug (旋轉),改用 MIDP 2.0 Game API |
| Nokia S60 MIDP 2.0 | N-Gage, 後期 S60 | MIDP 2.0 | 標準 Game API,較穩定 |
| Motorola V 系列 | V3, V3x, E680... | MIDP 2.0 | CPU 偏慢,支持 MIDI+WAV 並發播放 |
| Sony Ericsson | K700, W800... | MIDP 2.0 | 性能極佳,**不支持全屏**,screenSizeChanged 無效 |
| 三星 | S100, S200, C100 | MIDP 2.0 | 接近 Moto/SE,細節差異需實測 |
通用適配原則
- 自適應屏幕:所有座標、尺寸用
getWidth()/getHeight()、Font.stringWidth() 計算,**禁用硬編碼**
- 封裝繪圖原語:自實現
drawRotatedImage()、createTranslucentImage(),內部按平台分發 (Nokia UI / MIDP 2.0 / 純軟件)
- 音頻策略分層:
- S40:**無音效** (容量限制 + 喇叭極差)
- 其他:MIDI 背景 + WAV 音效,**無設備支持雙 MIDI 並發**
- Moto/SE:支持 MIDI+WAV 並發,Nokia 不支持
- 資源分包:大圖集、關卡數據按平台打包,運行時動態下載/載入
四、鍵盤事件:設備差異深度解析
Nokia S40 (7210/3100) — 單鍵佇列模式
- 同一時刻**只響應一個按鍵**
- 按住 ↑ 不放,再按 ← → **無 KeyPressed 事件**
- 釋放 ↑ 後,**殘留的 ← 會產生一次 KeyPressed** (硬件持續掃描鍵盤狀態,有等待佇列)
- **斜向移動不可行**,需邏輯層妥協:釋放主鍵後才響應次鍵
WTK 標準模擬器 / Moto / SE — 多鍵並發模式
- 支持**同時檢測多個按鍵按下**
if (up) if (left) 可實現斜向移動
if (up) else if (left) 優先級由代碼順序決定
統一處理策略:按鍵狀態機 + 邏輯層仲裁
// 每幀採樣一次硬件狀態 (getKeyStates 或自維護 pressed[])
// 邏輯層根據遊戲類型決策:
// - 格鬥/射擊:支持組合鍵 (上+攻擊=跳斬)
// - 迷宮/策略:單向優先 (上>下>左>右)
// - 通用:方向鍵互斥,功能鍵 (確認/取消/菜單) 獨立處理
五、實機測試數據速查表
| 指標 | Nokia 3100 | Nokia 3300 | Moto V3 | SE K700 |
| 地圖讀取 (優化後) | 1065 ms | ~800 ms | ~600 ms | ~500 ms |
| 屏幕尺寸 | 128x128 | 128x160 | 176x220 | 176x220 |
| 全屏支持 | Nokia UI | Nokia UI | MIDP 2.0 | **不支持** |
| 可變透明圖片 | 支持 | 支持 | 支持 | 支持 |
| 圖片旋轉 | Nokia UI | Nokia UI | MIDP 2.0 | MIDP 2.0 |
| MIDI+WAV 並發 | 否 | 否 | **是** | **是** |
| CPU 體感速度 | 基準 | 略快 | 偏慢 | **極快** |
六、總結:給後人的避坑指南
- IO 優化:少封裝、多實測、信 InputStream、棄 DataInputStream
- 壓縮策略:別與 JAR 爭壓縮,原始數據交給 DEFLATE 最優
- 移植核心:抽象平台介面、自適應座標、音頻分層、資源分包
- 鍵盤處理:認清單鍵/多鍵模式差異,邏輯層統一仲裁而非硬編碼
- 真機為王:模擬器只能驗證邏輯,性能、按鍵、音頻、屏幕**必須真機**
技術細節會隨平台迭代消失,但**「實測驅動決策、抽象隔離差異、簡單戰勝複雜」**這三條工程哲學永不過時。