广告位联系
返回顶部
分享到

Kotlin协程与挂起函数及suspend关键字深入理解

Android 来源:互联网 作者:佚名 发布时间:2022-12-09 23:04:14 人浏览
摘要

1.挂起函数 挂起函数在Kotlin协程中是一个比较重要的知识点,协程的非阻塞式、Channel、Flow等API都对它有充分的理解才能在学习时事半功倍。 已知的是Kotlin协程的特点是轻量和非阻塞,

1.挂起函数

挂起函数在Kotlin协程中是一个比较重要的知识点,协程的非阻塞式、Channel、Flow等API都对它有充分的理解才能在学习时事半功倍。

已知的是Kotlin协程的特点是轻量和非阻塞, 单靠这两个点就能说明Kotlin协程就的优势吗,不一定。这里先提出一个结论:挂起函数是Kotlin协程的最大优势。 下面对于挂起函数的讲解就围绕这句话展开。

以获取省市区Code为例,获取区域Code的前提是要有城市Code,城市Code的前提是要有省份Code,请求结果通过CallBack返回。

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

public class RequestCode {

    public static void main(String[] args) {

        getProvincesCode(provincesCode -> {

            getCityCode(provincesCode, cityCode -> {

                getAreaCode(cityCode, areaCode -> {

                });

            });

        });

    }

    /**

     * 获取省份Code

     *

     * @param callBack

     */

    private static void getProvincesCode(CallBack callBack) {

        callBack.onSuccess("100000");

    }

    /**

     * 获取城市Code

     *

     * @param provincesCode

     * @param callBack

     */

    private static void getCityCode(String provincesCode, CallBack callBack) {

        callBack.onSuccess("100010");

    }

    /**

     * 获取区域code

     *

     * @param cityCode

     * @param callBack

     */

    private static void getAreaCode(String cityCode, CallBack callBack) {

        callBack.onSuccess("100011");

    }

}

上面的代码在开发中都遇到过,代码可以优化的更简洁更易读,这里主要是证明挂起函数的优势,就不做优化了。

上面的代码可以看到三层嵌套是比较复杂的,如果再加上获取国家、区域街道的话嵌套层级会更深,这样就会对可读性、可扩展性、可维护性都有影响。那么用Kotlin这个代码要怎么写呢?

现在我用delay(1000L)替代CallBack模拟网络请求

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

fun main() = runBlocking {

    val provincesCode = getProvincesCode()

    val cityCode = getCityCode(provincesCode)

    val areaCode = getAreaCode(cityCode)

}

/**

 * 获取省份Code

 *

 */

private suspend fun getProvincesCode(): String {

    withContext(Dispatchers.IO) {

        delay(1000L)

    }

    return "100000"

}

/**

 * 获取城市Code

 *

 * @param provincesCode

 */

private suspend fun getCityCode(provincesCode: String): String {

    withContext(Dispatchers.IO) {

        delay(1000L)

    }

    return "100010"

}

/**

 * 获取区域code

 *

 * @param cityCode

 */

private suspend fun getAreaCode(cityCode: String): String {

    withContext(Dispatchers.IO) {

        delay(1000L)

    }

    return "100011"

}

Kotlin实现了同步方式完成异步请求,这种方式的实现要归功于具体的函数的实现,在这三个函数中可以发现他们的定义与普通函数的区别是多了一个suspend关键字,它的意思就是挂起。

再回头看main函数,前面加上了runBlocking也就是说建立了一个协程的作用域,这是因为suspend的出现,因为suspend的作用就是挂起和恢复,而挂起和恢复是需要上下文的,因此就需要定义一个作用域,这里选择了runBlocking。

在函数中还有一个地方withContext(Dispatchers.IO)这主要是执行线程的切换,除了IO线程以外还有Main、default等线程。

在Kotlin中挂起和恢复是成对出现的,因为既然会被挂起那肯定也会被恢复,而协程的非阻塞也是因为挂起和恢复能力,那么挂起和恢复又是怎么样的一个含义呢?

首先执行getProvincesCode()这是一个挂起函数,因为用了Dispatchers.IO切换到了IO线程因此这个等待时间就在IO线程执行,当等待时间结束后(CallBack回调结果)provincesCode收到返回的结果,这个函数中suspend挂起,main函数中收到结果就是恢复能力,

我们看一下debug日志:

  • 这是getProvincesCode的协程日志,可以获取以下几个信息:
  • 当前协程的名字是:coroutine#1
  • 协程运行在主线程:main
  • 任务被挂起

