本章节讲述Kotlin的挂起函数相关内容
一.简介
在一个普通Kotlin函数的前面加个suspend,这个普通的函数就成了挂起函数。挂起函数内部不一定会挂起,内部不挂起的称为伪挂起函数。
二.挂起函数
1.真正的挂起函数
private suspend fun testFunction():String {
delay(5000)
return "挂起函数返回字符串"
}
2.伪挂起函数
private suspend fun testFunction():String {
return "挂起函数返回字符串"
}
伪挂起函数,AndroidStudio会提示警告。
提示,删除suspend关键字,因为这个函数虽然前面加了suspend关键字,但是函数内部并没有真正的挂起,所以就是一个普通的函数,也就没有必要声明称挂起函数了。
3.调用
class KotlinActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_kotlin)
val result = testFunction()
}
private suspend fun testFunction(): String {
delay(3000)
return "挂起函数返回字符串"
}
}
AndroidStudio提示错误
也就是说,挂起函数只能从协程或其他挂起函数中调用。
4.正确挂起函数调用
class KotlinActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_kotlin)
GlobalScope.launch {
val result = testFunction()
Log.d("KotlinActivity", "result:$result")
}
Log.d("KotlinActivity", "onCreate方法执行...")
}
private suspend fun testFunction(): String {
delay(3000)
return "挂起函数返回字符串"
}
}
5.结果
2018-07-13 10:52:44.673 D/KotlinActivity: onCreate方法执行...
2018-07-13 10:52:47.679 D/KotlinActivity: result:挂起函数返回字符串
6.总结
<1> 挂起函数就是普通函数前面加上suspend关键字。
<2> 所谓的伪挂起函数,即普通函数前面加上suspend关键字,是没有意义的,AndroidStudio也会有相应的警告。
<3> 挂起函数,必须在协程中调用或者在其它挂起函数中调用。
三.挂起函数原理分析
1.查看Kotlin编译后的.java源码
步骤
2.上述代码编译后的.java文件
@Metadata(
mv = {1, 5, 1},
k = 1,
d1 = {"\u0000 \n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0000\n\u0002\u0010\u000e\n\u0002\b\u0002\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0012\u0010\u0003\u001a\u00020\u00042\b\u0010\u0005\u001a\u0004\u0018\u00010\u0006H\u0014J\u0011\u0010\u0007\u001a\u00020\bH\u0082@ø\u0001\u0000¢\u0006\u0002\u0010\t\u0082\u0002\u0004\n\u0002\b\u0019¨\u0006\n"},
d2 = {"Lcom/example/myapplication/kotlin/KotlinActivity;", "Landroidx/appcompat/app/AppCompatActivity;", "()V", "onCreate", "", "savedInstanceState", "Landroid/os/Bundle;", "testFunction", "", "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "MyApplication.app"}
)
public final class KotlinActivity extends AppCompatActivity {
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(1300028);
BuildersKt.launch$default((CoroutineScope)GlobalScope.INSTANCE, (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
int label;
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
Object var10000;
switch(this.label) {
case 0:
ResultKt.throwOnFailure($result);
KotlinActivity var4 = KotlinActivity.this;
this.label = 1;
var10000 = var4.testFunction(this);
if (var10000 == var3) {
return var3;
}
break;
case 1:
ResultKt.throwOnFailure($result);
var10000 = $result;
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
String result = (String)var10000;
Log.d("KotlinActivity", "result:" + result);
return Unit.INSTANCE;
}
@NotNull
public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
Intrinsics.checkNotNullParameter(completion, "completion");
Function2 var3 = new <anonymous constructor>(completion);
return var3;
}
public final Object invoke(Object var1, Object var2) {
return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
}
}), 3, (Object)null);
Log.d("KotlinActivity", "onCreate方法执行...");
}
private final Object testFunction(Continuation var1) {
Object $continuation;
label20: {
if (var1 instanceof <undefinedtype>) {
$continuation = (<undefinedtype>)var1;
if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
break label20;
}
}
$continuation = new ContinuationImpl(var1) {
// $FF: synthetic field
Object result;
int label;
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
this.result = $result;
this.label |= Integer.MIN_VALUE;
return KotlinActivity.this.testFunction(this);
}
};
}
Object $result = ((<undefinedtype>)$continuation).result;
Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch(((<undefinedtype>)$continuation).label) {
case 0:
ResultKt.throwOnFailure($result);
((<undefinedtype>)$continuation).label = 1;
if (DelayKt.delay(3000L, (Continuation)$continuation) == var4) {
return var4;
}
break;
case 1:
ResultKt.throwOnFailure($result);
break;
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
return "挂起函数返回字符串";
}
}
由上述,编译后的.java文件可以看出,我们测试的Kotlin的Activity中仅仅声明了一个简单的挂起方法,一共29行代码。
但是编译后的.java文件确足足有122行代码
这里我们仅仅看,我们声明的挂起函数的变化即可
private final Object testFunction(Continuation var1){
...
}
我们声明的时候,是没有入参的且返回值是String。
private suspend fun testFunction(): String {
...
}
但是,编译之后的.java文件中的对应挂起的方法中有入参,且返回值变成了Object。下面我们来分析下。
3.编译后的入参和返回值详解
Continuation,它其实是一个接口。源码
/**
* Interface representing a continuation after a suspension point that returns a value of type `T`.
*/
@SinceKotlin("1.3")
public interface Continuation<in T> {
/**
* The context of the coroutine that corresponds to this continuation.
*/
public val context: CoroutineContext
/**
* Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
* return value of the last suspension point.
*/
public fun resumeWith(result: Result<T>)
}
Continuation接口中
有一个属性
The context of the coroutine that corresponds to this continuation.
协程的上下文
有一个方法
Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the return value of the last suspension point.
将成功或失败[结果]作为最后一个挂起点的返回值
四.Java代码中调用挂起函数
Kotlin包含挂起函数的类
open class TestHangUp {
suspend fun testFunction(): String {
delay(3000)
return "挂起函数返回字符串..."
}
}
很简单,延时3秒钟返回 “挂起函数返回字符串...” 内容。
Java代码调用
TestHangUp testHangUp = new TestHangUp();
testHangUp.testFunction()
错误提示如下
也就是说,提示缺少参数,但是我们kotlin类定义的函数是没有入参的呢?为啥这里又要传参了呢?上面源码我们已近分析过滤,Java代码调用需要一个Continuation类型的参数。
正确的调用如下
new TestHangUp().testFunction(new Continuation<String>() {
@NonNull
@Override
public CoroutineContext getContext() {
return Dispatchers.getIO();
}
@Override
public void resumeWith(@NonNull Object o) {
String result = (String) o;
Log.d("KotlinActivity", "Java代码调用Kotlin挂起函数 result:" + result);
Log.d("KotlinActivity", "Java代码调用Kotlin挂起函数 线程:" + Thread.currentThread().getName());
}
});
Log.d("KotlinActivity", "Java代码调用Kotlin挂起函数 onCreate方法执行...");
结果
2018-07-13 14:08:23.916 : Java代码调用Kotlin挂起函数 onCreate方法执行...
2018-07-13 14:08:26.928 : Java代码调用Kotlin挂起函数 result:挂起函数返回字符串...
2018-07-13 14:08:26.929 : Java代码调用Kotlin挂起函数 线程:DefaultDispatcher-worker-1
由此可见,Java中调用Kotlin的挂起函数需要一个Continuation接口入参,且重写两个方法
方法resumeWith简单就是把挂起的函数结果回调给Java层。
方法getContext则是选择协程所处的线程。
<1> 上述getContext方法改成
Dispatchers.getMain()
运行结果
D/KotlinActivity: Java代码调用Kotlin挂起函数 onCreate方法执行...
D/KotlinActivity: Java代码调用Kotlin挂起函数 result:挂起函数返回字符串...
D/KotlinActivity: Java代码调用Kotlin挂起函数 线程:main
<2> 上述getContext方法改成
Dispatchers.getDefault()
运行结果
D/KotlinActivity: Java代码调用Kotlin挂起函数 onCreate方法执行...
D/KotlinActivity: Java代码调用Kotlin挂起函数 result:挂起函数返回字符串...
D/KotlinActivity: Java代码调用Kotlin挂起函数 线程:DefaultDispatcher-worker-2
总结
Java调用Kotlin的挂起函数两个方法中,resumeWith方法是拿挂起函数返回的回调,而getContext方法则是设置协程的上下文,决定resumeWith方法运行在那个线程中。
五.理解“挂起”&“恢复”
使用suspend 关键字修饰Kotlin的函数,该函数就是挂起函数。这就是“挂起”。那么挂起的函数怎么恢复呢,比如延时3秒后执行,3秒后谁来恢复这个挂起函数呢?
恢复函数就只继续函数的执行,由上述Java代码调用Kotlin挂起函数可知,resumeWith方法返回内容,也就是说 恢复挂起函数,其实就是执行resumeWith方法。
Kotlin协程中,“挂起”意味着一个协程停止执行(比如延时3秒钟),等待某些操作完成后继续执行(3秒钟后)。当协程被挂起时,它会保存其当前状态以便在恢复时可以继续执行。
“恢复”意味着一个挂起的协程继续执行,从它被挂起的地方开始。
Kotlin协程的挂起和恢复是通过编译器生成的状态机来实现的,开发者不需要手动管理状态切换,只需要使用协程提供的挂起函数即可实现异步非堵塞式编程方式。
这种方式不会堵塞主线程或占用过多的资源。