Android学习笔记03-Kotlin字节码解析

Posted by panthole on 2021-04-15

一、背景

kotlinx.coroutines 是由 JetBrains 开发的功能丰富的协程库。它包含本指南中涵盖的很多启用高级协程的原语,包括 launch、 async 等等。

本文是通过反编译协程相关class文件,分析协程的实现原理。

二、 Kotlin代码 T1.kt

第一个基本协程代码

1
2
3
4
5
6
7
8
9
10
11
12
13
import kotlinx.coroutines.*

fun main() {
println("Starting...")
GlobalScope.launch {
repeat(10) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
println("Hello,")
Thread.sleep(20000L)
}

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
Starting...
Hello,
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
I'm sleeping 3 ...
I'm sleeping 4 ...
I'm sleeping 5 ...
I'm sleeping 6 ...
I'm sleeping 7 ...
I'm sleeping 8 ...
I'm sleeping 9 ...

三、字节码

T1.kt编译后出现两个class文件T1kt.class和T1ktmain1.class

3.1 T1kt.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public final class T1Kt {
public static final void main();
Code:
0: ldc #11 // String Starting...
2: astore_0
3: iconst_0
4: istore_1
5: getstatic #17 // Field java/lang/System.out:Ljava/io/PrintStream;
8: aload_0
9: invokevirtual #23 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
12: getstatic #29 // Field kotlinx/coroutines/GlobalScope.INSTANCE:Lkotlinx/coroutines/GlobalScope;
15: checkcast #31 // class kotlinx/coroutines/CoroutineScope
18: aconst_null
19: aconst_null
20: new #33 // class T1Kt$main$1
23: dup
24: aconst_null
25: invokespecial #37 // Method T1Kt$main$1."<init>":(Lkotlin/coroutines/Continuation;)V
28: checkcast #39 // class kotlin/jvm/functions/Function2
31: iconst_3
32: aconst_null
33: invokestatic #45 // Method kotlinx/coroutines/BuildersKt.launch$default:(Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job;
36: pop
37: ldc #47 // String Hello,
39: astore_0
40: iconst_0
41: istore_1
42: getstatic #17 // Field java/lang/System.out:Ljava/io/PrintStream;
45: aload_0
46: invokevirtual #23 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
49: ldc2_w #48 // long 20000l
52: invokestatic #55 // Method java/lang/Thread.sleep:(J)V
55: return

public static void main(java.lang.String[]);
Code:
0: invokestatic #9 // Method main:()V
3: return
}

3.1.1 public static void main(java.lang.String[]) main

自动创建了public static void main(java.lang.String[]) main方法,通过invokestatic字节码调用public static final void main方法。

3.1.2 public static final void main

此方法对应于T1.kt中的fun main方法
* 0:从常量池加载字面量”Starting”
* 9:打印”Starting”
* 20:创建T1Ktmain1类
* 25:初始化T1Ktmain1对象
* 33:调用静态方法kotlinx/coroutines/BuildersKt.launch,入参是GlobalScope、null、null、T1Ktmain1对象(Function2接口的实例)
* 46:打印Hello
* 52:休眠20000毫秒,等待协程执行完

BuildersKt.launch方法的入参为CoroutineScope、CoroutineContext、CoroutineStart、Function2、Object。在此场景下:
* CoroutineScope:GlobalScope,默认的顶层协程
* CoroutineContext:四种协程上下文Unconfined、Default、newSingleThreadContext、runBlocking,此时使用Default调度器,使用一个JVM内的共享线程池
* CoroutineStart:DEFAULT,立即调度协程并使用指定的CoroutineContext

3.2 T1ktmain1.class

T1ktmain1继承了抽象类kotlin.coroutines.jvm.internal.SuspendLambda,并实现了接口kotlin.jvm.functions.Function2

3.2.1 public final java.lang.Object invoke(java.lang.Object, java.lang.Object)

协程执行入口

1
2
3
4
5
6
7
8
9
 0: aload_0
1: aload_1
2: aload_2
3: checkcast #149 // class kotlin/coroutines/Continuation
6: invokevirtual #151 // Method create:(Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
9: checkcast #2 // class T1Kt$main$1
12: getstatic #109 // Field kotlin/Unit.INSTANCE:Lkotlin/Unit;
15: invokevirtual #153 // Method invokeSuspend:(Ljava/lang/Object;)Ljava/lang/Object;
18: areturn
  • 0-2:加载this和两个入参(CoroutineScope和Coroutine)
  • 15:调用实例方法invokeSuspend

3.2.2 public final java.lang.Object invokeSuspend

本方法为协程的具体执行逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
0: invokestatic  #34                 // Method kotlin/coroutines/intrinsics/IntrinsicsKt.getCOROUTINE_SUSPENDED:()Ljava/lang/Object;
3: astore 11
5: aload_0
6: getfield #37 // Field label:I
9: tableswitch { // 0 to 1
0: 32
1: 156
default: 190
}
32: aload_1
33: invokestatic #43 // Method kotlin/ResultKt.throwOnFailure:(Ljava/lang/Object;)V
36: aload_0
37: getfield #45 // Field p$:Lkotlinx/coroutines/CoroutineScope;
40: astore_2
41: bipush 10
43: istore_3
44: iconst_0
45: istore 4
47: iconst_0
48: istore 5
50: iconst_0
51: istore 5
53: iload_3
54: istore 6
56: iload 5
58: iload 6
60: if_icmpge 186
63: iload 5
65: invokestatic #51 // Method kotlin/coroutines/jvm/internal/Boxing.boxInt:(I)Ljava/lang/Integer;
68: checkcast #53 // class java/lang/Number
71: invokevirtual #57 // Method java/lang/Number.intValue:()I
74: istore 7
76: iconst_0
77: istore 8
79: new #59 // class java/lang/StringBuilder
82: dup
83: invokespecial #63 // Method java/lang/StringBuilder."<init>":()V
86: ldc #65 // String I'm sleeping
88: invokevirtual #69 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
91: iload 7
93: invokevirtual #72 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
96: ldc #74 // String ...
98: invokevirtual #69 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
101: invokevirtual #78 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
104: astore 9
106: iconst_0
107: istore 10
109: getstatic #84 // Field java/lang/System.out:Ljava/io/PrintStream;
112: aload 9
114: invokevirtual #89 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
117: ldc2_w #90 // long 500l
120: aload_0
121: aload_0
122: iload 5
124: putfield #93 // Field I$0:I
127: aload_0
128: iload 6
130: putfield #95 // Field I$1:I
133: aload_0
134: iload 7
136: putfield #97 // Field I$2:I
139: aload_0
140: iconst_1
141: putfield #37 // Field label:I
144: invokestatic #103 // Method kotlinx/coroutines/DelayKt.delay:(JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
147: dup
148: aload 11
150: if_acmpne 179
153: aload 11
155: areturn
156: aload_0
157: getfield #97 // Field I$2:I
160: istore 7
162: aload_0
163: getfield #95 // Field I$1:I
166: istore 6
168: aload_0
169: getfield #93 // Field I$0:I
172: istore 5
174: aload_1
175: invokestatic #43 // Method kotlin/ResultKt.throwOnFailure:(Ljava/lang/Object;)V
178: aload_1
179: pop
180: iinc 5, 1
183: goto 56
186: getstatic #109 // Field kotlin/Unit.INSTANCE:Lkotlin/Unit;
189: areturn
190: new #111 // class java/lang/IllegalStateException
193: dup
194: ldc #113 // String call to 'resume' before 'invoke' with coroutine
196: invokespecial #116 // Method java/lang/IllegalStateException."<init>":(Ljava/lang/String;)V
199: athrow
  • 6: 获取对象37字段的值,并压入栈,37字段对应于T1Ktmain1.label。
    label是个int类型的标识,用于管理Coroutine的状态机
  • 9:由于分支集中,仅两个此处使用了tableswitch字节码,而非lookupswitch
  • 141:修改label为1,默认值0
  • 144:调用delay方法,挂起协程500毫秒
  • 150:回边判断
  • 179:goto回循环开始

四、总结

  • Kotlin协程通过编译器织入字节码实现
  • 协程执行逻辑被放入一个内部匿名类,继承了抽象类kotlin.coroutines.jvm.internal.SuspendLambda,并实现了接口kotlin.jvm.functions.Function2
  • 调度器调度入口是Function2的invoke方法
  • 通过delay方法挂起协程,该方法不会阻塞线程