withContext(Dispatchers.IO)切换线程

任务切换到DefaultDispatcher-worker-1线程继续执行

  • return "省:100000"回到主线程执行

我们总结一下上面的日志:

  • main → DefaultDispatcher-worker-1的过程是挂起;
  • DefaultDispatcher-worker-1 → main的过程是恢复;

那么挂起和恢复的含义也有非常明确了:

  • 挂起: 只是将程序执行流程转移到了其他线程,主线程并不会阻塞。我们知道Android中如果阻塞超过一定时间就会出现无响应,上面的代码在运行过程中并不会导致无响应的发生,在代码执行的过程中我们还可以做其他事情的,因为任务进入到子线程后主线程的状态是空闲的。
  • 恢复: 当子线程的任务执行完毕后再回到主线程的过程就叫做恢复。

挂起和恢复的能力是挂起函数特有的能力,普通函数时不具备的,如果在一个普通函数中仅仅加上suspend关键字会发现其实并没有什么用,编译器会告知这种定义是多余的。

上面还提出了一个结论——挂起函数是Kotlin协程的最大优势, 首先因为函数的执行过程中可以切换线程,其次函数的运行不会影响主线程导致主线程被阻塞。

2.深入理解suspend

上面挂起函数,挂起函数主要就是主线程切换到子线程,这个过程又是如何实现的?

已知挂起函数是依靠关键字suspend,我们将带有这个关键子的函数转换成Java代码进行分析:

1

2

3

4

5

6

private static final Object getProvincesCode(Continuation var0) {

    ...

    CoroutineContext var10000 = (CoroutineContext)Dispatchers.getIO();

    ...

    return "省:100000";

}

代码比较长,只保留了需要的地方。

  • getProvincesCode里面多了一个参数Continuation,意思是延续,而suspend没有了,那么Continuation又是什么

1

2

3

4

5

6

7

8

9

10

public interface Continuation<in T> {

    /**

     * 协程的上下文.

     */

    public val context: CoroutineContext

    /**

     * 恢复执行相应的协程,传递一个成功或失败的[result]作为最后一个挂起点的返回值

     */

    public fun resumeWith(result: Result<T>)

}

从Continuation的源码发现一个问题,它跟我们开头的CallBack是类似的,其中resumeWith和onSucess的功能也是一样的,区别在于Continuation是有一个泛型的参数

1

2

3

4

5

6

public interface CallBack {

    public void onSuccess(String response);

}

public interface Continuation<in T> {

    public fun resumeWith(result: Result<T>)

}

由此可以得出结论:Continuation本质上就是CallBack只是多了一个带有泛型的参数。

通过上面的分析可以得出结论 :挂起函数的本质就是Callback

我们已知Continuation的本质是CallBack,Continuation是延续,就是接下来要做的事情,那么在省市区获取的代码其实就是这样一个流程:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

getProvincesCode(object : Continuation<String> {

    override fun resumeWith(result: Result<String>) {

        val provinceCode = result.getOrNull()

        println("provincesCode:$provinceCode")

        getCityCode(provinceCode, object : Continuation<String> {

            override fun resumeWith(result: Result<String>) {

                val cityCode = result.getOrNull()

                println("cityCode:$cityCode")

                getAreaCode(cityCode, object : Continuation<String> {

                    override fun resumeWith(result: Result<String>) {

                        val areaCode = result.getOrNull()

                        println("areaCode:$areaCode")

                    }

                })

            }

        })

    }

})

这个过程是编译器在后面帮我们做的,实际编码中这种编码方式并不会出现,毕竟挂起函数解决的就是这个问题。

3.协程与挂起函数

需要说明的是协程与挂起函数并不是同一个东西,我们再最开始使用suspend实现省市区三级联动的时候用到了runBlocking,挂起函数的调用是需要一个协程作用域的,除了这点之外,在runBlocking的源码中还有一点要注意:

1

2

3

4

5

public actual fun <T> runBlocking(

    context: CoroutineContext,

    block: suspend CoroutineScope.() -> T): T {

    ...

}

第二个参数中有一个suspend,所以可以理解CoroutineScope.() -> T也是一个挂起函数那么被suspend关键字修饰的挂起函数可以运行在runBlocking中也就不难理解了。

那么我们就可以得到一个结论:挂起和恢复是协程的底层能力,而挂起函数是一种表现形式,通过suspend关键字修饰的函数可以让我们在上层很方便的实现这种底层能力。

