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然后在返回是一样的原理。
拦截器的作用:
关于协程这部分,现在还没有掌握透彻,目前只能写这么多,以后边用边熟悉边补充完善本博客。