“吹Kotlin协程的,可能吹错了!” 带你真正理解一波

Kotlin(九)探秘Kotlin协程机制,2021年Android常见面试题 场景1:异步回调嵌套 常规写法 //客户端顺序进行三次网络异步请求,并用最终结果更新UI request1(parameter) { value1 -> request2(value1) { value2 -> request3(value2) { value3 -> updateUI(value3) } } } 这种结构的代码无论是阅读起来还是维护起来都是极其糟糕的。对多个回调组成的嵌套耦合,我亲切地称为 “回调地狱”。 协程的写法 GlobalScope.launch(Dis 阅读详情

作者:Geekholt

链接:

http://blog.youkuaiyun.com/fu908323236/article/details/78356344

1概述

目录
目录
前言

Kotlin协程,现在已经成为了面试甚至是工作中一个非常火的东西。

本人在刚开始了解Kotlin协程的时候,断断续续看了网上不少文章,用长篇大论把Kotlin协程描述的非常玄乎,但是看完后还是依然云里雾里,所以决定来写一篇关于协程的文章,希望能够帮助大家能够更快的上手Kotlin协程.

注意:如果没有特殊提及,文中所有“协程”均代表“Kotlin协程”

2为什么要学习Kotlin协程?(官方版)

现在Android技术栈上的新东西层出不穷,kotlin、jetpack、flutter等等。很多人是为了准备面试而学习,所以往往往更偏向于去看一些概念性的东西,以便面试的时候能够蒙混过关。

但是我觉得,我们还是先要了解这个新的技术能够给我们的开发带来哪些实质性的帮助,我们再去针对性学习可能会更加有意义.

我们先来看看Kotlin官网是怎么体现使用协程的优势的

https://www.kotlincn.net/docs/reference/coroutines/basics.html

网上很多文章也用这个例子,也用这个官方例子来说明使用协程的优势,然后就说协程是什么轻量级的线程,又是什么用户态的,协程像线程但又不是线程...诸如此类。

所以很多人自认为学会了协程,最后就可能只能说出来使用协程的目的是比线程性能更好。

先不说这些概念对不对,我相信对于一个普通的Android开发来说,听到这些概念,第一反应肯定觉得协程这个东西非常的神秘且不好理解。

所以,不好理解我们就先不理解,我们先基于我们已有的知识来分析一下官网这个例子。

官网这个例子就是通过repeat函数启动了10000个协程,然后它让我们试一试使用Thread来实现会发生什么,也就是像下面这样:

repeat(100_000){    thread{       Thread.sleep(1000L)       print(""."")    }}

这个例子我们不用跑也知道大概会发生什么了。

但是,我想说的是,kotlin官方用这个例子真的有点不厚道了,用java底层的Thread类,和他们造出来的一个基于Thread类封装的“工具包”进行对比。

真正要比的话,我们用java的Executor和他比比?

repeat(100_000) {    val executor = Executors.newSingleThreadScheduledExecutor()    val task = Runnable {        print(".")    }    repeat(100_00) {        executor.schedule(task, 1, TimeUnit.SECONDS)    }}

<pre style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important;">我用上面这段程序跑了一下,用协程相对于java的线程池,并没有发现什么实质上的性能优势。感兴趣的也可以自己借助Android Studio Profiler试一试
</pre>

所以,到目前为止,我们可以下一个结论。

使用Kotlin协程,本质上其实并没有比我们原先的开发模式有多大性能上的优势,因为我们所使用的的OkHttp、AsyncTask等内部都帮我们封装了线程池,而不是直接使用Thread类。

我上面还提到了一个可能有点争议的观点,Kotlin协程只是一个基于Thread类封装的”工具包“而已。但目前还没有得到证明,我们不妨继续往下看。

3Kotlin协程运行在单线程里面吗?

这里我截了一个百度搜索Kotlin协程,比较高赞的一篇文章,文章中截取了各种概念,到网上找了各种和协程相关的图,但我想说,这其实是误导人!

