Android Kotlin 协程初探

这篇具有很好参考价值的文章主要介绍了Android Kotlin 协程初探。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1 它是什么(协程 和 Kotlin协程)

1.1 协程是什么

维基百科:协程,英文Coroutine [kəru’tin] (可入厅),是计算机程序的一类组件,推广了协作式多任务的子程序,允许执行被挂起与被恢复。

作为Google钦定的Android开发首选语言Kotlin,协程并不是 Kotlin 提出来的新概念,目前有协程概念的编程语言有Lua语言、Python语言、Go语言、C语言等,它只是一种编程思想,不局限于特定的语言。

而每一种编程语言中的协程的概念及实现又不完全一样,本次分享主要讲Kotlin协程。

1.2 Kotlin协程是什么

Kotlin官网:协程是轻量级线程

可简单理解:一个线程框架,是全新的处理并发的方式,也是Android上方便简化异步执行代码的方式

类似于 Java:线程池 Android:Handler和AsyncTask,RxJava的Schedulers

注:Kotlin不仅仅是面向JVM平台的,还有JS/Native,如果用kotlin来写前端,那Koltin的协程就是JS意义上的协程。如果仅仅JVM 平台,那确实应该是线程框架。

1.3 进程、线程、协程比较

可通过以下两张图理解三者的不同和关系

2 为什么选择它(协程解决什么问题)

异步场景举例:

  1. 第一步:接口获取当前用户token及用户信息
  2. 第二步:将用户的昵称展示界面上
  3. 第三步:然后再通过这个token获取当前用户的消息未读数
  4. 第四步:并展示在界面上

2.1 现有方案实现

apiService.getUserInfo().enqueue(object :Callback<User>{
    override fun onResponse(call: Call<User>, response: Response<User>) {
        val user = response.body()
        tvNickName.text = user?.nickName
        apiService.getUnReadMsgCount(user?.token).enqueue(object :Callback<Int>{
            override fun onResponse(call: Call<Int>, response: Response<Int>) {
                val tvUnReadMsgCount = response.body()
                tvMsgCount.text = tvUnReadMsgCount.toString()
            }
        })
    }
})

现有方案如何拿到异步任务的数据,得不到就毁掉哈哈哈,就是通过回调函数来解决。
若嵌套多了,这种画风是不是有点回调地狱的感觉,俗称的「callback hell」

2.2 协程实现

mainScope.launch {
    val user = apiService.getUserInfoSuspend() //IO线程请求数据
    tvNickName.text = user?.nickName //UI线程更新界面
    val unReadMsgCount = apiService.getUnReadMsgCountSuspend(user?.token) //IO线程请求数据
    tvMsgCount.text = unReadMsgCount.toString() //UI线程更新界面
}
suspend fun getUserInfoSuspend() :User? {
    return withContext(Dispatchers.IO){
        //模拟网络请求耗时操作
        delay(10)
        User("asd123", "userName", "nickName")
    }
}

suspend fun getUnReadMsgCountSuspend(token:String?) :Int{
    return withContext(Dispatchers.IO){
        //模拟网络请求耗时操作
        delay(10)
        10
    }
}

红色框框内的就是一个协程代码块。

可以看得出在协程实现中告别了callback,所以再也不会出现回调地狱这种情况了,协程解决了回调地狱

协程可以让我们用同步的代码写出异步的效果,这也是协程最大的优势,异步代码同步去写。

小结:协程可以异步代码同步去写,解决回调地狱,让程序员更方便地处理异步业务,更方便地切线程,保证主线程安全。

它是怎么做到的?

3 它是怎么工作的(协程的原理浅析)

3.1 协程的挂起和恢复

挂起(非阻塞式挂起)

suspend 关键字,它是协程中核心的关键字,是挂起的标识。

下面看一下上述示例代码切换线程的过程:

每一次从主线程切到IO线程都是一次协程的挂起操作;

每一次从IO线程切换主线程都是一次协程的恢复操作;

