本文提供完整的音频管理器代码,涵盖了背景音乐(BGM)和短音效的播放控制。无论是游戏中的音效,还是应用中的背景音乐,通过 AudioManager,你可以方便地管理和控制音频资源。
前言
在 Android 开发中,音频处理是提升用户体验的重要部分,尤其在游戏、音效反馈以及媒体应用中,背景音乐(BGM)和短音效(如点击音效、通知音效)是常见的需求。通过有效的音频管理,能确保音效播放的流畅性和低延迟,以及背景音乐的播放控制。
本篇文章将展示如何通过封装一个 AudioManager 类,来统一管理背景音乐和短音效的播放。我们将实现以下功能:
- 短音效:如点击、提示音等,通过 SoundPool 实现快速播放。
- 背景音乐(BGM):适用于长时间播放的音频,通过 MediaPlayer 实现稳定播放。
- 音频管理器:封装所有音频控制操作,方便全局调用,支持音效加载、播放、暂停、停止等功能。
音频播放机制
在 Android 中,音频播放通常分为两类:背景音乐和短音效。
MediaPlayer
使用MediaPlayer播放背景音乐,MediaPlayer 适合播放较长的音频文件,它支持流式播放,可以处理长时间的音频内容。
SoundPool
SoundPool 是专为短小音效设计的音频播放机制,适用于播放简单的音效(如短的 MP3、WAV 文件)。其特点是延迟低、资源占用少、支持同时播放多个音效,非常适合游戏或交互性较强的应用。
在 Android 中播放短音效(如短的 mp3、wav 文件),推荐用 SoundPool,专门为短小音效优化,延迟低、效率高!
如果用 MediaPlayer 播放短音效,性能没那么好,启动也慢一点。
初始化SoundPool
java
import android.media.AudioAttributes;
import android.media.SoundPool;
private SoundPool soundPool;
private int soundId;
创建SoundPool实例
(适配 Android 5.0+ 和更低版本)
java
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);
}
加载音效文件
java
// 例如文件名是 sound_effect.mp3
soundId = soundPool.load(context, R.raw.sound_effect, 1);
播放音效
java
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 销毁时调用
java
@Override
protected void onDestroy() {
super.onDestroy();
if (soundPool != null) {
soundPool.release();
soundPool = null;
}
}
AudioManager
需求概述
我们的 AudioManager 类将负责以下操作:
- 背景音乐(BGM)控制:播放、暂停、停止和循环。
- 短音效控制:加载、播放、释放资源。
- 支持 绝对路径 音频文件(如 /sdcard/...)。
- 实现 单例模式,便于全局调用。
完整代码
java
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();
}
}
播放音效
初始化音效池
java
AudioManager.getInstance().initSoundPool(context);
加载音效
这里采用绝对路径
java
AudioManager.getInstance().loadSound("/sdcard/sound/click.wav", 1);
AudioManager.getInstance().loadSound("/sdcard/sound/explosion.wav", 2);
播放音效
java
AudioManager.getInstance().playSound(1);
播放BGM
播放BGM
java
AudioManager.getInstance().playBgm("/sdcard/music/background.mp3", true);
暂停 / 继续播放
java
AudioManager.getInstance().pauseBgm();
AudioManager.getInstance().resumeBgm();
停止BGM
java
AudioManager.getInstance().stopBgm();
资源释放
java
AudioManager.getInstance().releaseAll();
注意事项
项目 | 要点 |
---|---|
BGM格式 | mp3、aac、wav,推荐 mp3 |
音效格式 | 推荐 wav(低延迟),支持 mp3、ogg |
路径 | 绝对路径(如 /sdcard/yourdir/xxx.mp3) |
权限 | 动态申请 READ_EXTERNAL_STORAGE |
动态权限申请:
java
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);
}
}