细说协程零四、Koltin协程的基本要素

Koltin协程的基本要素基本上都是上一节里面我们提到的那些元素:kotlin标准库、上下文、拦截器、挂起函数等等。这一节我们先来看看挂起函数。

挂起函数

val user = githubApi.getUserSuspend("bennyhuo")
println(user)

suspend fun getUserSuspend(): User {
    return githubApi.getUserSuspend("haoyuegongzi")
}	

interface GitHubApi {
    @GET("users/{login}")
    suspend fun getUserSuspend(@Path("login") login: String): User
}

上面的GitHubApi 接口里面,getUserSuspend()函数后面返回值为User(或者其他实体类)的时候,我们可以使用“suspend ”关键字修饰,但是如果我们在Retrofit+RxJava2环境下返回“Observable”的时候,则不能用“suspend ”关键字修饰,否则会抛异常。

运行结果如下:
在这里插入图片描述

详解:

上面代码中,在主调用流程里面,当执行到“githubApi.getUserSuspend(“bennyhuo”)”的时候就会被挂起,然后将调度权转移给异步线程做网络请求。当网络请求有返回结果(结果无论成功与否)时,再在原来挂起的地方继续执行。

另外,在IDE里面,上面的代码有一个奇怪的地方:
在这里插入图片描述
在这里插入图片描述

也就是上图中,左边会有一个箭头➕波浪线的标记。我们追根溯源会发现,凡是调用加了关键字“suspend”的地方就会出现这样的标记。而关键字“suspend”恰恰是“挂起函数”的标志。也就是说只要我们给某个函数前面添加“suspend”关键字去修饰,那么这个函数就成为挂起函数,然后调用这个函数的地方就会出现箭头➕波浪线的标记。包括上面的“.join()函数”其实内部也是被“suspend”关键字修饰的。
而出现箭头➕波浪线标记的地方我们又称为“挂起点”

另外,调用添加了“suspend”关键字挂起函数(比如本例中interface接口里面的getUserSuspend()函数)的函数也要用“suspend”关键字修饰(比如本例中的“suspend fun getUserSuspend(): User {…}”函数)。如果被调用函数有“suspend”关键字,而调用函数没有用“suspend”关键字,则会报错。而被调用函数没有“suspend”关键字,但调用函数有“suspend”关键字,那么调用函数里面的“suspend”关键字会被置灰,表示这个挂起无效,可以去掉这个“suspend”关键字。

协程挂起、恢复的关键:

协程体内的代码都是通过Coroutination.resumeWith调用;
每调用一次label就+1,每隔挂起点对应一个switch分支;
挂起函数在返回COROUTINE_SUSPENDED时才会挂起。
在这里插入图片描述
COROUTINE_SUSPENDED的值:
在这里插入图片描述

真正的挂起:

1、函数被“suspend”关键字修饰;
2、有线程的切换;
3、挂起函数真的调用了。

总结:

挂起函数就是被“suspend”关键字修饰的函数;
挂起函数只能在其他挂起函数或者协程里面调用,否则无效或者报错;
挂起函数调用时包含了协程的“挂起”语义;表示当前执行流程要让渡调度权了。
挂起函数返回时包含了协程的“恢复”语义;表示当前执行流程重新获得调度权。

协程的创建

首先,协程是一段可执行的程序;

然后,协程的创建需要一个函数:suspend function();

最后,协程的创建还需要一个API:createCoroutine、startCoroutine;

fun <T> (suspend()->T).createCoroutine(completion:Continuation<T>):Continuation<Unit> {
    return ...
}

fun <R, T>(suspend R.()->T).createCoroutine(receiver:R, completion:Continuation<T>):Continuation<Unit>{
    return ...
}

suspend函数本身执行需要一个“completion:Continuation”的实例在恢复的时候调用,也就是上面第一个函数的“completion”;

返回值“Continuation”则是创建出来的协程的载体,receiver,suspend函数会被传给该实例作为协程的实际执行体。

实际开发中,我们并不需要做些,kotlin协程已经帮我们处理好了这些逻辑。

协程上下文Context

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

上下文Context ,我们可以理解为,是获取资源的一个手段,数据的承载体,这根Android里面的Context是类似的,比如我们获取resource的时候通常是:Context .getResource()…,是一个数据集合;

而协程上下文Context包含协程执行过程中需要携带的数据:

索引是:

CoroutineContext.key;

返回的元素是:

CoroutineContext.Element;

源码如下:

public object EmptyCoroutineContext : CoroutineContext, Serializable {
    private const val serialVersionUID: Long = 0
    private fun readResolve(): Any = EmptyCoroutineContext

    public override fun <E : Element> get(key: Key<E>): E? = null
    public override fun <R> fold(initial: R, operation: (R, Element) -> R): R = initial
    public override fun plus(context: CoroutineContext): CoroutineContext = context
    public override fun minusKey(key: Key<*>): CoroutineContext = this
    public override fun hashCode(): Int = 0
    public override fun toString(): String = "EmptyCoroutineContext"
}

可以把这个上下文Context 理解为一个map集合或者list集合,里面存储这协程执行过程中需要携带的信息。

比如,都有get()方法、plus()方法(对应list的add方法)等等。
另外EmptyCoroutineContext的父类CoroutineContext还包含fold(initial: R, operation: (R, Element) -> R): R方法,这个跟集合里面的遍历是一样的。

因此,我们还可以根据自己的需要来自定义上下文Context。

拦截器ContinuationInterceptor

在上下文EmptyCoroutineContext的父类CoroutineContext里面有一个稍微让人困惑,抓狂的对象:ContinuationInterceptor拦截器。他是协程上下文的一部分,作用比较强大:

1、它看对协程上下文所在的协程Continuation进行来接、篡改。

public interface ContinuationInterceptor : CoroutineContext.Element {
    companion object Key : CoroutineContext.Key<ContinuationInterceptor>
    public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
	......
}

从上面源码我们可以知道interceptContinuation()方法,我们在外面传一个Continuation对象进去,然后又返回给我门一个Continuation)对象回来。

因此我们可以根绝需要,自定义ContinuationInterceptor拦截器,实现ContinuationInterceptor接口,重写public fun interceptContinuation(continuation: Continuation): Continuation方法;

class CustomerContinuationInterceptor: ContinuationInterceptor {
    override val key: CoroutineContext.Key<*>
        get() = TODO("Not yet implemented")

    @InternalCoroutinesApi
    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
        continuation.resumeWithException(Exception("拦截更改后新的interceptContinuation拦截器"))
        ......
        return continuation
    }
}

这个我们网络请求OkHttp里面addInterceptor的时候,在这里面更改Interceptor然后在返回是一样的原理。

拦截器的作用:
在这里插入图片描述

关于协程这部分,现在还没有掌握透彻,目前只能写这么多,以后边用边熟悉边补充完善本博客。


版权声明:本文为haoyuegongzi原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。