挂起和恢复是suspend函数特有的能力,其他函数不具备,挂起的内容是协程,不是挂起线程,也不是挂起函数,当线程执行到suspend函数的地方,不会继续执行当前协程的代码了,所以它不会阻塞线程,是非阻塞式挂起。

有挂起必然有恢复流程, 恢复是指将已经被挂起的目标协程从挂起之处开始恢复执行。在协程中,挂起和恢复都不需要我们手动处理,这些都是kotlin协程帮我们自动完成的。

那Kotlin协程是如何帮我们自动实现挂起和恢复操作的呢?

它是通过Continuation来实现的。 [kənˌtɪnjuˈeɪʃ(ə)n] (继续;延续;连续性;后续部分)

3.2 协程的挂起和恢复的工作原理(Continuation)

CPS + 状态机

Java中没有suspend函数,suspend是Kotlin中特有的关键字,当编译时,Kotlin编译器会将含有suspend关键字的函数进行一次转换。

这种被编译器转换在kotlin中叫CPS转换(cotinuation-passing-style)。

转换流程如下所示

程序员写的挂起函数代码:

suspend fun getUserInfo() : User {
    val user = User("asd123", "userName", "nickName")
    return user
}

假想的一种中间态代码(便于理解):

fun getUserInfo(callback: Callback<User>): Any? {
    val user = User("asd123", "userName", "nickName")
    callback.onSuccess(user)
    return Unit
}

转换后的代码:

fun getUserInfo(cont: Continuation<User>): Any? {
    val user = User("asd123", "userName", "nickName")
    cont.resume(user)
    return Unit
}

我们通过Kotlin生成字节码工具查看字节码,然后将其反编译成Java代码:

@Nullable
public final Object getUserInfo(@NotNull Continuation $completion) {
   User user = new User("asd123", "userName", "nickName");
   return user;
}

这也验证了确实是会通过引入一个Continuation对象来实现恢复的流程,这里的这个Continuation对象中包含了Callback的形态

它有两个作用:1. 暂停并记住执行点位;2. 记住函数暂停时刻的局部变量上下文。

所以为什么我们可以用同步的方式写异步代码,是因为Continuation帮我们做了回调的流程。

下面看一下这个Continuation 的源码部分

可以看到这个Continuation中封装了一个resumeWith的方法,这个方法就是恢复用的。

internal abstract class BaseContinuationImpl() : Continuation<Any?> {


    public final override fun resumeWith(result: Result<Any?>) {
        //省略好多代码
        invokeSuspend()
        //省略好多代码
    }


    protected abstract fun invokeSuspend(result: Result<Any?>): Any?
}