4.挂起函数是Kotlin协程的最大优势

开篇就提出了这个结论,最后再来总结一下这个能力:

  • 挂起函数在执行中可以切换线程且不会对主线程造成阻塞;
  • 挂起函数有挂起和恢复的能力,可以极大地简化异步编程,实现同步执行异步任务;
  • 挂起函数是Kotlin中特有的能力;

5.总结

  • 定义一个挂起函数只需要在普通函数上加上suspend关键字,而添加这个关键字之后函数类型就会被改变,如suspend (Int) -&gt; Double”与“(Int) -> Double并不是同一个类型;
  • 挂起函数具有挂起和恢复的能力,那么就会出现同一行代码会在两个线程中执行,Kotlin编译器会在后台进行编译;
  • 挂起函数的本质就是CallBack。只是说,Kotlin 底层用了一个更加高大上的名字,叫 Continuation;
  • 挂起和恢复是一种底层能力,而上层的表现形式是挂起函数;
  • 挂起函数只能在协程中被调用或者在挂起函数中调用,而协程中的block也是一个挂起函数。


版权声明 : 本文内容来源于互联网或用户自行发布贡献,该文观点仅代表原作者本人。本站仅提供信息存储空间服务和不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权, 违法违规的内容, 请发送邮件至2530232025#qq.cn(#换@)举报,一经查实,本站将立刻删除。
原文链接 :
相关文章
  • 使用Flutter实现手写签名效果的教程
    思路 需要监听用户触摸的起始点和结束点,并记录途经点,这里我使用了StreamController 将途经点从起始位置到结束位置绘制出来,这里用到
  • Kotlin协程与挂起函数及suspend关键字深入理解

    Kotlin协程与挂起函数及suspend关键字深入理解
    1.挂起函数 挂起函数在Kotlin协程中是一个比较重要的知识点,协程的非阻塞式、Channel、Flow等API都对它有充分的理解才能在学习时事半功倍。
  • Android自定义View实现绘制水波浪温度刻度表

    Android自定义View实现绘制水波浪温度刻度表
    之前的绘制圆环,我们了解了如何绘制想要的形状和进度的一些特点,那么此篇文章我们更近一步,绘制一个稍微复杂一点的刻度与波浪。
  • Android硬件解码组件MediaCodec使用教程
    1.MediaCodec 是什么 MediaCodec类可以访问底层媒体编解码器框架(StageFright 或 OpenMAX),即编解码组件。是Android 的低层多媒体基础设施的一部分
  • Flow解决背压问题的方法介绍

    Flow解决背压问题的方法介绍
    随着时间的推移,越来越多的主流应用已经开始全面拥抱Kotlin,协程的引入,Flow的诞生,给予了开发很多便捷,作为协程与响应式编程结合
  • Andorid开发中反射机制的介绍
    在andorid开发中,经常遇见在某些工具类中没有Context上下文对象时,一些系统服务的代理对象无法创建出来,举个例子:比如在源码(framewo
  • Flutter加载图片的多样玩法汇总

    Flutter加载图片的多样玩法汇总
    加载本地图片 在项目目录下创建assets文件夹,再在其文件夹下创建images文件夹,后面将需要的图片复制到其中即可 在pubspec.yaml文件中添加引
  • Kotlin的Collection与Sequence操作异同点介绍

    Kotlin的Collection与Sequence操作异同点介绍
    在Android开发中,集合是我们必备的容器,Kotlin的标准库中提供了很多处理集合的方法,而且还提供了两种基于容器的工作方式:Collection 和
  • 实现一个Kotlin函数类型方法

    实现一个Kotlin函数类型方法
    接口与函数类型 业务开发中,经常会有实现一个函数式接口(即接口只有一个方法需要实现)的场景,大家应该都会不假思索的写出如下代
  • Android10 App启动Activity源码分析
    ActivityThread的main方法 让我们把目光聚焦到ActivityThread的main方法上。 ActivityThread的源码路径为/frameworks/base/core/java/android/app/ActivityThread。 1 2
  • 本站所有内容来源于互联网或用户自行发布,本站仅提供信息存储空间服务,不拥有版权,不承担法律责任。如有侵犯您的权益,请您联系站长处理!
  • Copyright © 2017-2022 F11.CN All Rights Reserved. F11站长开发者网 版权所有 | 苏ICP备2022031554号-1 | 51LA统计