【Android】浅谈kotlin协程应用
一,概述
根据【Android】kotlin协程实现原理-CSDN博客,笔者知道了kotlin#协程本质,无外乎就是挂起-恢复状态机管理。与线程不同的是挂起点由用户决定,也就是kotlin#suspend关键字,因此协程又被称作用户态线程。
本文根据协程实现原理,详细讲解下kotlin的协程如何应用。
二,应用
1,协程启动
根据是否需要协程体返回值,kotlin提供了两种创建协程的方式,launch和async。
a,launch
如果不关心返回值,通过launch即可开启一个协程,launch函数返回一个Job对象,地位等同于线程的Thread对象。
GlobalScope.launch {println("Hello World!")}
launch函数需传递三个参数,如下:
context参数指协程上下文,这里可通过Dispatchers指定几个context,
Dispatchers.Default:默认上下文,内部会使用默认线程池调度挂起-恢复点。
Dispatchers.Main:UI平台相关,比如Android平台的主线程。决定挂起-恢复点在主线程。
默认是一个EmptyCoroutineContext
--------------------------------------------------------------------------------------------
start参数决定协程体执行时机,策略在CoroutineStart中,策略如下
DEFAULT --立即执行协程体
LAZY -- 被调用时才执行协程体
ATOMIC --原子地执行协程体
UNDISPATCHED --立即执行协程体直到遇到第一个挂起点。
--------------------------------------------------------------------------------------------
block参数就是传入的协程体了,定义如下,协程体本身是CoroutineScope的扩展函数。
block: suspend CoroutineScope.() -> Unit
通过launch返回一个Job实例,标准实现是StandaloneCoroutine。
Job地位在协程等同线程的Thread,提供了几个类似Thread方法:
Job#start == Thread#start
Job#cancel == Thread#interrupt
Job#isCancelled== Thread#isInterrupt
Job#join == Thread#join
b,async
如果需要协程体返回值,通过async启动一个协程,如下
val result = GlobalScope.async {delay(100)return@async "Async返回值"}.await()println(result)
async方法返回一个Deferred对象,Deferred继承Job,通过async启动方式返回,额外新增的await挂起方法,用于获得协程体返回值。
2,协程Join
join方法定义在Job#join,也是一个挂起函数,需要在协程体内调用,
runBlocking {val job1 = GlobalScope.launch {delay(2000)println("job1 done")}job1.join()println("job2 done")}
输出如下:
job1 done
job2 done
3,协程取消
直接通过Job#cancel即可,
val job1 = GlobalScope.launch {delay(2000)println("job1 done")}job1.cancel()println("job2 done")
输出如下:
job2 done
4,协程上下文切换
withContext可指定当前协程体上下文获得结果。
GlobalScope.launch(Dispatchers.Main) {val s = withContext(Dispatchers.IO){delay(200)println("withContext ${Thread.currentThread().name}")return@withContext "result"}println("launch ${Thread.currentThread().name}")println(s)}
withContext是一个挂起函数,即一个挂起点,通过上述写法即可实现异步编程效果,不会阻塞主线程。
5,协程超时
withTimeout可指定协程体超时时间,是一个挂起点,如果超时,下一个恢复状态不会执行,也就不会执行withTimeout后面的逻辑了
GlobalScope.launch(Dispatchers.Default) {println("launch1 ${Thread.currentThread().name}")val s = withTimeout(100){delay(200)println("withTimeout ${Thread.currentThread().name}")return@withTimeout "result"}println("launch2 ${Thread.currentThread().name}")println(s)}
输出如下:
launch1 DefaultDispatcher-worker-1
6,协程完成监听
通过Job#invokeOnCompletion即可添加协程体完成监听
GlobalScope.launch(Dispatchers.Default) {val job1 = GlobalScope.launch {delay(2000)println("job1 done")}.apply {invokeOnCompletion { c ->println("job1 completed")}}println("job2 done")}
输出如下:
job2 done
job1 done
job1 completed
7,协程添加取消监听
通过suspendCancellableCoroutine构造一个cancel监听,该回调会传递一个CancellableContinuation协程,可以调用invokeOnCancellation方法设置一个cancel监听,当该协程cancel后会立即回调监听。
suspend fun requestData():String{return suspendCancellableCoroutine { cancellableContinuation->cancellableContinuation.invokeOnCancellation {println("accept cancel")}Thread.sleep(200)cancellableContinuation.resume("requestData")}
}fun main() {val launch = GlobalScope.launch(Dispatchers.Default) {println(requestData())}Thread.sleep(100)println("invoke cancel")launch.cancel()Thread.sleep(5000)
}
输出如下
invoke cancel
accept cancel
8,协程异常处理器
启动协程时,指定异常拦截器,会拦截根协程体中的异常,如下
fun main() {GlobalScope.launch(Dispatchers.Default + CoroutineExceptionHandler { context, throwable ->println("CoroutineExceptionHandler got $throwable")}) {throw NullPointerException("null")}Thread.sleep(5000)
}
9,协程挂起点手动恢复
如果想手动恢复挂起点,可通过suspendCoroutine或suspendCancellableCoroutine两个函数实现,如下
suspend fun requestData(): String {return suspendCoroutine { cnt ->Thread {println("异步开始 Thread: ${Thread.currentThread().name}")//异步开始Thread.sleep(200)cnt.resumeWith(Result.success("Done"))}.start()}
}fun main() {GlobalScope.launch {println("Thread: ${Thread.currentThread().name}")val s = requestData()println("accept $s Thread: ${Thread.currentThread().name}")}Thread.sleep(5000)
}
当requestData执行完毕后,即可通过suspend*方法提供的Continuation手动触发resume相关方法,即可实现手动恢复挂起点。