kotlin协程

一、介绍

协程就是个线程框架,用同步的方式写异步的代码

二、使用

1.添加依赖

在项目中配置对 Kotlin 协程的支持

build.gradle文件中增加对Kotlin协程的依赖:

  • 项目根目录下的 build.gradle
buildscript {
    ...
    ext.kotlin_coroutines = '1.3.1'
    ...
}
  • Module 下的 build.gradle
dependencies {
    ...
    //依赖协程核心库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_coroutines"
    //依赖当前平台所对应的平台库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$kotlin_coroutines"
    ...
}
  • 核心库和平台库的版本应该保持一致
  • 核心库中包含的代码主要是协程的公共 API 部分。有了这一层公共代码,才使得协程在各个平台上的接口得到统一
  • 平台库中包含的代码主要是协程框架在具体平台的具体实现方式。因为多线程在各个平台的实现方式是有所差异的

2.基础使用

//使用runBlocking顶层函数
runBlocking {
    //耗时代码
}

//使用GlobalScope单例对象,调用launch开启协程
GlobalScope.launch {
    //耗时代码
}

//通过CoroutineContext创建一个 CoroutineScope 对象
val coroutineScope = CoroutineScope(context)
coroutineScope.launch {
   //耗时代码
}
  • runBlocking:适用于单元测试的场景,线程阻塞的
  • GlobalScope.launch:runBlocking的区别在于不会阻塞线程。它的生命周期会和app一致,且不能取消,不推荐使用
  • CoroutineScope(context):可以通过context参数去管理和控制协程的生命周期

可以使用 Dispatchers.IO 参数把任务切到 IO 线程执行:

coroutineScope.launch(Dispatchers.IO) {
    ...
}

使用 Dispatchers.Main 参数切换到主线程执行:

coroutineScope.launch(Dispatchers.Main) {
    ...
}

使用kotlin协程进行网络请求,切换到主线中更新UI

coroutineScope.launch(Dispatchers.Main) {   // 在主线程开启协程
    val user = api.getUser() // IO 线程执行网络请求
    nameTv.text = user.name  // 主线程更新 UI
}

多个网络请求需要等待所有请求结束之后再对 UI 进行更新

coroutineScope.launch(Dispatchers.Main) {
    val avatar = async { api.getAvatar(user) }    // 获取用户头像
    val logo = async { api.getCompanyLogo(user) } // 获取用户所在公司的 logo
    val merged = suspendingMerge(avatar, logo)    // 合并结果
    show(merged) // 更新 UI
}

这个launch函数是:创建一个新的协程,并在指定的线程上运行它,传给 launch 的那些代码,就叫做协程

协程的使用场景:需要切线程或者指定线程的时候

3.withContext的使用

withContext:可以切换到指定的线程,并在闭包内的逻辑执行结束之后,自动把线程切回去继续执行

原始代码(还是有嵌套):

coroutineScope.launch(Dispatchers.IO) {
    val image = getImage(imageId)
    launch(Dispatchers.Main) {
        avatarIv.setImageBitmap(image)
    }
}

结合withContext的使用

coroutineScope.launch(Dispatchers.Main) {      // 在UI线程开始
    val image = withContext(Dispatchers.IO) {  // 切换到IO线程,并在执行完成后切回UI线程
        getImage(imageId)                      // 将会运行在IO线程
    }
    avatarIv.setImageBitmap(image)             // 回到UI线程更新UI
} 

可以把 withContext 放进一个单独的函数里面:

launch(Dispatchers.Main) {              //在UI线程开始
    val image = getImage(imageId)
    avatarIv.setImageBitmap(image)     //执行结束后,自动切换回UI线程
}

fun getImage(imageId: Int) = withContext(Dispatchers.IO) {
    ...
}

编译器会报错,withContext 是一个 suspend 函数,它需要在协程或者是另一个 suspend 函数中调用

launch 与 async的异同点:

相同点:

        都可以用来启动一个协程,返回的都是 Coroutine

不同点:

    async 返回的 Coroutine多实现了Deferred接口,延迟获取

async的使用案例:

coroutineScope.launch(Dispatchers.Main) {
    //async 函数启动新的协程
    val avatar: Deferred = async { api.getAvatar(user) }    // 获取用户头像
    val logo: Deferred = async { api.getCompanyLogo(user) } // 获取用户所在公司的 logo
    //获取返回值
    show(avatar.await(), logo.await())                     // 更新 UI
}

而这个await() 就是用suspend关键字修饰的

4.suspend的使用

是协程中最核心的关键字,暂停或者可挂起的意思,代码执行到 suspend 函数的时候会『挂起』,并且这个挂起是非阻塞式的,它不会阻塞你当前的线程。

suspend fun getImage(imageId: Int) = withContext(Dispatchers.IO) {
    ...
}

挂起的对象是协程

        启动一个协程可以使用launch或者async函数,协程就是这两个函数中闭包的代码块

    launch、async或者其他函数创建的协程,在执行到 suspend 函数时,这个协程会被suspend,也就是被挂起。从当前线程挂起。也就是这个协程从正在执行它的线程上脱离,这个线程不再去管这个协程要做什么了,

        接下来线程就去做它该做的事情,没事情做的时候也可以被回收

        而协程在suspend函数指定的线程中继续执行,在 suspend函数执行完成之后,会自动把线程在切回来

举例:

// 主线程中
GlobalScope.launch(Dispatchers.Main) {
  val image = suspendingGetImage(imageId)  // 获取图片
  avatarIv.setImageBitmap(image)           // 显示出来
}

suspend fun suspendingGetImage(id: String) = withContext(Dispatchers.IO) {
  ...
}

        这段执行在主线程的协程,它实质上会往你的主线程 post 一个 Runnable,这个 Runnable 就是你的协程代码。

        等协程代码块执行完成之后,会自动切回来,相当于在post一个 Runnable,让剩下的代码继续回到主线程去执行

Dispatchers调度器

        它可以将协程限制在一个特定的线程执行,或者将它分派到一个线程池,或者让它不受限制地运行

常用的Dispatchers调度器:

  • Dispatchers.Main:Android 中的主线程
  • Dispatchers.IO:针对磁盘和网络 IO 进行了优化,适合 IO 密集型的任务,比如:读写文件,操作数据库以及网络请求
  • Dispatchers.Default:适合 CPU 密集型的任务,比如计算

总结:

        协程在执行到有suspend标记的函数时,会被suspend,也就是被挂起,而所谓的被挂起,就是切个线程;挂起函数在执行完成之后,协程会重新切回它原先的线程

        在 Kotlin 中所谓的挂起,就是一个稍后会被自动切回来的线程调度操作

        这个「切回来」的动作,在 Kotlin 里叫做resume恢复

suspend 关键字:

        只是一个提醒,就是限制这个函数只能在协程里被调用,如果在非协程的代码中调用,就会编译不通过。

什么时候需要自定义 suspend 函数:

  • 需要做耗时操作,I/O 操作和 CPU 计算工作。比如文件的读写、网络交互、图片的模糊处理
  • 延迟处理操作

具体实现:

  • 给函数加上 suspend 关键字,然后在 withContext 把函数的内容包住就可以了
  • 挂起函数 delay,等待一段时间后再继续往下执行代码

5.非阻塞式挂起

三、总结

  • 协程就是切线程;
  • 挂起指的是可以自动切回来的切线程;
  • 非阻塞式挂起指的是它能用看起来阻塞的代码写出非阻塞的操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值