本文提供完整的音頻管理器代碼,涵蓋了背景音樂(BGM)和短音效的播放控制。無論是游戲中的音效,還是應用中的背景音樂,通過 AudioManager,你可以方便地管理和控制音頻資源。
前言
在 Android 開發中,音頻處理是提升用戶體驗的重要部分,尤其在游戲、音效反饋以及媒體應用中,背景音樂(BGM)和短音效(如點擊音效、通知音效)是常見的需求。通過有效的音頻管理,能確保音效播放的流暢性和低延遲,以及背景音樂的播放控制。
本篇文章將展示如何通過封裝一個 AudioManager 類,來統一管理背景音樂和短音效的播放。我們將實現以下功能:
- 短音效:如點擊、提示音等,通過 SoundPool 實現快速播放。
- 背景音樂(BGM):適用于長時間播放的音頻,通過 MediaPlayer 實現穩定播放。
- 音頻管理器:封裝所有音頻控制操作,方便全局調用,支持音效加載、播放、暫停、停止等功能。
音頻播放機制
在 Android 中,音頻播放通常分為兩類:背景音樂和短音效。
MediaPlayer
使用MediaPlayer播放背景音樂,MediaPlayer 適合播放較長的音頻文件,它支持流式播放,可以處理長時間的音頻內容。
SoundPool
SoundPool 是專為短小音效設計的音頻播放機制,適用于播放簡單的音效(如短的 MP3、WAV 文件)。其特點是延遲低、資源占用少、支持同時播放多個音效,非常適合游戲或交互性較強的應用。
在 Android 中播放短音效(如短的 mp3、wav 文件),推薦用 SoundPool,專門為短小音效優化,延遲低、效率高!
如果用 MediaPlayer 播放短音效,性能沒那么好,啟動也慢一點。
初始化SoundPool
import android.media.AudioAttributes;
import android.media.SoundPool;private SoundPool soundPool;
private int soundId;
創建SoundPool實例
(適配 Android 5.0+ 和更低版本)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).build();soundPool = new SoundPool.Builder().setMaxStreams(5) // 同時播放幾個音效.setAudioAttributes(audioAttributes).build();
} else {// 老版本soundPool = new SoundPool(5, AudioManager.STREAM_MUSIC, 0);
}
加載音效文件
// 例如文件名是 sound_effect.mp3
soundId = soundPool.load(context, R.raw.sound_effect, 1);
播放音效
soundPool.play(soundId, 1f, 1f, 1, 0, 1f);
// 參數解釋:
// soundId -> 加載時返回的 id
// leftVolume -> 左聲道音量 (0-1)
// rightVolume -> 右聲道音量 (0-1)
// priority -> 優先級(通常為 1)
// loop -> 循環次數,0 表示不循環,-1 表示無限循環
// rate -> 播放速率 (0.5-2.0)
釋放資源
在 Activity 或 Fragment 銷毀時調用
@Override
protected void onDestroy() {super.onDestroy();if (soundPool != null) {soundPool.release();soundPool = null;}
}
AudioManager
需求概述
我們的 AudioManager 類將負責以下操作:
- 背景音樂(BGM)控制:播放、暫停、停止和循環。
- 短音效控制:加載、播放、釋放資源。
- 支持 絕對路徑 音頻文件(如 /sdcard/…)。
- 實現 單例模式,便于全局調用。
完整代碼
import android.content.Context;
import android.media.AudioAttributes;
import android.media.MediaPlayer;
import android.media.SoundPool;
import android.os.Build;
import android.util.Log;import java.io.IOException;
import java.util.HashMap;/*** AudioManager - 統一管理 BGM 和 音效*/
public class AudioManager {private static final String TAG = "AudioManager";private static AudioManager instance;// 音效private SoundPool soundPool;private HashMap<Integer, Integer> soundMap;private boolean soundPoolLoaded = false;// 背景音樂private MediaPlayer bgmPlayer;private boolean isBgmPrepared = false;private AudioManager() {soundMap = new HashMap<>();}public static AudioManager getInstance() {if (instance == null) {synchronized (AudioManager.class) {if (instance == null) {instance = new AudioManager();}}}return instance;}// -------------------- SoundPool - 音效部分 --------------------/*** 初始化 SoundPool*/public void initSoundPool(Context context) {if (soundPool != null) return;if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA).setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION).build();soundPool = new SoundPool.Builder().setMaxStreams(10).setAudioAttributes(audioAttributes).build();} else {soundPool = new SoundPool(10, android.media.AudioManager.STREAM_MUSIC, 0);}soundPool.setOnLoadCompleteListener((soundPool, sampleId, status) -> {soundPoolLoaded = true;Log.d(TAG, "SoundPool loaded: " + sampleId);});}/*** 加載音效(絕對路徑)*/public void loadSound(String absolutePath, int soundKey) {if (soundPool == null) {throw new IllegalStateException("SoundPool not initialized! Call initSoundPool(context) first.");}int soundId = soundPool.load(absolutePath, 1);soundMap.put(soundKey, soundId);}/*** 播放音效*/public void playSound(int soundKey) {if (!soundPoolLoaded) {Log.w(TAG, "SoundPool not loaded yet");return;}Integer soundId = soundMap.get(soundKey);if (soundId != null) {soundPool.play(soundId, 1f, 1f, 1, 0, 1f);} else {Log.w(TAG, "Sound key not found: " + soundKey);}}/*** 釋放音效資源*/public void releaseSoundPool() {if (soundPool != null) {soundPool.release();soundPool = null;soundMap.clear();soundPoolLoaded = false;}}// -------------------- MediaPlayer - 背景音樂部分 --------------------/*** 播放背景音樂* @param absolutePath 文件絕對路徑* @param looping 是否循環*/public void playBgm(String absolutePath, boolean looping) {stopBgm(); // 先停止再播放bgmPlayer = new MediaPlayer();try {bgmPlayer.setDataSource(absolutePath);bgmPlayer.setLooping(looping);bgmPlayer.setOnPreparedListener(mp -> {isBgmPrepared = true;mp.start();Log.d(TAG, "BGM started");});bgmPlayer.setOnCompletionListener(mp -> Log.d(TAG, "BGM completed"));bgmPlayer.prepareAsync();} catch (IOException e) {Log.e(TAG, "Error playing BGM: " + e.getMessage());}}/*** 暫停 BGM*/public void pauseBgm() {if (bgmPlayer != null && bgmPlayer.isPlaying()) {bgmPlayer.pause();Log.d(TAG, "BGM paused");}}/*** 繼續播放 BGM*/public void resumeBgm() {if (bgmPlayer != null && !bgmPlayer.isPlaying() && isBgmPrepared) {bgmPlayer.start();Log.d(TAG, "BGM resumed");}}/*** 停止 BGM*/public void stopBgm() {if (bgmPlayer != null) {if (bgmPlayer.isPlaying()) {bgmPlayer.stop();}bgmPlayer.release();bgmPlayer = null;isBgmPrepared = false;Log.d(TAG, "BGM stopped and released");}}// -------------------- 全局釋放 --------------------/*** 釋放所有資源(退出時調用)*/public void releaseAll() {releaseSoundPool();stopBgm();}
}
播放音效
初始化音效池
AudioManager.getInstance().initSoundPool(context);
加載音效
這里采用絕對路徑
AudioManager.getInstance().loadSound("/sdcard/sound/click.wav", 1);
AudioManager.getInstance().loadSound("/sdcard/sound/explosion.wav", 2);
播放音效
AudioManager.getInstance().playSound(1);
播放BGM
播放BGM
AudioManager.getInstance().playBgm("/sdcard/music/background.mp3", true);
暫停 / 繼續播放
AudioManager.getInstance().pauseBgm();
AudioManager.getInstance().resumeBgm();
停止BGM
AudioManager.getInstance().stopBgm();
資源釋放
AudioManager.getInstance().releaseAll();
注意事項
項目 | 要點 |
---|---|
BGM格式 | mp3、aac、wav,推薦 mp3 |
音效格式 | 推薦 wav(低延遲),支持 mp3、ogg |
路徑 | 絕對路徑(如 /sdcard/yourdir/xxx.mp3) |
權限 | 動態申請 READ_EXTERNAL_STORAGE |
動態權限申請:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1001);}
}