接《Android开发者快速上手Kotlin(六) 之协程上下文和拦截器》文章继续。
经前面两篇文章介绍了协程的概念和语言级别的语法后,相信你已经对协程有了一个大概的认识了。今天这篇文章主要是以应用层面来介绍Kotlin官方协程框架——kotlinx.coroutines。如果我们平时日常开发使用的是我们之前介绍的语言级别语法那是非常难用的,甚至你可能还会觉得不如使用以前Java中使用线程+接口回调的方式还比它好,而官方提供的协程框架kotlinx.coroutines它是基于语法实现的特性进行封装,这样我们在使用起来就会非常方便,我们的代码就会真正地发挥协程的优势。
Kotlin协程官方框架主页是:https://github.com/Kotlin/kotlinx.coroutines,我们要在项目代码中对它进行引用,只需要在Gradle中进行以下配置即可:
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.6'
}
示例
fun main() {
GlobalScope.launch { // 在应用程序的生命周期内启动一个新的协程并继续
delay(1000L) // 非阻塞的等待1秒钟
println("【${Thread.currentThread().name}】World!")
}
println("【${Thread.currentThread().name}】Hello,")
Thread.sleep(2000L) // 阻塞的等待2秒钟,因为协程的生命周期受应用程序生命周期限制,所以这里保证协程内部逻辑执行完
}
运行结果
【main】Hello,
【DefaultDispatcher-worker-1】World!
解说
示例1
fun main() {
GlobalScope.launch { // 在应用程序的生命周期内启动一个新的协程并继续
delay(1000L) // 非阻塞的等待1秒钟
println("【${Thread.currentThread().name}】World!")
}
println("【${Thread.currentThread().name}】Hello,")
// Thread.sleep(2000L) // 阻塞的等待2秒钟,因为协程的生命周期受应用程序生命周期限制,所以这里保证协程内部逻辑执行完
runBlocking { // 主线程中启动一个阻塞的协程
delay(2000L) // 使用非阻塞的等待2秒钟,这里仍然是阻塞
}
}
示例2
fun main() = runBlocking {
GlobalScope.launch {
delay(1000L)
println("【${Thread.currentThread().name}】World!")
}
println("【${Thread.currentThread().name}】Hello,")
delay(2000L)
}
两个示例运行结果一样
【main】Hello,
【DefaultDispatcher-worker-1】World!
解说
示例
fun main() = runBlocking {
val job = GlobalScope.launch {
delay(1000L)
println("【${Thread.currentThread().name}】World!")
}
println("【${Thread.currentThread().name}】Hello,")
job.join() // 等待直到子协程执行结束
}
运行结果
【main】Hello,
【DefaultDispatcher-worker-1】World!
解说
示例1:正常取消
fun main() = runBlocking {
val job = GlobalScope.launch {
repeat(1000) { i -> // 启动1000个协程
println("【${Thread.currentThread().name}】协程工作中: $i ...")
delay(500L)
}
}
delay(1100L)
println("【${Thread.currentThread().name}】准备取消协程")
job.cancel() // 取消该作业
job.join() // 等待作业执行结束,两个函数结合可用:job.cancelAndJoin()
println("【${Thread.currentThread().name}】已经取消协程")
}
运行结果1
【DefaultDispatcher-worker-1】协程工作中: 0 ...
【DefaultDispatcher-worker-1】协程工作中: 1 ...
【DefaultDispatcher-worker-1】协程工作中: 2 ...
【main】准备取消协程
【main】已经取消协程
解说1
示例2:计算任务不能取消
fun main() = runBlocking {
// val job = GlobalScope.launch {
// repeat(1000) { i -> // 启动1000个协程
// println("【${Thread.currentThread().name}】协程工作中: $i ...")
// delay(500L)
// }
// }
val startTime = System.currentTimeMillis()
val job = GlobalScope.launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 10) { // 一个执行计算的循环,只是为了占用 CPU
if (System.currentTimeMillis() >= nextPrintTime) {
println("协程工作中: ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1100L)
println("【${Thread.currentThread().name}】准备取消协程")
job.cancel() // 取消该作业
job.join() // 等待作业执行结束,两个函数结合可用:job.cancelAndJoin()
println("【${Thread.currentThread().name}】已经取消协程")
}
运行结果2
【DefaultDispatcher-worker-1】协程工作中: 0 ...
【DefaultDispatcher-worker-1】协程工作中: 1 ...
【DefaultDispatcher-worker-1】协程工作中: 2 ...
【main】准备取消协程
【DefaultDispatcher-worker-1】协程工作中: 3 ...
【DefaultDispatcher-worker-1】协程工作中: 4 ...
【DefaultDispatcher-worker-1】协程工作中: 5 ...
【DefaultDispatcher-worker-1】协程工作中: 6 ...
【DefaultDispatcher-worker-1】协程工作中: 7 ...
【DefaultDispatcher-worker-1】协程工作中: 8 ...
【DefaultDispatcher-worker-1】协程工作中: 9 ...
【main】已经取消协程
解说2
示例3:取消计算任务
fun main() = runBlocking {
val startTime = System.currentTimeMillis()
val job = GlobalScope.launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 10 && isActive) { // 一个执行计算的循环,只是为了占用 CPU
if (System.currentTimeMillis() >= nextPrintTime) {
println("【${Thread.currentThread().name}】协程工作中: ${i++} ...")
nextPrintTime += 500L
}
}
}
delay(1100L)
println("【${Thread.currentThread().name}】准备取消协程")
job.cancel() // 取消该作业
job.join() // 等待作业执行结束,两个函数结合可用:job.cancelAndJoin()
println("【${Thread.currentThread().name}】已经取消协程")
}
运行结果3
【DefaultDispatcher-worker-1】协程工作中: 0 ...
【DefaultDispatcher-worker-1】协程工作中: 1 ...
【DefaultDispatcher-worker-1】协程工作中: 2 ...
【main】准备取消协程
【main】已经取消协程
解说3
示例
fun main() = runBlocking {
val job = GlobalScope.launch {
try {
repeat(1000) { i -> // 启动1000个协程
println("【${Thread.currentThread().name}】协程工作中: $i ...")
delay(500L)
}
} finally {
println("【${Thread.currentThread().name}】协程已被取消,正在释放资源")
delay(1000L)
println("【${Thread.currentThread().name}】协程已被取消,已经释放完资源")
}
}
delay(1100L)
println("【${Thread.currentThread().name}】准备取消协程")
job.cancel() // 取消该作业
job.join() // 等待作业执行结束,两个函数结合可用:job.cancelAndJoin()
println("【${Thread.currentThread().name}】已经取消协程")
}
运行结果
【DefaultDispatcher-worker-1】协程工作中: 0 ...
【DefaultDispatcher-worker-1】协程工作中: 1 ...
【DefaultDispatcher-worker-1】协程工作中: 2 ...
【main】准备取消协程
【DefaultDispatcher-worker-1】协程已被取消,正在释放资源
【main】已经取消协程
解说
示例
fun main() = runBlocking {
val job = GlobalScope.launch {
try {
repeat(1000) { i -> // 启动1000个协程
println("【${Thread.currentThread().name}】协程工作中: $i ...")
delay(500L)
}
} finally {
withContext(NonCancellable) {
println("【${Thread.currentThread().name}】协程已被取消,正在释放资源")
delay(1000L)
println("【${Thread.currentThread().name}】协程已被取消,已经释放完资源")
}
}
}
delay(1100L)
println("【${Thread.currentThread().name}】准备取消协程")
job.cancel() // 取消该作业
job.join() // 等待作业执行结束,两个函数结合可用:job.cancelAndJoin()
println("【${Thread.currentThread().name}】已经取消协程")
}
运行结果
【DefaultDispatcher-worker-1】协程工作中: 0 ...
【DefaultDispatcher-worker-1】协程工作中: 1 ...
【DefaultDispatcher-worker-1】协程工作中: 2 ...
【main】准备取消协程
【DefaultDispatcher-worker-1】协程已被取消,正在释放资源
【DefaultDispatcher-worker-1】协程已被取消,已经释放完资源
【main】已经取消协程
解说
示例1:withTimeout
fun main() = runBlocking {
try {
withTimeout(1100L) {
repeat(1000) { i ->
println("【${Thread.currentThread().name}】协程工作中: $i ...")
delay(500L)
}
}
} catch (e: TimeoutCancellationException) {
println("【${Thread.currentThread().name}】协程超时了,${e.message}")
}
}
运行结果1
【main】协程工作中: 0 ...
【main】协程工作中: 1 ...
【main】协程工作中: 2 ...
【main】协程超时了,Timed out waiting for 1100 ms
解说1
1. withTimeout 抛出了 TimeoutCancellationException,它是 CancellationException 的子类。
示例2:withTimeoutOrNull
fun main() = runBlocking {
val result = withTimeoutOrNull(1100L) {
repeat(1000) { i ->
println("【${Thread.currentThread().name}】协程工作中: $i ...")
delay(500L)
}
"Done" // 正常结束返回的结果
}
println("【${Thread.currentThread().name}】协程运行的结果是: $result")
}
运行结果2
【main】协程工作中: 0 ...
【main】协程工作中: 1 ...
【main】协程工作中: 2 ...
【main】协程运行的结果是: null
解说2
假设我们存在两个耗时的进行计算的挂起函数。现在我们需要对它们进行计算的结果进行总汇处理。
示例1
fun main() = runBlocking {
val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
println("【${Thread.currentThread().name}】计算结果: ${one + two}")
}
println("【${Thread.currentThread().name}】共耗时: $time ms")
}
suspend fun doSomethingUsefulOne(): Int {
delay(1000L)
println("【${Thread.currentThread().name}】doSomethingUsefulOne 计算中")
return 2
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L)
println("【${Thread.currentThread().name}】doSomethingUsefulTwo 计算中")
return 3
}
运行结果1
【main】doSomethingUsefulOne 计算中
【main】doSomethingUsefulTwo 计算中
【main】计算结果: 5
【main】共耗时: 2012 ms
解说1
1.上面示例代码中,doSomethingUsefulOne函数执行完毕后才到doSomethingUsefulTwo函数,它们是顺度执行的,所以它们的总耗时是两个函数执行时间之和。
示例2
fun main() = runBlocking {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("【${Thread.currentThread().name}】计算结果: ${one.await() + two.await()}")
}
println("【${Thread.currentThread().name}】共耗时: $time ms")
}
suspend fun doSomethingUsefulOne(): Int {
delay(1000L)
println("【${Thread.currentThread().name}】doSomethingUsefulOne 计算中")
return 2
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L)
println("【${Thread.currentThread().name}】doSomethingUsefulTwo 计算中")
return 3
}
运行结果2
【main】doSomethingUsefulOne 计算中
【main】doSomethingUsefulTwo 计算中
【main】计算结果: 5
【main】共耗时: 1020 ms
解说2
我们在上一篇文章《Android开发者快速上手Kotlin(六) 之 协程上下文和拦截器》针对Kotlin语言层面的协程学习中有介绍过,协程上下文就是一个在执行过程中携带数据的载体对象,它其实就是一个用Key作索引,Element作元素的集合,一般用于数据从协程外层传递协程内部。我们在使用协程框架同样也是可以进行上下文的自定义的。
示例
class ParameterContext(val isSuccess: Boolean) : AbstractCoroutineContextElement(Key) {
companion object Key : CoroutineContext.Key
}
fun main() = runBlocking {
launch(CoroutineName("HelloWorld") + ParameterContext(true)) {
println("Job的上下文对象是:${coroutineContext[Job]}")
println("传递的参数值是:${coroutineContext[ParameterContext]?.isSuccess}")
println("协程的名字是:${coroutineContext[CoroutineName]?.name}")
}
println("Main函数执行完毕")
}
运行结果
Main函数执行完毕
Job的上下文对象是:StandaloneCoroutine{Active}@369f73a2
传递的参数值是:true
协程的名字是:HelloWorld
解说
我们在协程Hello World中提到,GlobalScope.launch用于启动了一个运行在子线程的顶层协程,这个顶层的意思就是它的生命周期随应用程序的生命周期。虽然协程是轻量级的,但是它的运行仍然会消耗一些内存资源。所以我们完全可以在指定作用域内去启动协程。
示例
fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("【${Thread.currentThread().name}】在 runBlocking 里的任务")
}
coroutineScope { // 创建一个协程作用域
launch {
delay(500L)
println("【${Thread.currentThread().name}】在 coroutineScope 里嵌套 launch 中的任务")
}
delay(100L)
println("【${Thread.currentThread().name}】在 coroutineScope 里的任务")
}
println("【${Thread.currentThread().name}】在 Main 里的任务")
}
运行结果
【main】在 coroutineScope 里的任务
【main】在 runBlocking 里的任务
【main】在 coroutineScope 里嵌套 launch 中的任务
【main】在 Main 里的任务
解说
示例1
fun main() = runBlocking {
val job = launch {
GlobalScope.launch {
println("嵌套的顶层协程开始工作")
delay(1000)
println("嵌套的顶层协程结束工作")
}
launch {
println("嵌套的子协程开始工作")
delay(1000)
println("嵌套的子协程结束工作")
}
}
delay(500)
job.cancel()
delay(2000)
println("Main函数执行完毕")
}
运行结果1
嵌套的顶层协程开始工作
嵌套的子协程开始工作
嵌套的顶层协程结束工作
Main函数执行完毕
解说1
示例2
fun main() = runBlocking {
val job = launch {
repeat(3) { i -> // 启动3个子协程
launch {
delay((i + 1) * 200L)
println("子协程 $i 执行完毕")
}
}
println("父协程已经执行完毕")
}
job.join()
println("Main函数执行完毕")
}
运行结果2
父协程已经执行完毕
子协程 0 执行完毕
子协程 1 执行完毕
子协程 2 执行完毕
Main函数执行完毕
解说2
我们在上一篇文章《Android开发者快速上手Kotlin(六) 之 协程上下文和拦截器》针对Kotlin语言层面的协程学习中还介绍过拦截器,拦截器是对协程上下文所在的协程进行拦截,从而达到线程切换效果。然而所谓协程调度器本质就是上下文+拦截器的封装,我们在使用框架来启动一个协程时,也是可以通过传入指定的协程上下文来将协程限制在一个特定的线程执行。
示例
fun main() = runBlocking {
launch { // 不传参数就是 EmptyCoroutineContext
println("【${Thread.currentThread().name}】EmptyCoroutineContext 运行在父协程的所在线程的协程")
}
launch(Dispatchers.Unconfined) {
println("【${Thread.currentThread().name}】Unconfined 运行在当前线程的协程1")
delay(1000L)
println("【${Thread.currentThread().name}】Unconfined 运行在当前线程的协程2")
}
// launch(Dispatchers.Main) { // 该代码需要运行在Android工程中,并在Gradle中依赖org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version
// println("【${Thread.currentThread().name}】运行在UI线程的协程")
// }
launch(Dispatchers.Default) { // GlobalScope.launch 就是使用了 Dispatchers.Default
println("【${Thread.currentThread().name}】Default 运行在线程池的协程,用于处理CPU密集型工作")
}
launch(Dispatchers.IO) {
println("【${Thread.currentThread().name}】IO 运行在线程池的协程,用于处理IO密集型工作")
}
launch(newSingleThreadContext("Myhread")) {
println("【${Thread.currentThread().name}】运行在指定线程的协程")
}
println("【${Thread.currentThread().name}】Main函数执行完毕")
}
运行结果
【main】Unconfined 运行在当前线程的协程1
【DefaultDispatcher-worker-1】Default 运行在线程池的协程,用于处理CPU密集型工作
【DefaultDispatcher-worker-2】IO 运行在线程池的协程,用于处理IO密集型工作
【main】Main函数执行完毕
【main】EmptyCoroutineContext 运行在父协程的所在线程的协程
【Myhread】运行在指定线程的协程
【kotlinx.coroutines.DefaultExecutor】Unconfined 运行在当前线程的协程2
解说
协程的启动模式一共有4种, 我们从其源码可见它是一个枚举类,它们区别在其注释中已经说明得很清楚,这里我将其翻译成中文并用一句话进行了概括:
public enum class CoroutineStart {
// 立即开始执行协程体,随时可以取消
DEFAULT,
// 只有在需要(start/join/await)时开始执行
LAZY,
// 立即开始执行协程体,且在第一个挂起点前不能被取消
@ExperimentalCoroutinesApi
ATOMIC,
// 立即在当前线程执行协程体,直到遇到第一个挂起点
@ExperimentalCoroutinesApi
UNDISPATCHED;
}
示例1
fun main() = runBlocking {
launch(start = CoroutineStart.DEFAULT) {
println("【${Thread.currentThread().name}】协程开始")
delay(100)
println("【${Thread.currentThread().name}】协程结束")
}
println("【${Thread.currentThread().name}】Main函数执行完毕")
}
运行结果1
【main】Main函数执行完毕
【main】协程开始
【main】协程结束
示例2
fun main() = runBlocking {
launch(start = CoroutineStart.UNDISPATCHED) {
println("【${Thread.currentThread().name}】协程开始")
delay(100)
println("【${Thread.currentThread().name}】协程结束")
}
println("【${Thread.currentThread().name}】Main函数执行完毕")
}
运行结果2
【main】协程开始
【main】Main函数执行完毕
【main】协程结束
解说
未完,请关注后面文章更新…