上一篇文章介绍了什么是协程,以及协程的作用,优点等,还没了解过的同学,可以去看我上一篇关于协程的文章,链接放在下面
https://developer.aliyun.com/article/999065?spm=a2c6h.24874632.expert-profile.12.30ed6e4fZlpRRb
这篇文章我们来讲协程的使用,简单的使用,毕竟协程是一个很强大的设计模式,深入了解需要花很多的时间,我们先从简单开始,其实学会了简单的使用,基本已经可以满足我们平时的开发需要了,话不多说,开始。
在开始前,要先贯彻一句话,就是上一篇文章讲到的协程的核心,归纳为一句话就是:
协程异步就是将耗时的函数标记为suspend,并在协程中调用!不需要开启新线程,不会阻塞UI。
先看kotlin中协程的简单创建,后面我们再举个例子说明上面的总结。创建前要先了解几个概念
一、作用域
协程是通过作用域来创建的,这里有几个常见的作用域,分别是
GlobalScope - 不推荐使用
GlobalScope是全局协程,生命周期与Application一样,一般不推荐使用
runBlocking{} - 主要用于测试
runBlocking主要用于测试,开启的协程会阻塞开启协程的线程,这种异步没有意义
不常用、不推荐!
MainScope() - 可用于开发
常用,需要手动停止,这是在应用中最推荐使用的协程使用方式,为自己的组件实现CoroutieScope接口,在需要的地方使用launch{}方法启动协程,在Android中常在Activity\Fragment中使用,缺点是需要手动停止,如果Activity销毁了,忘记手动停止就会造成内容泄漏
lifecycleScope和viewModelScope
这两个主要是Android中使用协程,lifecycle对于协程的扩展封装,主要用在Android 开发过程中,我们下一篇讲解在Android中使用协程的时候再讲。
这里只要明白lifecycleScope主要用于Activity\Fragment中,viewModelScope主要用于viewModel中,并且两者都自动绑定生命周期,不需要手动停止。
二、讲完了作用域,介绍需要介绍一下调度器
Dispatchers.Default
默认的调度器,适合处理后台计算,是一个CPU密集型任务调度器。注意它和IO共享线程池,只不过限制了最大并发数不同。
Dispatchers.IO
很显然这是用来执行阻塞 IO 操作的,是和Default共用一个共享的线程池来执行里面的任务。根据同时运行的任务数量,在需要的时候会创建额外的线程,当任务执行完毕后会释放不需要的线程。
Dispatchers.Unconfined
由于Dispatchers.Unconfined未定义线程池,所以执行的时候默认在启动线程。遇到第一个挂起点,之后由调用resume的线程决定恢复协程的线程。
Dispatchers.Main
指定执行的线程是主线程,在Android上就是UI线程
调度器其实可以理解为自定运行环境,有点类似与线程,我们指定在IO输入中去做耗时操作,还是切换到主线程操作,类似于这个道理,但是又不是线程的切换。
三、作用域有了,调度器也有了,还差最后一个,那就是启动方法
启动方法有两种,分别是
launch:启动新协程,launch的返回值为Job,协程的执行结果不会返回给调用方。
async:启动新协程,async的返回值为Deferred,Deferred继承至Job,可通过调用Deferred::await获取协程的执行结果,其中await是挂起函数。
看看两者的区别:
launch(返回Job)与async(返回Deferred)的区别:
launch启动的协程没有返回结果;async启动的协程有返回结果,该结果可以通过Deferred的await方法获取。
launch启动的协程有异常会立即抛出;async启动的协程的异常不会立即抛出,会等到调用Deferred::await的时候才将异常抛出。
async适合于一些并发任务的执行,例如有这样的业务:做两个网络请求,等两个请求都完成后,一起显示请求结果。
好了,启动条件需要满足的三个条件都已经讲解完了,分别就是作用域、调度器、启动方法,下面看看一个简单启动协程的栗子
launch方式:
class MainActivity : AppCompatActivity() {
/**
* 使用官方库的 MainScope()获取一个协程作用域用于创建协程
*/
private val mScope = MainScope()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 创建一个默认参数的协程,其默认的调度模式为Main 也就是说该协程的线程环境是Main线程
val job1 = mScope.launch {
// 这里就是协程体
// 延迟1000毫秒 delay是一个挂起函数
// 在这1000毫秒内该协程所处的线程不会阻塞
// 协程将线程的执行权交出去,该线程该干嘛干嘛,到时间后会恢复至此继续向下执行
delay(1000)
}
// 创建一个指定了调度模式的协程,该协程的运行线程为IO线程
val job2 = mScope.launch(Dispatchers.IO) {
// 此处是IO线程模式
// 切线程 将协程所处的线程环境切至指定的调度模式Main
withContext(Dispatchers.Main) {
// 现在这里就是Main线程了 可以在此进行UI操作了
}
}
// 下面直接看一个例子: 从网络中获取数据 并更新UI
// 该例子不会阻塞主线程
mScope.launch(Dispatchers.IO) {
// 执行getUserInfo方法时会将线程切至IO去执行
val userInfo = getUserInfo()
// 获取完数据后 切至Main线程进行更新UI
withContext(Dispatchers.Main) {
// 更新UI
}
}
}
/**
* 获取用户信息 该函数模拟IO获取数据
* @return String
*/
private suspend fun getUserInfo(): String {
return withContext(Dispatchers.IO) {
delay(2000)
"Kotlin"
}
}
override fun onDestroy() {
super.onDestroy()
// 取消协程 防止协程泄漏 如果使用lifecycleScope则不需要手动取消
mScope.cancel()
}
}
async方式:主要用于获取返回值和并发
fun asyncTest() {
mScope.launch {
// 开启一个IO模式的线程 并返回一个Deferred,Deferred可以用来获取返回值
// 代码执行到此处时会新开一个协程 然后去执行协程体 父协程的代码会接着往下走
val deferred = async(Dispatchers.IO) {
// 模拟耗时
delay(2000)
// 返回一个值
"Quyunshuo"
}
// 等待async执行完成获取返回值 此处并不会阻塞线程 而是挂起 将线程的执行权交出去
// 等到async的协程体执行完毕后 会恢复协程继续往下执行
val date = deferred.await()
}
}
以上就是本篇的内容,下一篇讲讲协程在并发上的优势,that's all