Kotlin—Coroutine(协程)的基本使用
什么是协程
在java中异步都会使用到线程,在kotlin中引入了协程的概念。与线程类似,协程也是用于处理异步的,不过与线程相比更加轻巧。协程完全通过编译技术实现,使用挂起机制来实现异步,而不会阻塞线程。协程是一种避免线程阻塞、开销更小且更加可控的异步操作。
协程的基础使用
创建协程,有三种方式runBlocking
、launch
、async
。
-
runBlocking
创建一个阻塞的协程,当协程内部代码执行完毕后才会执行后面的代码。fun main() { println("Hello") runBlocking { delay(1000) println("World") } println("---end---") }
在
runBlocking
创建的协程里面,调用delay
让此协程挂起1秒。执行代码,首先打印Hello,1秒后打印World,最后打印—end—。注意delay挂起函数只有在协程内部才能调用。 -
launch
在当前协程作用域下面创建一个非阻塞子协程,同时返回个Job
对象,用来控制当前协程。fun main() = runBlocking { println("hello") launch { delay(1000) println("world") } println("---end---") }
使用
runBlocking
来包裹main
函数,函数整体都处于runBlocking
创建的协程作用域下面,然后通过launch
创建一个子协程,挂起1秒之后打印输出。执行代码发现首先输出hello和—end—,等待一秒之后打印world,如何让他按顺序执行。fun main() = runBlocking { println("hello") val job = launch { delay(1000) println("world") } job.join() println("---end---") }
修改代码,拿到
launch
返回的job对象,调用join()
函数,此时会等待job协程内部的代码执行完毕后才会往后执行。执行代码,先打印hello,然后1秒后打印world和—end—。 -
Job
常用方法- isActive
- isCompleted
- isCanceledstart
- cancel
- join
-
GlobalScope.launch
创建一个全局非阻塞协程,fun main() { println("hello") GlobalScope.launch { delay(1000) println("world") } Thread.sleep(1500) println("---end---") }
使用
GlobalScope.launch
在任何地方都能创建一个全局的协程,由于launch
创建的协程是非阻塞的,所以让当前线程睡眠1.5秒等待协程执行完毕。与上面代码打印结果一样。由于使用GlobalScope.launch
时,会创建一个顶层协程。虽然很轻量,但它运行时仍会消耗一些内存资源,所以通常是在协程内部作用域下使用launch
创建协程,而不是使用GlobalScope来创建全局协程。 -
async
创建一个非阻塞协程,同时返回Deferred
,可通过await()
函数获取协程返回的具体值;fun main() = runBlocking { println("hello") val def = async { delay(1000) "world" } println(def.await()) println("---end---") }
-
挂起函数
接着上面例子,将
launch
协程内部的代码封装为一个函数,使用suspend
关键,这个函数就叫挂起函数
,上面的delay
就是一个封装好的挂起函数。fun main() = runBlocking { println("hello") val job = launch { delay1000() } job.join() println("---end---") } suspend fun delay1000(){ delay(1000) println("world") }
-
调度器
上面提到三种创建协程的方式都有一个选填参数
CoroutineContext
,协程的上下文环境。也就是协程调度器,调度器确定了协程执行的线程环境。-
Dispatchers.Default
默认后台线程池里的线程 ; -
Dispatchers.Main
Android主线程; -
Dispatchers.IO
后台线程池里的IO线程 ; -
Dispatchers.Unconfined
不限制,使用父协程所属的线程; -
newSingleThreadContext
使用新的线程。
如需要在协程中切换调度器可使用
withContext
,withContext
返回最后一行代码的返回值。 -
-
Android中的使用
简单了介绍了协程的基本使用,写个小demo,模拟网络请求然后再界面展示数据。在activity中首先要实现
CoroutineScope
接口,然后通过by
关键字将接口的具体实现委托给MainScope
,这样当前activity就是一个协程作用域了。class MainActivity : AppCompatActivity(), CoroutineScope by MainScope() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) button.setOnClickListener { showData() } } // 创建协程,默认在当前线程,也就是UI线程 private fun showData() = launch { progress.visibility = View.VISIBLE textView.text = loadData() progress.visibility = View.GONE } //网络请求 切换为IO线程 private suspend fun loadData()= withContext(Dispatchers.IO) { delay(2000)//挂起2秒 模拟网络请求 "假装这是网络请求到的数据" //返回请求到的数据 } }
基本介绍完了协程的最最最基础的使用,相比于线程除了内存开销更小、不会造成线程阻塞的优点之外之外,个人觉得在没有用RxJava的情况下可以少写很多线程的创建、切换、异步接口的回调。让代码更加干净清爽。
个人学习笔记,如有错误不对的地方欢迎各位大佬指出。