一文读懂kotlin协程常用知识点,安卓开发快速学习

创建协程

创建协程有三种方式:launch、async、runBlocking

launch

launch 方法签名如下:

public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
//省略
return coroutine
}

launch 是 CoroutineScope 的扩展方法,需要 3 个参数。第一个参数,看字面意思是协程上下文,后边会重点讲到。第二个参数是协程启动模式,默认情况下,协程是创建后立即执行的。第三个参数,官方文档说这个 block 就是协程代码块,所以是必传的。返回的是一个 Job,这个 Job 可以理解为一个后台工作,在 block 代码块执行完成后会结束,也可以通过 Job 的 cancel 方法取消它。

async

async 方法签名如下:

public fun CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred {
//省略
return coroutine
}

同样也是 CoroutineScope 的扩展方法,参数跟 launch 是一模一样的,只是返回参数变成了 Deferred,这个 Deferred 继承于 Job,相当于一个带返回结果的 Job,返回结果可以通过调用它的 await 方法获取。

runBlocking

runBlocking 会阻塞调用他的线程,直到代码块执行完毕。

Log.i(“zx”, “当前线程1-” + Thread.currentThread().name)
runBlocking(Dispatchers.IO) {
delay(2000)
Log.i(“zx”, “休眠2000毫秒后,当前线程” + Thread.currentThread().name)
}
Log.i(“zx”, “当前线程2-” + Thread.currentThread().name)

输出内容

当前线程1-main
休眠2000毫秒后,当前线程DefaultDispatcher-worker-1
当前线程2-main

可以看到,即使协程指定了运行在 IO 线程,依旧会阻塞主线程。runBlocking 主要用来写测试代码,平常不要随意用,所以不再过多介绍。

CoroutineScope 协程作用域

launch 和 async 都是 CoroutineScope 的扩展函数,CoroutineScope 又是什么呢,字面意思翻译过来是协程作用域,协程作用域类似于变量作用域,定义了协程代码的作用范围。作用域取消时,作用域中的协程都会被取消。 比如如下代码:

MainScope().launch {
var i = 0

launch(Dispatchers.IO) {
while (true) {
Log.i(“zx”, “子协程正在运行着$i”)
delay(1000)
}
}

while (true) {
i++
Log.i(“zx”, “父协程正在运行着$i”)

if (i>4) {
cancel()
}
delay(1000)
}
}

输出:

父协程正在运行着1
子协程正在运行着1
父协程正在运行着2
子协程正在运行着2
父协程正在运行着3
子协程正在运行着3
父协程正在运行着4
子协程正在运行着4
子协程正在运行着4
父协程正在运行着5

5 秒后,父协程调用 cancel()结束了,子协程也就结束了,并没有继续打印出值。

可以通过 CoroutineScope()来创建协程作用域,这并不是一个构造函数,CoroutineScope 是一个接口,所以没有构造函数,只是函数名与接口名同名而已,源码如下:

@Suppress(“FunctionName”)
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
ContextScope(if (context[Job] != null) context else context + Job())

源码可见,创建 CoroutineScope 时需要传入 CoroutineContext,这个 CoroutineContext 也是 CoroutineScope 接口中唯一的成员变量。CoroutineScope.kt 这个文件中使用 CoroutineScope()创建了两个 Scope,一个是 MainScope,一个是 GlobalScope。源码如下:

public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

public object GlobalScope : CoroutineScope {
override val coroutineContext: CoroutineContext
get() = EmptyCoroutineContext
}

MainScope 是一个方法,返回了一个运行在主线程的作用域,需要手动取消。GlobalScope 是一个全局作用域,整个应用程序生命周期他都在运行,不能提前取消,所以一般不会使用这个作用域。Android 中,ktx 库提供了一些常用的作用域供我们使用,如 lifecycleScope 和 viewModelScope。在 LifecycleOwner 的所有实现类中,如 Activity 和 Fragment 中都可以直接使用 lifecycleScope,lifecycleScope 会跟随 Activity 或 Fragment 的生命周期,在 Activity 或 Fragment 销毁时,自动取消协程作用域中的所有协程,不用手动管理,不存在内存泄露风险。类似的 viewModelScope 也会随着 viewModel 的销毁而取消。

目前已经有好几个地方出现了 CoroutineContext:启动协程时 launch 或者 async 方法需要 CoroutineContext,创建协程作用域时需要 CoroutineContext,协程作用域中有且只有一个成员变量也是 CoroutineContext,如下源码所示:

public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}

如此看来,CoroutineContext 必定很重要。

CoroutineContext 协程上下文

CoroutineContext 保存了协程的上下文,是一些元素的集合(实际并不是用集合 Set 去存储),集合中每一个元素都有一个唯一的 key。通俗来讲,CoroutineContext 保存了协程所依赖的各种设置,比如调度器、名称、异常处理器等等。

CoroutineContext 源码如下:

public interface CoroutineContext {

public operator fun get(key: Key): E?<

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值