Kotlin中非阻塞循环实现:音频播放与倒计时同步问题求助
问题:Kotlin Android音频播放与倒计时非阻塞实现
我是Kotlin编程初学者,目前正在开发一款小型应用,需要实现两个功能:
- 先播放一段音频2秒,接着播放另一段音频20秒;
- 在音频播放期间从22倒计时至0。
单独的倒计时和单独的音频播放功能均正常,我编写的音频播放代码如下:
var player1 = MediaPlayer.create(this, com.example.dindon.R.raw.bell) var player2 = MediaPlayer.create(this, com.example.dindon.R.raw.pink) player1!!.setVolume(1F, 1F) player1!!.start() var timeNow = LocalDateTime.now() while (LocalDateTime.now().minusSeconds(2) < timeNow) {} player1!!.stop() player1.prepareAsync(); player2!!.start() timeNow = LocalDateTime.now() while (LocalDateTime.now().minusSeconds(20) < timeNow) {} player2!!.stop() player2.prepareAsync(); player1!!.release() player1 = null player2!!.release() player2 = null
这段代码中的两个while循环是为了保证音频播放指定时长,否则stop指令会紧随start执行,导致无法听到音频。但问题在于这两个循环会阻塞其他所有操作,启动后无法看到倒计时及应用内其他变化,甚至会阻止键盘监听器执行(我需要该监听器输入音频循环播放的次数)。我尝试过线程、Thread.sleep等方案,但仍然存在阻塞问题,每次执行while循环时应用看似冻结,实际上倒计时并未停止,只是循环结束后才显示0。
我希望实现音频完整播放且不干扰其他功能(尤其是倒计时)的非阻塞方案,特此求助。
解决方案:非阻塞式音频播放+倒计时实现
核心思路是利用MediaPlayer的完成监听回调控制音频切换,同时用Android原生工具或Kotlin协程实现倒计时,全程不阻塞主线程。
方案1:MediaPlayer监听+CountDownTimer(原生可靠)
1. 定义全局变量(避免局部变量被回收)
private var player1: MediaPlayer? = null private var player2: MediaPlayer? = null private var countDownTimer: CountDownTimer? = null
2. 初始化播放器并设置完成监听
// 初始化音频1,设置播放完成后触发音频2启动 player1 = MediaPlayer.create(this, R.raw.bell).apply { setVolume(1F, 1F) setOnCompletionListener { mp -> mp.stop() mp.release() player1 = null // 音频1结束后启动音频2 player2?.start() } } // 初始化音频2,设置播放完成后清理资源 player2 = MediaPlayer.create(this, R.raw.pink).apply { setVolume(1F, 1F) setOnCompletionListener { mp -> mp.stop() mp.release() player2 = null // 音频2结束后停止倒计时(如果未完成) countDownTimer?.cancel() } }
3. 启动整个流程(倒计时+音频播放)
fun startFullProcess() { // 启动22秒倒计时,每秒更新UI countDownTimer = object : CountDownTimer(22 * 1000, 1000) { override fun onTick(millisUntilFinished: Long) { // 更新倒计时显示,替换成你的UI控件 val remainingSeconds = millisUntilFinished / 1000 binding.tvCountDown.text = remainingSeconds.toString() } override fun onFinish() { binding.tvCountDown.text = "0" // 倒计时结束后停止音频2(如果还在播放) player2?.stop() player2?.release() player2 = null } }.start() // 启动第一段音频 player1?.start() }
4. 页面销毁时清理资源
override fun onDestroy() { super.onDestroy() player1?.release() player2?.release() countDownTimer?.cancel() }
方案2:Kotlin协程实现(代码更简洁)
如果熟悉Kotlin协程,可用delay替代循环等待,同时在协程中处理音频切换:
private var player1: MediaPlayer? = null private var player2: MediaPlayer? = null private var processJob: Job? = null fun startFullProcess() { processJob = lifecycleScope.launch { // 启动倒计时协程 launch { var count = 22 while (count >= 0) { binding.tvCountDown.text = count.toString() delay(1000) count-- } } // 播放音频1并等待2秒 player1 = MediaPlayer.create(this@MainActivity, R.raw.bell).apply { setVolume(1F, 1F) start() } delay(2000) player1?.stop() player1?.release() player1 = null // 播放音频2并等待20秒 player2 = MediaPlayer.create(this@MainActivity, R.raw.pink).apply { setVolume(1F, 1F) start() } delay(20000) player2?.stop() player2?.release() player2 = null // 音频播放完成后取消倒计时(如果未结束) processJob?.cancel() } } override fun onDestroy() { super.onDestroy() player1?.release() player2?.release() processJob?.cancel() }
关键注意事项
- 绝对不要在主线程使用
while循环或Thread.sleep,会直接阻塞UI更新 - 务必在页面生命周期结束时释放MediaPlayer和倒计时资源,避免内存泄漏
- 协程方案推荐使用
lifecycleScope(需依赖AndroidX),确保协程随页面销毁自动取消
内容的提问来源于stack exchange,提问作者Andrea Carta
相关产品推荐
相关产品推荐

