写点什么

Coroutine 基本原理,Android 开发大佬的百度美团快手等大厂 Offer 收割之旅

用户头像
Android架构
关注
发布于: 刚刚

.enqueue(obj : Callback<Bitmap> {...override fun onResponse(call:Call<Bitmap>,response: Resoinse<Bitmap>) {val card = resonse.body()getSofList(canId).enqueue(obj : Callback<Bitmap> {...override fun onResponse(call:Call<Bitmap>response: Resoinse<Bitmap>) {val sofList = response.body()show(suspendMerge(cardDetail, sofList)) // 合并两个信息 & UI 显示})}})


??这个代码就是用回调的方式实现的,它有什么问题呢?


两个网络请求原本是可以并行的网络请求,只是他们需要一起展示出来。如果用回调的方法,就会被做成一个串行的,然后网络等待多了一倍。


  1. 那我们再用 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 挂起后,就出现了两条线:


  1. A 线程:该执行什么执行什么,比如刷新界面,或者没有其他事情可做就被回收再利用

  2. 挂起函数:切换到指定线程,并从被挂起的那行代码开始,继续向下执行代码,执行完以后,再切回到 A 线程


举个例子吧:


安卓开发中,主线程 Main 正在执行任务,执行到一个网络请求 N(挂起函数)时,N 脱离主线程,到指定的 IO 线程做网络请求。 然后线程 Main 将继续渲染界面(比如转圈圈 loading),等 N 在 IO 线程执行结束后,切回 Main 线程,拿着刚请求完的值,继续做后序操作(比如 loading 结束,显示请求回来的数据结果)

挂起函数问题

  1. 挂起的对象是协程

  2. 挂起函数只能在另一个挂起函数或者协程中被调用:why?

  3. 因为,切走再恢复(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, 为什么这里变


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


成 Any 了?? 因为编译以后,除了要返回原本要返回的值,还要返回一个标记——COROUTINE_SUSPENDED,所以找不到一个对应的类型来和返回结果匹配,因此变成类一个 Any?类型。 ?表示可以为空。


讲原理以前我们先看看怎么写回调: 正常打印一个 6


private fun printSix() {println("6")}


Kotlin 中用回调实现:向函数中传入一个 Printer,并且把具体的实现方式暴露给调用这个方法的人,


interface Printer{fun print()}


private fun printSix(printer: Printer) {printer.print()

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
Coroutine 基本原理,Android开发大佬的百度美团快手等大厂Offer收割之旅