一、什么是协程?
协程:可以简单地将它理解成一种轻量级的线程
协程允许我们在单线程模式下模拟多线程编程的效果,
代码执行时的挂起与恢复完全是由编程语言来控制的,和操作系统无关。
这种特性使得高并发程序的运行效率得到了极大的提升。
协程让异步线程同步化,杜绝回调地狱。
协程最核心的点就是,函数或者一段程序能够被挂起,稍后再在挂起的位置恢复。
二、Android中协程用来解决什么问题?
1、处理耗时任务:这种任务常常会阻塞主线程
2、保证主线程安全:即确保安全地从主线程调用任何suspend函数
三、需要添加地依赖
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
四、创建协程
1、runBlocking:阻塞主线程
runBlocking {
}
2、GlobalScope.launch:顶级协程,不阻塞主线程
GlobalScope.launch {
}
3、在协程中使用 launch 新建多个子协程(同一线程池),launch 和 launch之间是并发执行,
只有在 CoroutineScope 作用域下才可以使用 launch。
GlobalScope.launch {
launch {
}
launch {
}
launch {
}
}
runBlocking {
launch {
}
launch {
}
launch {
}
}
CoroutineScope(Dispatchers.Default).launch {
}
五、协程的两部分
Kotlin的协程实现分为两个层次:
* 基础设施层,标准库的协程API,主要对协程提供了概念和语义上的最基本的支持
* 业务框架层,协程的上层框架支持
我们可以使用基础设施层创建协程:
val continuation = suspend { // 协程体
5
}.createCoroutine(object : Continuation<Int> {
override val context: CoroutineContext = EmptyCoroutineContext
override fun resumeWith(result: Result<Int>) {
println(result)
}
})
continuation.resume(Unit)
我们知道了其实创建协程的过程,在基础设施层中也是看起来那么复杂。
六、协程的挂起与恢复
常规函数基本操作包括:invoke(或call)和return,协程新增了suspend和resume:
* suspend:也称为挂起或暂停,用于暂停执行当前协程,并保存所有局部变量;
* resume:用于让已暂停的协程从其暂停处继续执行。
使用suspend关键字修饰的函数叫作挂起函数。
挂起函数只能在协程体内或其他挂起函数内调用。
七、协程调度器
Dispatchers.Default:表示会使用一种默认低并发的线程策略,当你要执行的代码属于计算密集型任务时,
开启过高的并发反而可能会影响任务的运行效率,此时就可以使用Dispatchers.Default。
场景:数组排序,JSON数据解析,处理差异判断。
Dispatchers.IO:表示会使用一种较高并发的线程策略,当你要执行的代码大多数时间是在阻塞和等待中,
比如说执行网络请求时,为了能够支持更高的并发数量,此时就可以使用Dispatchers.IO。
场景:数据库、文件读写、网络处理
Dispatchers.Main:则表示不会开启子线程,而是在Android 主线程中执行代码,
但是这个值只能在Android 项目中使用。
场景:调用suspend函数、调用UI函数、更新LiveData
八、挂起和阻塞
GlobalScope.launch(Dispatchers.Main) { // 主线程
// delay是一个非阻塞式的函数,让协程挂起,不影响其它协程运行
// 让协程挂起15秒
delay(15000) // 假设有15s的耗时操作
}
以上协程已经切换到了Main线程调度器,
delay 是挂起函数,可以让delay函数暂时挂起,挂起函数不会阻塞线程(主线程或其它线程),
另外,协程体本身就是一个挂起函数。
如果不使用协程,在主线程中,存在15秒耗时操作,会引起ANR。
比如:Thread.sleep(15000)
Thread.sleep函数是真正的阻塞函数,它会让当前线程阻塞。
耗时处理一般在异步线程来处理,异步线程不仅创建了多余的内存,而且异步线程执行完之后,需要切换线程之后才能更新UI,
如果不使用协程,使用存java的方式切换线程,代码量较多,后期维护成本较大。
使用协程,可以简化代码,而且优化了并发速度。
我们知道,协程体中代码是暂时被挂起的,不会引起阻塞,但是为了更好的并发,我们将耗时操作放到Dispatchers.Default或者Dispatchers.IO调度器上处理,优化有的协程代码如下:
GlobalScope.launch(Dispatchers.Main) { // 主线程
withContext(Dispatchers.Default) { // 耗时操作在IO中执行
// delay是一个非阻塞式的函数,让协程挂起,不影响其它协程运行
// 让协程挂起15秒
delay(15000) // 假设有15s的耗时操作
}
// 更新UI
//。。。执行更新UI操作。。。
}
九、协程的任务泄露
当某个协程任务丢失,会导致内存、CPU、磁盘等资源浪费,甚至发送一个无用的网络请求,这种情况称为任务泄露。
为了能够避免协程泄露,Kotlin引入了结构化并发机制。
使用结构化并发可以做到:
取消任务,当某项不再需要时取消它。
追踪任务,当任务正在执行时,追踪它。
发出错误信号,当协程失败时,发出错误信号表明有错误发生。
CoroutinesScope:定义协程必须指定其CoroutinesScope,它会跟踪所有协程,同样它还可以取消由它所启动的所有协程。
常用的相关API有:
GlobalScope,生命周期是process级别的,即使Activity或Fragment已经被销毁,协程仍然在执行。
GlobalScope.launch { }
MainScope,在Activity中使用,可以在onDestory中取消协程。
private val mainScope = MainScope()
mainScope.launch { // 创建协程
}
// 在onDestroy中取消协程,防止协程泄露
override fun onDestroy() {
super.onDestroy()
mainScope.cancel()
}
一般使用委托的方式取处理:
定义一个基类:
abstract class ScopedActivity: AppCompatActivity(), CoroutineScope by MainScope(){
override fun onDestroy() {
super.onDestroy()
cancel()
}
}
这样就可以方便的在Activity中使用协程了,而且还不用担心协程泄露,
在Activity中使用时,直接launch就可以了:
launch {
}
viewModelScope,只能在ViewModel中使用,绑定ViewModel的生命周期。
继承 ViewModel 或 AndroidViewModel
依赖:
implementation"androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"
viewModelScope.launch { // 创建协程
}
lifecycleScope,只能在Activity、Fragment中使用,会绑定Activity和Fragment的生命周期。
依赖:
implementation"androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
lifecycleScope.launch {
}
和生命周期绑定,会自动取消。
作者:NoBugException
链接:https://www.jianshu.com/p/d7efc3e3cc19
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
网友评论