UniJa (聯想 Java 平台) 在 MIDP 2.0 javax.microedition.media.Player 基礎上封裝音頻播放。開發早期採用「每次播放開新線程」模式,導致長時間運行 (5-6 小時) 後 CPU 調度開銷累積,出現「死機」現象。本文分析根因,提出單線程複用 Player 的穩健方案。
// 每次播放創建新線程
new Thread() {
public void run() {
snd.playMIDI("/a.mid");
}
}.start();
// 停止時銷毀對象
if (snd != null) snd.destroy(); // 釋放為 null,下次需 new SoundEffects()
缺點:線程創建/銷毀頻繁 → CPU 調度壓力大 → 長時間運行資源洩漏 → 系統假死。
核心原則:應用全生命周期僅維護 一個 Player 實例,播放新音效前 close() 釋放舊資源,再 createPlayer() 載入新數據。
package fancy.test;
import javax.microedition.media.*;
import javax.microedition.media.control.VolumeControl;
import java.io.*;
public class SoundEffects {
private String filename;
private String contentType; // audio/midi, audio/x-wav, audio/basic, audio/mpeg, audio/x-tone-seq
private int loopCount; // 循環次數,1_000_000 模擬無限循環
private Player player;
private InputStream is;
public SoundEffects(int loopCount) {
this.loopCount = loopCount;
}
// 統一播放入口
public void playMIDI(String filename) { playSound(filename, "audio/midi"); }
public void playWAV(String filename) { playSound(filename, "audio/x-wav"); }
public void playAU(String filename) { playSound(filename, "audio/basic"); }
public void playMP3(String filename) { playSound(filename, "audio/mpeg"); }
public void playTone(String filename) { playSound(filename, "audio/x-tone-seq"); }
protected synchronized void playSound(String filename, String contentType) {
close(); // 1. 釋放舊 Player
this.filename = filename;
this.contentType = contentType;
playFile(); // 2. 載入並播放新音頻
}
// 釋放所有資源
protected void close() {
if (player != null) {
player.close();
player = null;
}
if (is != null) {
try { is.close(); } catch (Exception ignored) {}
is = null;
}
filename = null;
System.gc(); // 提示 GC 回收 Player 佔用的本地內存
}
public void destroy() { close(); }
// 載入並啟動播放
private void playFile() {
if (filename == null) return;
try {
is = getClass().getResourceAsStream(filename);
if (is == null) throw new IOException("Resource not found: " + filename);
player = Manager.createPlayer(is, contentType);
player.realize();
VolumeControl vc = (VolumeControl) player.getControl("VolumeControl");
if (vc != null) vc.setLevel(100); // 最大音量
player.setLoopCount(loopCount);
player.start();
} catch (IOException | MediaException e) {
e.printStackTrace();
}
}
// 控制接口
public void startPlay() { try { player.start(); } catch (MediaException e) { e.printStackTrace(); } }
public void pausePlay() { try { player.stop(); } catch (MediaException e) { e.printStackTrace(); } }
public int getState() { return player != null ? player.getState() : Player.CLOSED; }
}
public class GameCanvas extends Canvas {
private SoundEffects bgm = new SoundEffects(1_000_000); // 背景音樂無限循環
private SoundEffects sfx = new SoundEffects(1); // 音效單次播放
protected void startGame() {
bgm.playMIDI("/bgm.mid"); // 進入菜單播放背景音樂
}
protected void enterGameplay() {
bgm.close(); // 停止背景音樂
sfx.playWAV("/jump.wav"); // 播放跳躍音效
}
protected void pauseGame() {
bgm.pausePlay();
}
protected void destroy() {
bgm.destroy();
sfx.destroy();
}
}
| 問題 | 解決方案 |
|---|---|
| MIDI 不支持多 Player 並發 | 全應用單 Player 實例,播放前 close() 釋放 |
| 頻繁切換音效導致卡頓 | 減少音效數量 (建議 1-2 個),避免高頻 createPlayer |
| 長時間運行死機 | 杜絕多線程,單線程複用 + 顯式 close() + System.gc() |
| 無限循環播放 | setLoopCount(Integer.MAX_VALUE) 或大數值 (100 萬) |
| 音量控制 | 獲取 VolumeControl,setLevel(0-100) |
通過 contentType 參數統一支持:
audio/midi - MIDI (最通用)audio/x-wav - WAVaudio/basic - AUaudio/mpeg - MP3 (需設備支持 MMAPI)audio/x-tone-seq - Tone Sequence (MIDP 2.0 標準)在資源受限的 UniJa/功能機平台,單線程複用 Player、顯式資源釋放、控制音效並發數量 是穩定播放音頻的鐵律。本文提供的 SoundEffects 類經實戰驗證,可直接集成到項目中作為音頻管理核心組件。