SoundPool

一、SoundPool 特点

  • 适合场景:短音效(<5秒)、游戏音效、频繁播放的音效

  • 优势:低延迟、可同时播放多个音效、音量独立控制

  • 限制:不适合长音频(内存占用大)

二、完整实现步骤

步骤1:添加音频文件到项目

  1. res 目录下创建 raw 文件夹:

    复制代码
    app/src/main/res/raw/
  2. .ogg 文件放入(如:explosion.ogg, click.ogg

步骤2:初始化 SoundPool(现代方式 - API 21+)

Kotlin 复制代码
class SoundActivity : AppCompatActivity() {
    // 声明变量
    private lateinit var soundPool: SoundPool
    private val soundMap = HashMap<String, Int>() // 存储音效ID
    private var loaded = false // 加载状态标志
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_sound)
        
        initializeSoundPool()
        loadSounds()
    }
    
    private fun initializeSoundPool() {
        // 配置音频属性
        val audioAttributes = AudioAttributes.Builder()
            .setUsage(AudioAttributes.USAGE_GAME) // 用途:游戏
            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) // 内容类型:音效
            .build()
        
        // 创建 SoundPool
        soundPool = SoundPool.Builder()
            .setMaxStreams(10) // 最大同时播放流数
            .setAudioAttributes(audioAttributes) // 音频属性
            .build()
            
        // 设置加载完成监听器
        soundPool.setOnLoadCompleteListener { _, sampleId, status ->
            if (status == 0) {
                loaded = true
                Log.d("SoundPool", "音效加载完成,ID: $sampleId")
            }
        }
    }
}

步骤3:加载多个音效

Kotlin 复制代码
private fun loadSounds() {
    // 加载音效文件(参数:上下文, 资源ID, 优先级)
    soundMap["explosion"] = soundPool.load(this, R.raw.explosion, 1)
    soundMap["click"] = soundPool.load(this, R.raw.click, 1)
    soundMap["coin"] = soundPool.load(this, R.raw.coin_collect, 1)
    soundMap["jump"] = soundPool.load(this, R.raw.jump, 1)
}

步骤4:播放音效的完整方法

Kotlin 复制代码
/**
 * 播放音效
 * @param soundName 音效名称(在soundMap中的key)
 * @param loop 循环次数:0=不循环,-1=无限循环
 * @param rate 播放速率:1.0=正常,0.5=半速,2.0=倍速
 */
fun playSound(soundName: String, loop: Int = 0, rate: Float = 1.0f) {
    if (!loaded) {
        Log.w("SoundPool", "音效尚未加载完成")
        return
    }
    
    val soundId = soundMap[soundName]
    if (soundId == null) {
        Log.e("SoundPool", "未找到音效: $soundName")
        return
    }
    
    // 播放音效
    val streamId = soundPool.play(
        soundId,      // 音效ID
        1.0f,         // 左声道音量(0.0-1.0)
        1.0f,         // 右声道音量(0.0-1.0)
        1,            // 优先级(0=最低)
        loop,         // 循环次数
        rate          // 播放速率
    )
    
    if (streamId == 0) {
        Log.e("SoundPool", "播放失败")
    }
}

// 使用示例
playSound("click")           // 播放点击音效
playSound("explosion")       // 播放爆炸音效
playSound("coin", loop = 0, rate = 1.2f) // 加速播放

步骤5:音效控制方法

Kotlin 复制代码
/**
 * 暂停指定音效
 */
fun pauseSound(streamId: Int) {
    soundPool.pause(streamId)
}

/**
 * 恢复播放指定音效
 */
fun resumeSound(streamId: Int) {
    soundPool.resume(streamId)
}

/**
 * 停止指定音效
 */
fun stopSound(streamId: Int) {
    soundPool.stop(streamId)
}

/**
 * 设置音量
 * @param streamId 流ID(play()方法的返回值)
 * @param leftVolume 左声道音量 0.0-1.0
 * @param rightVolume 右声道音量 0.0-1.0
 */
fun setVolume(streamId: Int, leftVolume: Float, rightVolume: Float) {
    soundPool.setVolume(streamId, leftVolume, rightVolume)
}

/**
 * 设置播放速率(0.5-2.0)
 */
fun setRate(streamId: Int, rate: Float) {
    soundPool.setRate(streamId, rate)
}

步骤6:资源管理

Kotlin 复制代码
class SoundActivity : AppCompatActivity() {
    // ... 其他代码 ...
    
    /**
     * 手动卸载音效
     */
    fun unloadSound(soundName: String) {
        val soundId = soundMap[soundName]
        if (soundId != null) {
            soundPool.unload(soundId)
            soundMap.remove(soundName)
        }
    }
    
    /**
     * 卸载所有音效
     */
    fun unloadAllSounds() {
        soundMap.values.forEach { soundId ->
            soundPool.unload(soundId)
        }
        soundMap.clear()
    }
    
    override fun onPause() {
        super.onPause()
        // 暂停所有音效
        soundPool.autoPause()
    }
    
    override fun onResume() {
        super.onResume()
        // 恢复音效
        soundPool.autoResume()
    }
    
    override fun onDestroy() {
        // 释放所有资源
        unloadAllSounds()
        soundPool.release()
        super.onDestroy()
    }
}

三、SoundPool 操作的耗时性分析

1. 耗时操作(应该异步)

Kotlin 复制代码
// ❌ 这些操作会阻塞主线程,应该异步执行

// 加载音频文件(最耗时)
soundPool.load(context, R.raw.sound, 1)  // 可能耗时 50-500ms

// 释放资源
soundPool.release()  // 可能耗时 10-100ms

// 初始化 SoundPool(较新版本)
SoundPool.Builder().build()  // 可能耗时 10-50ms

2. 非耗时操作(可以在主线程)

Kotlin 复制代码
// ✅ 这些操作很快,可以在主线程执行

// 播放音效
soundPool.play(soundId, 1.0f, 1.0f, 1, 0, 1.0f)  // < 1ms

// 控制播放
soundPool.pause(streamId)    // < 1ms
soundPool.resume(streamId)   // < 1ms
soundPool.stop(streamId)     // < 1ms

// 设置参数
soundPool.setVolume(streamId, 0.5f, 0.5f)  // < 1ms
soundPool.setRate(streamId, 1.5f)          // < 1ms
复制代码
相关推荐
城东米粉儿2 分钟前
Android MediaPlayer 笔记
android
Jony_25 分钟前
Android 启动优化方案
android
阿巴斯甜34 分钟前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇36 分钟前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_5 小时前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android
_小马快跑_5 小时前
Kotlin | 从SparseArray、ArrayMap的set操作符看类型检查的不同
android
_小马快跑_5 小时前
Android | 为什么有了ArrayMap还要再设计SparseArray?
android
_小马快跑_5 小时前
Android TextView图标对齐优化:使用LayerList精准控制drawable位置
android
_小马快跑_5 小时前
Kotlin协程并发控制:多线程环境下的顺序执行
android
_小马快跑_5 小时前
Kotlin协程异常捕获陷阱:try-catch捕获异常失败了?
android