请求解析Kotlin挂起函数反编译代码的执行流程
挂起函数与Continuation的反编译代码执行路径解析
我正在阅读Ray Wenderlich所著的《Kotlin Coroutines by Tutorials》第4章——挂起函数,但难以理解作者对反编译代码的解释,恳请结合挂起函数与Continuation(续体),解析以下反编译代码的执行路径:
原Kotlin挂起函数
suspend fun getUserSuspend(userId: String): User { delay(1000) return User(userId, "Filip") }
反编译后的Java代码
@Nullable public static final Object getUserSuspend( @NotNull String userId, @NotNull Continuation var1) { Object $continuation; label28: { if (var1 instanceof <undefinedtype>) { $continuation = (<undefinedtype>)var1; if (((<undefinedtype>)$continuation).label & Integer.MIN_VALUE != 0) { ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE; break label28; } } $continuation = new ContinuationImpl(var1) { // $FF: synthetic field Object result; int label; Object L$0; @Nullable public final Object invokeSuspend (@NotNull Object result) { this.result = result; this.label |= Integer.MIN_VALUE; return MainKt.getUserSuspend((String)null, this); } }; } Object var2 = ((<undefinedtype>)$continuation).result; Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED(); switch(((<undefinedtype>)$continuation).label) { case 0: if (var2 instanceof Failure) { throw ((Failure) var2).exception; } ((<undefinedtype>)$continuation).L$0 = userId; ((<undefinedtype>)$continuation).label = 1; if (DelayKt.delay(1000L, (Continuation)$continuation) == var4) { return var4; } break; case 1: userId = (String)((<undefinedtype>)$continuation).L$0; if (var2 instanceof Failure) { throw ((Failure) var2).exception; } break; default: throw new IllegalStateException("call to ’resume’ before ’invoke’ with coroutine"); } return new User(userId, "Filip"); }
核心概念前置
- 挂起函数:Kotlin协程中可暂停执行、后续恢复的函数,编译后自动追加
Continuation参数,用于保存执行状态和恢复入口。 - Continuation(续体):本质是回调对象,存储挂起点的上下文(局部变量、执行位置),恢复时携带结果重新进入函数。
执行路径分步解析
1. 第一次调用:初始化续体
首次调用getUserSuspend时,传入的var1是外部协程的续体,并非内部生成的ContinuationImpl实例:
- 进入
label28代码块,因var1不是目标类型,创建新的ContinuationImpl实例:- 该实例包含三个核心字段:
result(存储挂起恢复后的结果)、label(标记当前执行阶段)、L$0(暂存局部变量userId)。 - 它的
invokeSuspend方法会在挂起恢复时触发:将恢复结果存入result,给label打上Integer.MIN_VALUE标记(标识已处于恢复状态),随后重新调用getUserSuspend并传入自身。
- 该实例包含三个核心字段:
- 初始化完成后,
$continuation指向新续体,此时label为0,result为null。
2. 阶段0:执行delay并挂起
进入switch的case 0分支:
- 检查
result是否为Failure(异常场景),首次调用时result为null,跳过异常抛出。 - 将
userId存入续体的L$0字段(挂起后局部变量会丢失,需提前保存上下文)。 - 将续体的
label设为1,标记下次恢复后要执行的阶段。 - 调用
DelayKt.delay(1000L, $continuation):delay作为挂起函数,会启动定时器,1秒后通过传入的$continuation恢复协程。delay返回IntrinsicsKt.getCOROUTINE_SUSPENDED(),告知调用者当前函数需挂起。
- 函数返回
var4(即COROUTINE_SUSPENDED),协程进入挂起状态。
3. 挂起恢复:重新进入函数
1秒后定时器触发,调用续体的invokeSuspend方法:
- 将
delay的执行结果(这里是Unit,因delay无返回值)存入result。 - 给
label按位或Integer.MIN_VALUE,标记续体已处于恢复状态。 - 重新调用
getUserSuspend(null, this),此时传入的var1就是当前续体实例。
4. 阶段1:完成剩余逻辑
重新进入getUserSuspend:
- 进入
label28代码块,检测到var1是内部的ContinuationImpl实例,且label带有Integer.MIN_VALUE标记:- 将
label减去Integer.MIN_VALUE,得到之前设置的1,跳出label28块,$continuation直接使用传入的续体。
- 将
- 进入
switch的case 1分支:- 从续体的
L$0字段恢复userId的值。 - 再次检查
result是否为Failure,此处为正常恢复,跳过异常。
- 从续体的
- 跳出
switch后,执行return new User(userId, "Filip"),返回最终结果,协程执行完成。
内容的提问来源于stack exchange,提问作者VIN
相关产品推荐
相关产品推荐

