+ 我要发布
我发布的 我的标签 发现
浏览器扩展
斑点象@Edge

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..." } ```
我的笔记