internal abstract class ContinuationImpl(
    completion: Continuation<Any?>?,
    private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {

protected abstract fun invokeSuspend(result: Result<Any?>): Any?

//invokeSuspend() 这个方法是恢复的关键一步

继续看上述例子:

这是一个CPS之前的代码:

suspend fun testCoroutine() {
    val user = apiService.getUserInfoSuspend() //挂起函数  IO线程
    tvNickName.text = user?.nickName //UI线程更新界面
    val unReadMsgCount = apiService.getUnReadMsgCountSuspend(user?.token) //挂起函数  IO线程
    tvMsgCount.text = unReadMsgCount.toString() //UI线程更新界面
}

当前挂起函数里有两个挂起函数

通过kotlin编译器编译后:

fun testCoroutine(completion: Continuation<Any?>): Any? {
    // TestContinuation本质上是匿名内部类
    class TestContinuation(completion: Continuation<Any?>?) : ContinuationImpl(completion) {
        // 表示协程状态机当前的状态
        var label: Int = 0


        // 两个变量,对应原函数的2个变量
        lateinit var user: Any
        lateinit var unReadMsgCount: Int


        // result 接收协程的运行结果
        var result = continuation.result


        // suspendReturn 接收挂起函数的返回值
        var suspendReturn: Any? = null


        // CoroutineSingletons 是个枚举类
        // COROUTINE_SUSPENDED 代表当前函数被挂起了
        val sFlag = CoroutineSingletons.COROUTINE_SUSPENDED


        // invokeSuspend 是协程的关键
        // 它最终会调用 testCoroutine(this) 开启协程状态机
        // 状态机相关代码就是后面的 when 语句
        // 协程的本质,可以说就是 CPS + 状态机
        override fun invokeSuspend(_result: Result<Any?>): Any? {
            result = _result
            label = label or Int.Companion.MIN_VALUE
            return testCoroutine(this)
        }
    }


    // ...
    val continuation = if (completion is TestContinuation) {
        completion
    } else {
        //                作为参数
        //                   ↓
        TestContinuation(completion)
loop = true
while(loop) {
when (continuation.label) {
    0 -> {
        // 检测异常
        throwOnFailure(result)


        // 将 label 置为 1,准备进入下一次状态
        continuation.label = 1


        // 执行 getUserInfoSuspend(第一个挂起函数)
        suspendReturn = getUserInfoSuspend(continuation)


        // 判断是否挂起
        if (suspendReturn == sFlag) {
            return suspendReturn
        } else {
            result = suspendReturn
            //go to next state
        }
    }


    1 -> {
        throwOnFailure(result)


        // 获取 user 值
        user = result as Any


        // 准备进入下一个状态
        continuation.label = 2


        // 执行 getUnReadMsgCountSuspend
        suspendReturn = getUnReadMsgCountSuspend(user.token, continuation)


        // 判断是否挂起
        if (suspendReturn == sFlag) {
            return suspendReturn
        } else {
            result = suspendReturn
            //go to next state
        }
    }


    2 -> {
        throwOnFailure(result)


        user = continuation.mUser as Any
        unReadMsgCount = continuation.unReadMsgCount as Int
        loop = false
}
}

通过一个label标签控制分支代码执行,label为0,首先会进入第一个分支,首先将label设置为下一个分支的数值,然后执行第一个suspend方法并传递当前Continuation,得到返回值,如果是COROUTINE SUSPENDED,协程框架就直接return,协程挂起,当第一个suspend方法执行完成,会回调Continuation的invokeSuspend方法,进入第二个分支执行,以此类推执行完所有suspend方法。

每一个挂起点和初始挂起点对应的 Continuation 都会转化为一种状态,协程恢复只是跳转到下一种状态中。挂起函数将执行过程分为多个 Continuation 片段,并且利用状态机的方式保证各个片段是顺序执行的。

小结:协程的挂起和恢复的本质是CPS + 状态机

4 总结

总结几个不用协程实现起来很麻烦的骚操作:

  1. 如果有一个函数,它的返回值需要等到多个耗时的异步任务都执行完毕返回之后,组合所有任务的返回值作为 最终返回值
  2. 如果有一个函数,需要顺序执行多个网络请求,并且后一个请求依赖前一个请求的执行结果
  3. 当前正在执行一项异步任务,但是你突然不想要它执行了,随时可以取消
  4. 如果你想让一个任务最多执行3秒,超过3秒则自动取消

Kotlin协程之所以被认为是假协程,是因为它并不在同一个线程运行,而是真的会创建多个线程。

Kotlin协程在Android上只是一个类似线程池的封装,真就是一个线程框架。但是它却可以让我们用同步的代码风格写出异步的效果,至于怎么做的,这个不需要我们操心,这些都是kotlin帮我们处理好了,我们需要关心的是怎么用好它

它就是一个线程框架。

作者:京东物流 王斌

来源:京东云开发者社区 自猿其说Tech 转载请注明来源文章来源地址https://www.toymoban.com/news/detail-711031.html

到了这里,关于Android Kotlin 协程初探的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Kotlin 协程一 —— 协程 Coroutine

    1.1.1基本定义 进程 进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。 进程是资源分配的最小单位,在单核CPU中,同一时刻只有一个程序在内存中被CPU调用运行。 线程 基本的

    2024年02月05日
    浏览(47)
  • kotlin语法进阶 - 协程(一)协程基础

    协程并不是一个新的概念,而是一个非常老的概念,很多语言都支持协程,建议去浏览器去了解一下协程的历史和基本概念,这里我们只讲一下kotlin中的协程的作用。 从代码实现角度来看:kotlin协程底层是用线程实现的,是一个封装完善供开发者使用的线程框架。kotlin的一个

    2024年02月09日
    浏览(44)
  • 【Android】Kotlin 中的Flow是个什么东西

    前言 Kotlin Flow 是 Kotlin Coroutine 用于异步获取数据流的一个库。它允许我们以类似于集合的方式发射多个异步生成的值,并通过类似于 RxJava 的操作符链式处理这些值。 基本概念 Flow 的基本概念是,一个 Flow 代表了一个异步生成的值序列,这些值可能会在不同的时间点被发送出

    2024年02月08日
    浏览(47)
  • Kotlin协程-从一到多

    上一篇文章,我介绍了Kotlin协程的创建,使用,协作等内容。本篇将引入更多的使用场景,继续带你走进协程世界。 常用编程语言都会内置对同一类型不同对象的数据集表示,我们通常称之为容器类。不同的容器类适用于不同的使用场景。Kotlin的 Flow 就是在异步计算的需求下

    2024年02月09日
    浏览(54)
  • Kotlin协程-从理论到实战

    上一篇文章从理论上对Kotlin协程进行了部分说明,本文将在上一篇的基础上,从实战出发,继续协程之旅。 在Kotlin中,要想使用协程,首先需要使用协程创建器创建,但还有个前提——协程作用域( CoroutineScope )。在早期的Kotlin实现中,协程创建器是一等函数,也就是说我们随

    2024年02月09日
    浏览(41)
  • Kotlin协程学习之-02

    协程的基本使用 GlobalScope.launch 生命周期与进程一致,且无法取消 runBlocking 会阻塞线程,一般在测试阶段可以使用 val coroutineScope = CoroutineScope(context) coroutineScope.launch 通过context参数去管理和控制协程的生命周期 用法 val coroutineScope = CoroutineScope(context) coroutineScope.launch(Dispatche

    2024年01月22日
    浏览(42)
  • Kotlin 协程基础使用学习

    原文: Kotlin 协程基础使用学习-Stars-One的杂货小窝 本篇阅读可能需要以下知识,否则可能阅读会有些困难 客户端开发基础(Android开发或JavaFx开发) Java多线程基础 kotlin基础 本文尽量以使用为主,以代码为辅讲解,不提及过深协程底层代码逻辑,仅做一个基础入门来快速上手学习(断断

    2024年03月18日
    浏览(59)
  • Kotlin 协程 - 多路复用 select()

            又叫选择表达式,是一个挂起函数,可以同时等待多个挂起结果,只取用最快恢复的那个值(即多种方式获取数据,哪个更快返回结果就用哪个)。         同时到达 select() 会优先选择先写子表达式,想随机(公平)的话使用 selectUnbiased() 替换 。         能

    2024年02月10日
    浏览(43)
  • 【Kotlin】协程的字节码原理

    前言 协程是Koltin语言最重要的特性之一,也是最难理解的特性。网上关于kotlin协程的描述也是五花八门,有人说它是轻量级线程,有人说它是无阻塞式挂起,有人说它是一个异步框架等等,众说纷芸。甚至还有人出了书籍专门介绍kotlin协程。 笔者刚开始接触这个概念也是一

    2024年01月18日
    浏览(38)
  • Kotlin 协程 supervisorScope {} 运行崩溃解决

    简单介绍 supervisorScope 函数,它用于创建一个使用了 SupervisorJob 的 coroutineScope, 该作用域的特点:抛出的异常,不会 连锁取消 同级协程和父协程。 看过很多  supervisorScope {}   文档的使用,我照抄一摸一样的代码,运行就崩溃,最后找到了解决方法,应该是kotlin版本更新做过

    2024年01月25日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包