Android Kotlin协程(挂起函数+协程作用域)
# 协程关键字
+ CoroutineScope 协程的作用域
+ CoroutineDispatcher 协程的调度器
+CoroutineContext 协程上下文
# 挂起函数
概念:使用关键词suspend修饰。
**delay**
将当前协程挂起指定时间,但不会阻塞线程,必须在协程的作用域或者其他挂起函数中执行。
**withContext**
必须在协程的作用域中调用,必须指定协程的上下文,函数的最后一行是返回值。
```
GlobalScope.launch {
withContext(Dispatchers.Default) {
delay(2000)
"return string"
}
}
//运行结果:
return string
```
**yield**
挂起当前的协程,将当前协程分发到CoroutineDispatcher队列(当前协程的父协程),等待其他协程执行完/挂起,再执行先前的协程。
```
fun main(args: Array<String>) = runBlocking {
launch {
println("1")
yield()
println("2")
}
launch {
println("3")
yield()
println("4")
}
delay(5000)
}
//运行结果:
1
3
2
4
```
# 协程上下文
**NonCancellable**
始终处于活动状态的不可取消的任务,配合withContext一起使用。
使用场景:已经被cancel的协程,可以使用withContext(NonCancellable)继续执行协程中的任务, 用于重置对象及清理工作。
```
fun main(args: Array<String>) = runBlocking {
val job1 = GlobalScope.launch {
try {
delay(1000)
println("job 1")
} catch (e: Exception) {
e.printStackTrace()
} finally {
withContext(NonCancellable) {
delay(1000)
println("job: I'm running finally")
}
}
}
job1.cancel()
delay(5000)
}
//运行结果:
job: I'm running finally
```
可以看到正在运行的协程被cancel后,会报异常JobCancellationException,可看到println("job 1")没有执行到 finally中的代码有正常执行,有打印日志(job: I'm running finally),可使用withContext(NonCancellable)创建协程做一些清理工作。
# 协程作用域
**父子协程**
A协程是使用B协程的CoroutineContext创建的,那么B就是A的父协程。协程默认是串行执行的,父协程会等待子协程执行完毕。
```
val job = GlobalScope.launch {
val job1 = launch {
println("job 1 start")
delay(1000)
println("job 1 end")
}
val job2 = GlobalScope.launch {
println("job 2 start")
delay(1000)
println("job 2 end")
}
}
job.cancel()
//运行结果:
job 1 start
job 2 start
job 2 end
```
结论:父协程取消后,子协程的任务也会被取消。
以下三种方式创建的都是子协程:
+ withContext(Dispatchers.Default) { }
+ launch { }
+ launch(coroutineContext) { }
以下两种方式创建的则不是子协程
+ CoroutineScope(Dispatchers.Default)
+ GlobalScope.launch { }
**CoroutineContext +**
CoroutineContext使用+,使一个协程具有多个CoroutineContext特性。
```
val job = GlobalScope.launch {
val job1 = GlobalScope.launch(Dispatchers.Default + coroutineContext) {
println("job 1 start")
delay(500)
println("job 1 end")
}
}
job.cancel()
//运行结果:
job 1 start
```
说明:使用 + 后,job1变成job的子协程,job被cancel后,job1也被停止了。去掉 + coroutineContext试一下。
```
val job = GlobalScope.launch {
val job1 = GlobalScope.launch(Dispatchers.Default) {
println("job 1 start")
delay(500)
println("job 1 end")
}
}
job.cancel()
//运行结果:
job 1 start
job 1 end
```
结论:两个协程没有父子关系,互不影响,job取消后job1还可以继续执行。
**CoroutineContext+Job**
使用CoroutineContext+Job后,可以使用Job统一管理协程:
```
val job = Job()
val job1 = GlobalScope.launch(Dispatchers.Default + job) {
println("job 1 start")
delay(500)
println("job 1 end")
}
job.cancel()
//运行结果:
job 1 start
```
结论:job取消后,job1也被取消了。在 Android 开发中,Activity/Fragment 可以通过创建一个 Job 对象,并让该 Job 管理其他的协程。等到退出 Activity/Fragment 时,调用 job.cancel() 来取消协程的任务。
**GlobalScope**
主要分享GlobalScope的易错点,以下是错误的写法:
```
fun main(args: Array<String>) {
try {
GlobalScope.launch {
println(doSomething())
}
} catch (e:Exception) {
}
//一定要加这行代码,不然main都执行结束了,代码还没执行到doSomething()方法
Thread.sleep(100)
}
suspend fun doSomething(): String = withContext(Dispatchers.Default) {
throw RuntimeException("this is an exception")
"doSomething..."
}
```
此代码会抛出异常,try catch并没有捕获到异常,应该写在GlobalScope.launch {}里面。正确的写法:
```
fun main(args: Array<String>) {
GlobalScope.launch {
try {
println(doSomething())
} catch (e:Exception) {
}
}
Thread.sleep(100)
}
suspend fun doSomething(): String = withContext(Dispatchers.Default) {
throw RuntimeException("this is an exception")
"doSomething..."
}
```
我的笔记