协程是什么:
- 协程是Kotlin中有特色的一项技术,大部分编程语言没有协程这个概念。协程可简单理解成轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的。一个线程内可创建多个协程,协程可在编程语言层面实现不同协程之间的切换,从而在单线程模式下模拟多线程的效果。其中的原理是协程可在某个地方挂起,并且可以重新在挂起处继续运行。而代码执行时的挂起与恢复完全由编程语言控制,与操作系统无关。
协程特点:
轻量
可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作
内存泄漏更少
用的结构化并发机制能帮助追踪运行在协程中的任务,比如在不需要协程的时候取消任务,协程运行时追踪任务,协程执行失败时传播错误信号
内置取消支持
取消操作会自动在运行中的整个协程层次结构内传播
jetpack集成
许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可用于结构化并发。
协程使用:
提示:Kotlin没有将协程纳入标准库的API中,而是以依赖库的形式提供。 使用前需在app/build.gradle文件中添加如下依赖库
例如:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
//用于android
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
代码实例
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
//新建一个协程,从UI线程移出
viewModelScope.launch(Dispatchers.IO) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
}
代码分析
viewModelScope :是在ViewModel KTX扩展中已定义的CoroutineScope(协程作用域),所有的协程都必须在一个作用域内运行。
常见的创建协程作用域方法如下:
fun abc(){
val scope = CoroutineScope(Dispatchers.Main)
scope.launch {
//填入代码
}
}
或者将类继承自CoroutineScope,整个类内即协程作用域
class abc() :CoroutineScope{
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main
fun abcd(){
launch {
//填入代码
}
}
}
kotlin 中 GlobalScope 类提供了几个创建协程的构造函数:
协程构造器
launch: 创建协程
async : 创建带返回值的协程,返回的是 Deferred 类; 可使用 await 的挂起函数调用
withContext:不创建新的协程,指定协程上运行代码块
runBlocking:不是 GlobalScope 的 API,可以独立使用,区别是 runBlocking 里面的 delay 会阻塞线程,而 launch 创建的不会
suspend 挂起
其中suspend将函数声明为挂起函数,表示耗时操作。被suspend声明的函数只能在suspend函数或协程作用域内调用。
suspend fun abc(){
val a = aysnc{
//填入代码
return xxx;
}
var b = a.await()//调用
}
withContext 切换线程
withContext这个函数主要可以切换到指定的线程,并在闭包内的逻辑执行结束之后,自动把线程切回去继续执行:
//在viewmodel中
suspend fun getCollectionResults(){
withContext(Dispatchers.IO){
/*val list= mutableListOf<CardInfo>()
val info0=CardInfo("cctv1", "animals")
val info1=CardInfo("cctv2", "economical")
val info2=CardInfo("cctv13", "news")
val info3=CardInfo("cctv6", "movie")
list.apply { add(info0)
add(info1)
add(info2)
add(info3)}
for (i in 1..15){
list.add( CardInfo("CCTV$i", "details_$i"))
}
delay(2000)*/
val data= async { repository.getAllFavor() }
val list=data.await()
_collectionResults.postValue(list)
}
}
代码中将协程执行操作移置了I/O线程,避免在主线程做耗时操作,因此是主线程安全的
coroutineScope.launch(Dispatchers.Main) { // 在 UI 线程开始
val image = withContext(Dispatchers.IO) { // 切换到 IO 线程,并在执行完成后切回 UI 线程
getImage(imageId) // 将会运行在 IO 线程
}
avatarIv.setImageBitmap(image) // 回到 UI 线程更新 UI
}
Kotlin提供的调度程序
●Dispatchers.Main - 使用此调度程序可在 Android 主线程上运行协程。此调度程序只能用于与界面交互和执行快速工作。示例包括调用 suspend 函数,运行 Android 界面框架操作,以及更新 LiveData 对象。
● Dispatchers.IO - 此调度程序经过了专门优化,适合在主线程之外执行磁盘或网络 I/O。示例包括使用 Room 组件、从文件中读取数据或向文件中写入数据,以及运行任何网络操作。
● Dispatchers.Default - 此调度程序经过了专门优化,适合在主线程之外执行占用大量 CPU 资源的工作。用例示例包括对列表排序和解析 JSON。
● Dispatchers.Unconfined 不限制任何制定线程 高级调度器,不应该在常规代码里使用
可取消的Job:
使用 launch 或 async 创建的每个协程都会返回一个 Job 实例,该实例是其协程的唯一标识并管理其生命周期。 可以将 Job 传递给 CoroutineScope 以进一步管理其生命周期。
class ExampleClass {
...
fun exampleMethod() {
val job = scope.launch {
// 新携程
}
if (...) {
//job的取消并不影响scope
job.cancel()
}
}
}