【Android】安卓原生应用播放背景音乐与音效(笔记)


本文提供完整的音频管理器代码,涵盖了背景音乐(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);
    }
}
相关推荐
小墙程序员36 分钟前
一文了解 Android 中的 UID、GID、PID
android
&有梦想的咸鱼&2 小时前
Android Compose 框架文本选择与编辑模块源码深度剖析(三)
android
二流小码农3 小时前
鸿蒙开发:远场通信服务rcp会话问题
android·ios·harmonyos
stevenzqzq5 小时前
kotlin @JvmStatic的使用
android·开发语言·kotlin
氦客5 小时前
Kotlin知识体系(二) : Kotlin的七个关键特性
android·开发语言·kotlin·安卓·特性·data class·密封类
阿豪元代码6 小时前
Perfetto 快速上手指南1 —— Trace 的抓取
android
&有梦想的咸鱼&6 小时前
Android Fresco 框架扩展模块源码深度剖析(四)
android
fatiaozhang95276 小时前
烽火HG680-KB_海思HI3798MV310_安卓9.0_U盘强刷固件包及注意点说明
android·华为·机顶盒rom·魔百盒刷机·移动魔百盒
YEAH!启动!9 小时前
WPS二次开发系列:WPS SDK事件回调
android·java·前端·pdf·word·wps·ppt
stevenzqzq10 小时前
kotlin 线程池封装
android·开发语言·kotlin