Kotlin教程 挂起函数详解

本章节讲述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协程的挂起恢复是通过编译器生成状态机实现的,开发者不需要手动管理状态切换,只需要使用协程提供的挂起函数即可实现异步非堵塞式编程方式。
这种方式
不会堵塞主线程占用过多的资源

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值