在Kotlin中如何取消协程?Android节拍器快速点击启动多协程问题求解
问题修复方案
问题根源
- 未持有协程的
Job实例,每次点击播放都会启动全新协程,快速点击时多个协程并行执行节拍播放逻辑,导致音效重复 - 使用
GlobalScope启动协程,既没有和Android组件生命周期绑定易引发内存泄漏,也无法统一管控运行中的协程 - 状态切换和协程启动逻辑未做互斥处理,快速点击时会多次触发协程创建
修复方案
1. 前置准备
不要使用GlobalScope,优先使用Android官方提供的生命周期绑定协程作用域,比如Activity中使用lifecycleScope(需要在模块build.gradle引入androidx.lifecycle:lifecycle-runtime-ktx依赖),同时在类中声明两个成员变量:
// 保存运行中的节拍协程实例 private var metroJob: Job? = null // 给播放状态加volatile注解,保证多线程可见性 @Volatile private var isPlaying = false
2. 修改点击事件逻辑
每次启动新协程前先取消原有协程,从根源避免多协程并行:
playBtn.setOnClickListener { // 切换播放状态 isPlaying = !isPlaying playBtn.setText(if (isPlaying) R.string.stop else R.string.play) // 先取消之前运行的所有协程,避免多实例并行 metroJob?.cancel() if (isPlaying) { // 用生命周期绑定的作用域启动协程,页面销毁时自动回收 metroJob = lifecycleScope.launch { while (isPlaying) { delay(bpmToMillis) metro1.start() } } } }
3. 可选优化:添加点击防抖
如果要避免极端快速点击导致的状态抖动,可以加100ms左右的点击拦截逻辑:
// 新增成员变量记录上次点击时间 private var lastClickTime = 0L playBtn.setOnClickListener { val currentTime = System.currentTimeMillis() // 100ms内的重复点击直接拦截 if (currentTime - lastClickTime < 100) { return@setOnClickListener } lastClickTime = currentTime // 原有点击逻辑... }
额外注意事项
- 协程中不要使用
Thread.sleep(),会阻塞整个线程,直接使用非阻塞的delay()即可 - 如果使用
lifecycleScope,页面销毁时协程会自动取消,不需要额外写回收逻辑 - 如果音效播放存在原生层同步问题,可以给
metro1.start()的调用加锁,避免并行调用异常
内容的提问来源于stack exchange,提问作者Stepan
相关产品推荐
相关产品推荐