很简单,我们做一个小实验就能知道结果:

fun main(){    //在没有开启协程前,先打印一下进程名称和进程id    println(        "Main: " +                "threadName = " + Thread.currentThread().name                + " threadId = " + Thread.currentThread().id    )    //循环20次    repeat(20) {        GlobalScope.launch {            //开启协程后,先打印一下进程名称和进程id            println(                "IO: " +                        "threadName = " + Thread.currentThread().name                        + " threadId = " + Thread.currentThread().id            )            delay(1000L)        }    }}

日志打印结果:

发现了什么?所谓的协程完全就是开启了一个新的线程来执行任务,有些任务的线程名称和线程id还是完全一致的!这像不像java中的线程池?

看到这里,是不是有点颠覆你原来对协程的认识?难道网上的所有文章都是错误的?

其实网上大部分文章说的协程,指的可能都是其他语言的协程的特点,比如Go、Lua...

而我们要学的,是Kotlin协程,它不是真正意义上的协程,它也没有那么的神秘,本质上还是一套基于原生Java Thread API 的封装。只要你没有魔改JVM,start了几个线程,操作系统就会创建几个线程,Kotlin协程只是做了一个类似线程池的封装,根本谈不上什么性能更好。

总结下来Kotlin协程其实就是为了让我们更方便的来进行多线程开发而已,所以我们就抱着当初学习Handler、AsyncTask这些的心态来学习协程这个工具包就好了,不要想那么多复杂的东西来扰乱自己的思路。

4Kotlin协程有那么好用吗

一个新的技术的出现,大家往往从学习到真正在项目中实践往往需要一个过程,这里面有非常多的因素,有个人学习成本的因素,有公司方面的因素等等,但是最重要的,其实还是这个新的技术,到底是不是真的有取代我们现有技术的必要。接下来我就带大家一起来用用协程吧。

okhttp异步请求

这是我们常规的一个异步请求,通过回调的方式来处理请求结果

fun enqueue() {

使用协程的okhttp同步请求

这里先记住一句话,我们什么时候要用到协程的?

需要切换线程的时候要用到协程

所以想切到什么线程,就用GlobalScope.launch(Dispatchers.XX)切一下就好了,代码如下

fun execute() {    //切到io线程来执行同步请求    GlobalScope.launch(Dispatchers.IO) {        val result = ApiService.execute("/test")        //切到主线程来执行UI操作        GlobalScope.launch(Dispatchers.Main) {            tv_content.text = result        }    }}

看到这里,有的人可能觉得有点怪怪的,这看起来完全不足以吸引我使用协程,用回调不好吗?

kotlin毕竟是一门比较新的语言,所以在协程中,它同时给我们提供了一些非常实用的函数,所以上面的代码可以写成下面这样:

fun execute() {    GlobalScope.launch(Dispatchers.Main) {        //切到子线程执行任务        var result = withContext(Dispatchers.IO) {            ApiService.execute("/test")        }        //任务执行完后自动回到主线程        tv_content.text = result    }}

这个withContext函数的意义呢,就是能把耗时任务切到子线程去,然后任务执行完之后,又会自动切回主线程。

我们还可以继续优化一下代码,把数据请求和处理抽取到一个方法中,方便调用。

有人可能看到这里多了一个suspend关键字,这个其实就是告诉编译器这里要执行一个异步代码,调用者需要把我切到协程里,用什么切?

就是这个GlobalScope.launch(Dispatchers.Main) 。

不要想太多,就是一个编译检查而已,如果你不使用 GlobalScope.launch来调用suspend修饰的方法就不能编译通过

fun execute() {

到了这里,我相信很多喜欢回调的朋友们心中依然觉得,这依然不足以让我来使用协程。

我们继续往下看。

利用接口回调处理有上下文关联的任务

这时候有一个需求,我们需要先获取用户的token,再通过token查询用户名称

fun enqueue() {    //先获取token    ApiService.enqueue("/login", object : Callback {        override fun onResponse(call: Call, response: Response) {            val token = ...             //通过token请求用户信息            ApiService.enqueue("/getUserInfo", token, object : Callback {                override fun onResponse(call: Call, response: Response) {                    val user = ...                    runOnUiThread {                        //切换到主线程更新信息                        tv_content.text = user.name                    }                }                override fun onFailure(call: Call, e: IOException) {                }            })        }        override fun onFailure(call: Call, e: IOException) {        }    })}

使用协程处理有上下文关联的任务

fun execute() {    GlobalScope.launch(Dispatchers.Main) {        val token = login()        val user = getUsrInfo(token)        tv_content.text = user.name    }}

这时候利用协程的优势就明显了很多。

但是,就是有人深深的爱着回调,而且我知道你心里想的是什么,反正用这种多层回调的场景也不多,应用程序能跑起来不影响性能就好了。

这么说确实也没错,那么我们继续往下看。

前面这种情况,getUserInfo接口是依赖于login接口返回的token的,所以不可避免的使用了回调。

但是现在有一个场景,我们需要将接口A中的接口B中的数据进行合并展示,但是这两个接口在服务端的接口设计上是没有非常强的关联的,这时候出现了两种人。

第一种:想了想觉得没啥办法,然后还是按照先调用接口A,成功后再调用接口B,然后在接口B的回调中进行数据合并

第二种:觉得第一种方式不合理,所以去找服务端“撕逼”,告诉服务端把这两个接口的数据合并到一个接口中返回,客户端处理不了就找服务端呗。这番“撕逼”下来,你有可能成功了,也有可能失败了,但最终的结果都不是非常好。

我们想象一下,假设接口A耗时100ms,接口B耗时120ms,那么实际上在并发处理的情况下,你最快只需要120ms就可以将两个数据进行合并,但是使用回调的方式需要220ms(100ms+120ms)。

之前讲的都只是代码的美观层面的东西,到这里就是性能问题了,各种小的性能问题不解决,一个app怎么可能有比较好的用户体验呢。

我们来看看通过协程可以怎么做:

fun execute() {    GlobalScope.launch(Dispatchers.Main) {            //使用async发起两个异步请求        val one = async { one() }        val two = async { two() }                //使用await进行合并        val result = one.await() + two.await()        tv_content.text = result    }}

看到这里,我们再来看协程这个名字,英文名是Coroutine,中文全称叫做“协同程序”,结合我们前面说的内容,你是否对协程有了新的认识呢?

协程就是协同多个程序之间进行合作,帮助我们轻松的写出复杂的并发代码,甚至还能用非常简单的方式实现原本不可能实现的并发任务。这就是我们为什么要学习协程的理由。

5到底什么是非阻塞式挂起

网上很多文章都提到协程的挂起是非阻塞式的,挂起是什么呢?

就是我们前面说的withContext(Dispatchers.IO)挂起函数,当然还有delay、async,说白了只要不影响我们主线程的工作,那就是被挂起了,这里的”挂起“两个字用的非常玄乎,感觉像挂在我们的主线程,其实按照我上面的分析,更贴切的说法是”切到另一个线程“。

再来分析这个非阻塞式,阻塞很简单,就是字面意思,那既然是阻塞,那总得知道阻塞了什么吧?

在我们Android中,其实就是阻塞了主线程的运行,那反过来非阻塞式其实就是没有卡住主线程的运行.

所以,协程是非阻塞式挂起的是什么意思呢?就是一段程序切到另一个线程执行,不会卡住到主线程。

没错,这听起来就像是一句废话,但是网上很多文章却用一些更高级的词汇来说出来这个废话,让大家去花更多的心思去琢磨。不用想那么多,非阻塞式挂起其实就是这么简单~

6总结

本文只是给你讲了协程是什么,并用一个小例子给你展示了协程的作用,更多API使用方式还是推荐看官方文档哦.

最后做一个总结

  1. 协程(Coroutine)就是协同程序,而Kotlin协程就是一个基于Java Thread API封装的工具包,帮助我们轻松的写出复杂的并发代码

  2. kotlin协程相较于线程池,并没有什么性能上的优势

  3. 非阻塞式挂起没什么特别的,java子线程也同样是非阻塞式的

  4. 如果你熟悉Rxjava,你再对比一下协程,你会发现协程比Rxjava还好用

推荐阅读:2020阿里P7最新Android面试真题解析

Kotlin相关面试题 目录 一.请简述下什么是kotlin?它有什么特性? 二.Kotlin 中注解 @JvmOverloads 的作用? 三.Kotlin中的MutableList与List有什么区别? 四.kotlin实现单例的几种方式? 五. kotlin中关键字data的理解?相对于普通的类有哪些特点? 六.什么是委托属性?简单说一下应用场景? 七.kotlin中with、run、apply、let函数的区别?一般用于什么场景? 八.kotlin中Unit的应用以及和Java中void的区别? 九.Ko 阅读详情

相关推荐

MFC+Modbus-Tcp协议实现温湿度传感器采集

本文简单介绍MFC使用-Tcp通信实现系列MODBUSTCP型温湿度传感器采集数据。

zhigang_zhao的博客 2831

Android面试题——kotlin相关面试题

Jetpack 是一个丰富的组件库,它的组件库按类别分为 4 类1-架构(Architecture)LifecyclesLiveDataNavigationPagingRoomViewModel2- 界面(UI)EmojiFragmentLayoutPalette3-行为(behavior)SharingSlices4-基础(foundation)AppCompatMultidexTest。

我的博客 6055

[Github]GitHub 2FA快速安全配置全攻略

GitHub强制要求用户启用双重身份验证(2FA)以提升代码安全。本文详细介绍了2FA的三种实现方式:TOTP应用验证(推荐)、SMS短信验证(备用)和硬件安全密钥(最安全),并提供了配置指南。特别推荐使用在线工具2FA.run快速测试TOTP功能。文章还涵盖恢复代码管理、企业级2FA策略制定以及常见问题解决方案,如时间同步问题、多设备同步等。通过强制2FA、定期审计和建立安全事件响应流程,开发者可构建更安全的代码管理体系。

冰茶的博客 2620

Android学习总结之kotlin协程面试篇

kotlin协程的面试题讲解

2301_80329517的博客 3977

量子密钥分发系统的工程实践(四):FPGA在后处理中的核心实现与优化

本文深入探讨了FPGA在量子密钥分发(QKD)系统后处理中的核心实现与优化策略。文章详细拆解了FPGA如何高效完成原始密钥提取、误码协商和私密放大等关键任务,并分享了流水线设计、资源复用及存储器架构优化等工程实践“心法”,旨在解决高速、实时QKD系统中的性能瓶颈,提升密钥生成率。

t8u9v0的博客 388

Ubuntu声卡检测与故障排查:从基础命令到驱动修复

本文详细介绍了在Ubuntu系统中进行声卡检测与故障排查的完整流程。从使用lspci、aplay等基础命令识别硬件,到排查驱动冲突、修复权限问题,再到调整PulseAudio高级配置,提供了一套系统性的解决方案。文章结合实战案例,帮助用户快速诊断并解决Ubuntu系统常见的“无声”问题,恢复音频功能。

g2h3i4j5的博客 228

“吹Kotlin协程的,可能吹错了!”带你真正理解一波

网上很多文章都提到协程的挂起是非阻塞式的,挂起是什么呢?就是我们前面说的withContext(Dispatchers.IO)挂起函数,当然还有delay、async,说白了只要不影响我们主线程的工作,那就是被挂起了,这里的”挂起“两个字用的非常玄乎,感觉像挂在我们的主线程,

m0_74931244的博客 1111

uni-app刮奖

uni-app的刮奖效果,要注意的点: 1.小程序的canvas层级问题(可用cover-view规避) 2.不能操作dom 以下是uni-app刮奖效果的代码: <template> <!-- 刮奖 --> <view v-else class="scratch"> <view class="box"> &lt...

qq_40101922的博客 4307

【kotlin 协程】万字协程 一篇完成kotlin 协程进阶

在 Android 平台上,协程主要用来解决两个问题:从本质上来说,协程就是一个轻量级的线程。在使用协程之前,我们需要先引入Coroutine 的包 创建协程的方式有很多种,这里不延伸协程的高级用法(热数据通道Channel、冷数据流Flow.…),创建协程这里介绍常用的三种方式: runBlocking 启动一个协程会阻塞调用它的线程,只到里面的代码执行结束,返回值是泛型T。 launch启动一个协程不会阻塞调用线程,必须要在协程作用域(CoroutineScope)中才能调用,返回值是一个Job asy

半身风雪 5676

Blender点云渲染实战:用Geometry Nodes快速搞定论文配图(附Cycles渲染技巧)

本文详细介绍了如何利用Blender的Geometry Nodes系统,高效地将三维点云数据转化为高质量的科研论文配图。通过实战步骤,从数据导入、节点化几何处理、材质赋予到Cycles渲染引擎的优化设置,手把手教你制作出专业、美观的可视化图像,助力科研成果展示。

hp777的博客 265

ADS1293心电图模块实战:从电极贴片到信号采集的全流程指南

本文提供了一份基于TI ADS1293芯片的心电图采集系统全流程实战指南。文章深入探讨了从电极贴片选择与皮肤预处理、ADS1293前端配置(包括PGA、RLD和威尔逊网络),到PCB布局、电源去耦以及固件滤波与运动伪影处理等关键环节,旨在帮助硬件工程师和嵌入式开发者构建稳定可靠的可穿戴心电监测设备。

weixin_29201313的博客 384

Kotlin协程

1、什么是协程 它其实和线程是有点类似的,可以简单地将它理解成一种轻量级的线程。要知道,我们之前所学习的线程是非常重量级的,它需要依靠操作系统的调度才能实现不同线程之间的切换。而使用协程却可以仅在编程语言的层面就能实现不同协程之间的切换,从而大大提升了并发编程的运行效率。举一个具体点的例子,比如我们有如下foo()和bar()两个方法: fun foo(){ a() b() c() } fun bar(){ x() y() z() } 在没有开启线程的情况

布里啾啾提不哩多的博客 1万+

DeepSeek与豆包无缝对接:5分钟搞定智能周报自动化(附完整代码)

本文详细介绍了如何利用DeepSeek与豆包API,快速搭建一个智能周报自动化系统。通过整合豆包平台的工作流数据,并调用DeepSeek大模型进行深度分析与内容生成,该系统能在5分钟内自动抓取、分析多源信息,并生成结构清晰、富有洞察力的团队周报,显著提升工作效率。

weixin_29029995的博客 493

“吹Kotlin协程的,可能吹错了,2021最新百度、头条等公司Android社招面试题目

Kotlin协程,现在已经成为了面试甚至是工作中一个非常火的东西。 本人在刚开始了解Kotlin协程的时候,断断续续看了网上不少文章,用长篇大论把Kotlin协程描述的非常玄乎,但是看完后还是依然云里雾里,所以决定来写一篇关于协程的文章,希望能够帮助大家能够更快的上手Kotlin协程. 注意:如果没有特殊提及,文中所有“协程”均代表“Kotlin协程” 为什么要学习Kotlin协程?(官方版) 现在Android技术栈上的新东西层出不穷,kotlin、jetpack、flutter等等。很多人是为了准备面试

m0_64383736的博客 2261

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

上一篇: 2020精编面试算法题类型之暴力枚举
下一篇: 深入解析Kotlin 泛型
Python_林一
博客等级 码龄7年 882粉丝 510原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值