第一眼在kotlin官方文档中看到协程,哇什么东西,好像很厉害的样子。而文档中讲述线程之类的却不如它多,第一是因为kotlin可以运行在jvm中,自然是可以现成用java的并发库,所以也就简单的讲了讲。第二就是它希望开发者使用协程。关于协程的概念很多,有些语言有而有些没有。这里的话我们就只讲kotlin中的协程。
what and why:什么是协程,为什么用协程
关于名词解释有很多,比如官方文档:“本质上,协程是轻量级的线程”。还有“协作式”等等,乍一看,哦好像懂了。其实还是不懂,像极了学习计算机网络中的各种抽象的名词概念,而我们又拼命的想只知道这是什么,为什么叫协程。它和线程怎么比较。其实本质上名词只是指代一个具体事物的映射。所以当我们用抽象的东西去解释抽象东西,第一次真的想破头。所以我们不去管为啥叫协程,它的专业解释是什么。我们来看看它具体干了啥事,有什么用。有了具体事物就可以映射概念了。
1 | fun main(args: Array<String>) { |
这个协程的helloworld代码,有一个GlobalScope
的单例对象。持有一个CoroutineContext
上下文对象
1 | public object GlobalScope : kotlinx.coroutines.CoroutineScope { |
去调用扩展函数launch
,下面的时lauchd
的函数声明
1 | public fun kotlinx.coroutines.CoroutineScope.launch( |
前两个参数应该是有默认参数(用idea看的没有看到源码)。我们launch
括号后面的其实是一个lambda(函数字面值,表达式)承载着具体的执行代码。launch返回的是Job
。并且执行代码。可以说lauch
括号内执行这个过程就是一个协程。其实说白了就是一段程序的执行,加上一些特殊效果就被定义为协程。那么什么特殊的效果呢?
launch
中的有CoroutineContext
是可以指定协程运行的环境的。大家都知道进程是计算机资源分配的最小单位,线程是cpu执行的最小单位。而协程就是在线程上执行的有特殊效果的代码片段。
1 | fun main(args: Array<String>) { |
上述代码开启了四个协程。每个协程都运行在不同的线程环境中。
1 | Dispatchers.Default//默认 |
我们通过指定环境很轻松的就切换了线程。这就是协程的特性。在Android中我们常常因为耗时操作需要去将代码运行在IO线程,在运行完毕后需要切换回UI线程,然后又因为业务问题有需要切换。这样切来切去也是常有的事。但是切换通常需要大量的回调和不清晰的代码来执行。你可能想到了使用RxJava来实现比较方便的线程切换。而kotlin通过协程库帮我我们做到了类似RxJava的操作,虽然两者实现思想不一样,但是相对于原来的回调好用、清晰、方便。所以总结下来就是kotlin协程库就是方便我们操作线程的一个库,而kotlin协程则是一个运行在线程上的更小的代码单位。
通俗的搞清楚了kotin协程是什么后,为什么用协程已经不言而喻。为了更加方便的线程切换,更加可阅读性代码。接下来就看看怎么用吧
how
添加依赖
作为额外的扩展依赖
1 | dependencies { |
创建协程
首先我们需要取得CoroutineScope
的实例,然后通过调用launch
基本就就是一个运行协程的创建开启。
1 |
|
第一种是会阻塞线,效果相当于Thread.sleep()
的效果,适用于单元测试。
第二种的话使用了一个全局的scope对象,如果用这个的话,相当于把协程和全局的生命周期绑定在一起了。不能及时取消协程,不推荐。
第三种通过手动的方式构建。
指定运行线程
在开头的介绍的中说明了,在线程中存在很多的回调以及层级等待,线程切换。
1 | coroutineScope.launch(Dispatchers.IO) { |
设置协程的运行环境。
比如常见的Android中网络图片加载
1 | coroutineScope.launch(Dispatchers.IO) { |
使用withContext
指定环境
1 | coroutineScope.launch(Dispachers.Main) { |
但是目前还是将代码杂糅在一个地方,如果我们继续将带协程执行放在函数里面,这就成为了一个挂起函数
1 | //👇 |
挂起的意思其实就是标志下,这里面有个协程会执行,并且可能要花一些时间的意思。调用这个协程的协程后续代码要待会才能继续执行。比如上菜的服务员告诉厨师做什么菜,然后厨师告诉他我要做10分钟,你才能来取菜。所以服务员就不能立马取菜。其实说白了就是线程切换等待通信知识,只不过操作更加简单,控制性更强了。
非阻塞式挂起
其实这玩意没啥玄乎的。通常我们在程序执行中,有耗时,有cpu,有IO操作,有UI操作。影响用户的是UI线程,所以我们将耗时操作放到其他的线程中执行。这样的感受就不那么卡了。而这里的非阻塞挂起其实就是通过线程切换调度完成的,手动的话也是可以实现,毕竟我们老干这事,不然就要ANR了。
参考扔物线的博客