协程
今日核心目标
- 掌握职场常用协程用法,避开实际开发踩坑点。
- 掌握协程池设计核心逻辑,解决线程过多的性能问题。
- 完成通用协程Scope封装,落地可复用的协程池。
掌握协程3个核心知识点
- 协程Dispatchers优化(贴合你的开发场景)
- Job生命周期管理(解决协程泄漏问题)
- 协程异常处理(保障App稳定性)
协程Dispatchers优化(贴合你的开发场景)
- Dispatchers.Main
- Dispatchers.IO
- Dispatchers.Default
Dispatchers.Main
- 主线程,用于更新UI、处理UI相关操作(例:Toast提示、页面刷新等)。
- 禁止在此线程执行耗时操作,否则会导致App卡顿、ANR。
Dispatchers.IO
- IO线程,用于执行耗时IO操作(例:网络请求、文件读写、蓝牙连接、NDK数据解析等)。
- 线程池可自动扩容,适合多任务并行(例:多设备蓝牙连接)。
Dispatchers.Default
- 默认线程,用于执行CPU密集型操作(例:复杂计算)。
- 不适合IO操作,避免与网络请求、蓝牙、NDK等IO操作混用,防止线程堵塞。
Job生命周期管理(解决协程泄漏问题)
- CoroutineScope#launch {}
- CoroutineScope#async {}
- Job#cancel()
- Job#join()
CoroutineScope#launch
- 无返回值,返回Job对象,可通过cancel()取消任务。
- 适合执行不需要返回结果的操作(例:文件读写、蓝牙连接、数据发送等)。
CoroutineScope#async
- 有返回值,返回Deferred对象,通过await()获取结果。
- 适合执行需要获取结果的操作(例:蓝牙设备信息获取、NDK数据解析等)。
Job#cancel
- 取消机制
- 例:蓝牙断连页面销毁后,蓝牙连接协程继续尝试连接,占用资源。此时必须调用Job.cancel(),否则协程会继续执行,导致内存泄漏。
Job#join
- 等待机制
- 适合多任务顺序执行,等待前一个协程执行完成,再执行下一个
- 例
- 场景1:先登录,再获取用户信息
- 场景2:先连接蓝牙,再获取设备信息
协程异常处理(保障App稳定性)
- try-catch
- CoroutineExceptionHandler
- supervisorScope
try-catch
- 局部异常捕获
- 适用于单个协程的异常处理
- 例:网络请求获取数据失败、单个蓝牙设备连接失败等
CoroutineExceptionHandler
- 全局异常处理器
- 统一捕获所有协程的异常,便于排查问题,避免App闪退
- 例:网络请求、蓝牙连接、数据库操作,异常日志统一打印
supervisorScope
- 监督作用域
- public suspend fun supervisorScope(block: suspend CoroutineScope.() -> R)
- 子协程异常不会影响其他子协程,适合多任务并行场景
- 例:多设备连接时,一个设备连接失败,不会影响其他设备连接
协程池知识点
- 协程池设计(解决性能问题)
- 协程泄漏进阶解决方案(规避泄漏)
- async/await实战(适配有返回值场景)
协程池设计(解决性能问题)
- 痛点:协程Scope默认线程池,在多任务、多设备蓝牙连接场景会自动创建大量线程,导致线程混乱、CPU占用过高、App卡顿等问题。
- 逻辑:通过自定义Dispatcher,控制线程数量,避免线程无限扩容,适配多任务、多设备蓝牙连接并行场景,提升App性能和稳定性。
- 关键点:设置合理线程数(3-5个即可,无需过多,避免资源浪费),确保多任务并行且不占用过多系统资源。
协程泄漏进阶解决方案(规避泄漏)
按场景分析解决方案
- 场景1:蓝牙后台监听协程(全局协程),App退出后台后,协程仍在运行,导致资源浪费和泄露,添加后台状态监听,自动暂停/恢复协程。
- 场景2:NDK长耗时解析协程,解析未完成时页面销毁,单纯取消Job可能无法彻底终止NDK任务,需结合NDK接口,实现协程与NDK任务的联动取消。
async/await实战(适配有返回值场景)
- 用途:适合有返回值的耗时操作,替代Callback获取返回值,简化代码。
- NDK数据解析,返回解析结果。
- 蓝牙设备信息获取,返回设备名称、信号强度等。
- 用法:async启动协程,返回Deferred对象,通过await()获取返回值,await()会挂起协程,直到获取到结果,避免阻塞线程。
- 异常:async协程的异常可通过全局异常处理器捕获,也可通过try-catch局部获取,避免闪退。
- 场景:多设备蓝牙连接时,同时获取多个设备的信息,用async/await实现并行获取,提升效率。
通用协程Scope封装
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancelChildren
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Semaphore
/**
* 通用协程域封装,自带异常隔离+线程池
*/
open class BaseCoroutineScope {
// 父Job:一个子协程崩溃,不会干扰其他子协程
private val parentJob = SupervisorJob()
// 全局异常捕获处理器
private val coroutineHandler = CoroutineExceptionHandler { _, throwable ->
throwable.printStackTrace()
}
/* ----- 绑定默认调度器 ----- */
// IO线程:网络、数据库、文件
protected val ioScope =
CoroutineScope(parentJob + Dispatchers.IO + coroutineHandler)
// 主线程:UI操作
protected val mainScope =
CoroutineScope(parentJob + Dispatchers.Main + coroutineHandler)
// 专用线程池,避免阻塞IO线程:蓝牙、硬件通信
protected val singleTaskScope =
CoroutineScope(parentJob + Dispatchers.Default.limitedParallelism(1) + coroutineHandler)
/**
* 并发任务
*
* @param permits 最大并发数
*/
fun multiTaskLaunch(
permits: Int,
block: suspend () -> Unit
) {
val semaphore = Semaphore(permits)
CoroutineScope(parentJob + Dispatchers.Default + coroutineHandler).launch {
semaphore.acquire()
try {
block()
} finally {
semaphore.release()
}
}
}
// 生命周期销毁时取消所有协程
fun cancelAll() {
parentJob.cancelChildren()
}
// 退出App,一般不用
fun destroy() {
parentJob.cancel()
}
}