Coroutine 基本原理,吃透这份阿里 P8 纯手打 Android 面经
那我们再用 Kotlin 的协程实现一下呢?
//协程 suspend fun showCard(){val cardDetail = getCard(canId) // 获取卡详情 val sofList = getSofList(canId) // 获取银行卡信息 val cardInfo = suspendMerge(cardDetail.await, sofList.await) // 合并两个信息 show(cardInfo) // UI 显示}
哇塞!是不是炒鸡简洁明了?? 对了,这就是为什么要用 kotlin 协程。这个里面干了啥我们不用管,反正此刻只需要知道,协程就让原本用 callback 写起来很复杂的东西变的整洁干净,清晰明了就好了。
挂起函数(suspend 函数)
刚上面就出现了一个关键字 suspend
, 那这个加了 suspend 这个词的函数又是啥呢? -> 挂起函数
那什么是挂起函数?
就是在普通的函数前面加一个关键字 suspend
一般函数写法:
fun getCard(
) {......}
挂起函数写法:
suspend fun getCard() {......}
suspend 关键字作用:
提醒 这个函数的调用者: 我是一个耗时的函数, 请在协程中调用我。
挂起函数作用
说了半天,为什么要用挂起函数?
挂起的意思是:暂时从当前线程脱离,一会儿再切/恢复回来(resume)
当一个函数从当前线程 A 挂起后,就出现了两条线:
A 线程:该执行什么执行什么,比如刷新界面,或者没有其他事情可做就被回收再利用
挂起函数:切换到指定线程,并从被挂起的那行代码开始,继续向下执行代码,执行完以后,再切回到 A 线程
举个例子吧:
安卓开发中,主线程 Main 正在执行任务,执行到一个网络请求 N(挂起函数)时,N 脱离主线程,到指定的 IO 线程做网络请求。 然后线程 Main 将继续渲染界面(比如转圈圈 loading),等 N 在 IO 线程执行结束后,切回 Main 线程,拿着刚请求完的值,继续做后序操作(比如 loading 结束,显示请求回来的数据结果)
挂起函数问题
挂起的对象是协程
挂起函数只能在另一个挂起函数或者协程中被调用:why?
因为,切走再恢复(resume)回来是协程的东西,所以只能在协程中调用
原理
好了,现在知道什么是suspend
函数了,但是具体是实现或者它原理是什么样的呢?我们来慢慢捋一捋。
挂起函数就是在普通的函数前面加一个关键字suspend
,然而 Java 平台并没有 suspend
关键字,也没有 suspending 机制,那 kotlin 又是怎么实现了可以用看起来阻塞的方式实现不阻塞的代码的呢?
CPS 变换
Kotlin 编译器会对 suspending 方法做特殊处理,对代码进行转换,从而实现 suspending 机制。
那 Kotlin 编译器做了哪些处理?简单说,主要做了下面这三项处理:
处理一:增加 Continuation 类型入参,返回值变为 Any?
处理二:生成 Continuation 类型的匿名内部类
处理三:对 suspending 方法的调用变为 switch 形式的状态机
[以上引用于作者: 编走编想](
)
那我们来看看最根本的变化:
//挂起函数的函数签名 suspend fun <T> CompletableFuture<T>.await():
变化后
fun <T> CompletableFuture<T>.await(continuation: Continuation<T>): Any?
可以看到,编译器做了什么? 传入了一个Continuation
并且返回类型变为了Any?
那
Continuation
是什么?
一个接口:Kotlin 的接口可以既包含抽象方法的声明也包含实现。与抽象类不同的是,接口无法保存状态。它可以有属性但必须声明为抽象或提供访问器实现。 ([Kotlin interface 官方解释](
))
interface Continuation<in T> {val context: CoroutineContextfun resumeWith(result: Result<T>)
Any?
是什么? 表示返回的类型可以是任意类型。
那这里就有一个问题,比如一个函数,原本返回的是 String, 为什么这里变成 Any 了?? 因为编译以后,除了要返回原本要返回的值,还要返回一个标记——COROUTINE_SUSPENDED,所以找不到一个对应的类型来和返回结果匹配,因此变成类一个 Any?类型。 ?
表示可以为空。
讲原理以前我们先看看怎么写回调: 正常打印一个 6
private fun printSix() {println("6")}
Kotlin 中用回调实现:向函数中传入一个 Printer,并且把具体的实现方式暴露给调用这个方法的人,
interface Printer{fun print()}
private fun printSix(printer: Printer) {printer.print()}
fun callPrintSix() {printSix(object : Printer {override fun print() {//具体的实现暴露给调用者 println("6")Log.i("tag", "6")...}})}
那么知道 Kotlin 怎么写回调以后,再回过头看看我们 suspend 函数的变化:
suspend fun getCard(cardNum : String) {val card = api.getCard(cardNum)
评论