sum(a, b) { sum ->

// 加完执行乘法

double(sum) { double ->

// 乘完就加2

add2(double) { result ->

// 最后打印

println(result)

}

}

}

}

这就是CPS的代码风格:通过接口回调的方式来实现的

假设: 我们上述的几个方法: sum()/double()/add2()都是挂起函数,那么最终也会编译为CPS风格的回调函数方式,也就是:原来看起来同步的代码,经过编译器的"修改",变成了异步的方法,也就是:CPS化了,这就是kotlin协程的顶层实现逻辑。

现在,让我们来验证一下,我们定义一个suspend函数,反编译看下是否真的CPS化了。

// 定义挂起函数

suspend fun test(id: String): String = “hello”

反编译结果如下:

// 参数添加了一个Continuation参数

public final Object test(@NotNull String id, @NotNull Continuation $completion) {

return “hello”;

}

可以看到,多了个Continuation参数,这是个接口,是在本次函数执行完毕后执行的回调,内容如下:

public interface Continuation {

// 保存上下文(比如变量状态)

public val context: CoroutineContext

// 方法执行结束的回调,参数是个范型,用来传递方法执行的结果

public fun resumeWith(result: Result)

}

好,现在我们知道了suspend函数 是通过添加Continuation来实现的,我们来看个具体的业务:

// 根据id获取token

suspend fun getToken(id: String): String = “token”

// 根据token获取info

suspend fun getInfo(token: String): String = “info”

// 测试

suspend fun test() {

// 先获取token,这是耗时请求

val token = getToken(“123”)

// 再根据token获取info,这也是个耗时请求

val info = getInfo(token)

// 打印

println(info)

}

上述的业务代码很简单,但是前两步都是耗时操作,线程会卡在那里wait吗?显然不会,既然是suspend函数,那么就可以CPS化,等价的CPS代码如下:

// 跟上述相同,传递了Continuation回调

fun getToken(id: String, callback: Continuation): String = “token”

// 跟上述相同,传递了Continuation回调

fun getInfo(token: String, callback: Continuation): String = “info”

// 测试(只写了主线代码)

fun test() {

// 先获取token,传入回调

getToken(“123”, object : Continuation {

override fun resumeWith(result: Result) {

// 用token获取info,传入回调

val token = result.getOrNull()

getInfo(token!!, object : Continuation {

override fun resumeWith(result: Result) {

// 打印结果

val info = result.getOrNull()

println(info)

}

})

}

})

}

上述就是无suspend的CPS风格代码,通过传入接口回调来实现协程的同步代码风格。

接下来我们来反编译suspend风格代码,看下它里面是怎么调度的。

协程的底层实现-状态机

我们先来简单修改下suspend test函数:

// 没变化

suspend fun getToken(id: String): String = “token”

// 没变化

suspend fun getInfo(token: String): String = “info”

// 添加了局部变量a,看下suspend怎么保存a这个变量

suspend fun test() {

val token = getToken(“123”) // 挂起点1

var a = 10 // 这里是10

val info = getInfo(token) // 挂起点2,需要将前面的数据保存(比如a),在挂起点之后恢复

println(info)

println(a

}

每个suspend函数调用点,都会生成一个挂起点,在挂起点我们要保存当前的运行状态,比如局部变量等。

反编译后的代码大致如下:

public final Object getToken(String id, Continuation completion) {

return “token”;

}

public final Object getInfo(String token, Continuation completion) {

return “info”;

}

// 重点函数(伪代码)

public final Object test(Continuation: continuation) {

Continuation cont = new ContinuationImpl(continuation) {

int label; // 保存状态

Object result; // 保存中间结果,还记得那个Result吗,是个泛型,因为泛型擦除,所以为Object,用到就强转

int tempA; // 保存上下文a的值,这个是根据具体代码产生的

};

switch(cont.label) {

case 0 : {

cont.label = 1; //更新label

getToken(“123”,cont) // 执行对应的操作,注意cont,就是传入的回调

break;

}

case 1 : {

cont.label = 2; // 更新label

// 这是一个挂起点,我们要保存上下文数据,这里就保存a的值

int a = 10;

cont.tempA = a; // 保存a的值

// 获取上一步的结果,因为泛型擦除,需要强转

String token = (Object)cont.result;

getInfo(token, cont); // 执行对应的操作

break;

}

case 2 : {

String info = (Object)cont.result; // 获取上一步的结果

println(info); // 执行对应的操作

// 在挂起点之后,恢复a的值

int a = cont.tempA;

println(a);

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
ITtcenK1-1715758673902)]

[外链图片转存中…(img-yEYNsglW-1715758673903)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