饿了么资深Android工程师带你领略Kotlin协程的力量

内容来源:2018 年 6 月 28 日,饿了么资深Android工程师张涛在“droidcon上海2018安卓技术大会”进行《领略kotlin协程的力量》演讲分享。IT 大咖说(微信id:itdakashuo)作为独家视频合作方,经主办方和讲者审阅授权发布。

阅读字数:3232 | 9分钟阅读

摘要

协程相对多线程有着更易于控制的优势,很多语言都提供了协程的能力,kotlin也不例外。本次分享将通过一个小案例展示协程在kotlin中是如何应用的,以及如何在现有项目中引入协程。

获取嘉宾演讲视频及PPT,扫一扫下方二维码即可。

协程是什么

进程

早期的计算机运行程序还是只能一次运行一个任务,之后进程的出现实现了近似同步的执行效果,其本质上是程序的交替执行。为了保证进程中的程序能够正常执行,还会有一些存储进程状态的保存集。随着硬件的发展和多CPU的出现,能够同时执行的进程数量逐渐增多。这就带来了一个问题,即用来存储进程状态的集合所占用的资源比一个进程可以执行的资源还要多,相当于整个系统大半的进行都是用来保存进程的状态。

线程

线程的提出有效的解决了这个问题。进程不再频繁的切换,而是先执行,遇到阻塞的话暂时不管,继续执行其他的任务,当其他任务执行完之后再回过头来看阻塞任务是否执行完。多线程的缺陷在于无法自主控制调度,除开一定会执行的主线程之外,其他线程的执行顺序都无法控制,在Java上是由Java虚拟机调度,其他平台大多是由系统控制。

协程

线程执行过程中发生线程切换的时候会损耗一定的资源,这部分资源用来保存线程的状态。执行过程中如果发生了磁盘读写或网络请求这样的IO操作的时候线程的执行会被阻塞,但同时该线程还会持有CPU资源,这就造成了一定了资源浪费。理想的情况是在发送阻塞的时候,该线程主动交出CPU给其他线程使用或者给内部的其他任务。

这种方式其实就是协程的体系。通过提升CPU利用率,减少线程切换,进而提升程序运行效率。

延伸开来协程主要有三个特性。第一个是可控制,不同于线程协程能做到可被控制的发起子任务;第二个是轻量级,协程非常小、占用资源比线程还少,在JVM平台上它的本质就是一次方法的调用;第三个是语法糖,目前能够使用协程的语言都提供了很好的语法糖支持,使多任务或多线程切换不在使用回调语法。

通过Kotlin在JVM平台使用协程

示例:第三方登录

第三登录在应用开发中可以算是一个很常见的场景,具体的逻辑是这样的,首先向第三方平台请求用户token,然后将token和自身平台上的用户账号关联起来,最后获取用户信息展示到UI界面上。

对此最常见的做法是采用回调的形式。requestToken会先发出一次网络请求,请求返回后执行回调并传入token,回调内部又会用token作为参数向我们自己的服务器发起请求获得到用户信息,最终完成用户信息在UI上的改变。这种方式的问题在于嵌套层级过多,链路一旦过长看起来会非常复杂。

协程改写

要改变这种现状,自然就要用到协程,上图是用协程对前面示例的改写。在Kotlin中如果函数的函数体内只有一个语句,那么就可以将这条语句直接赋值给函数声明。另外如果方法只有一个参数且该参数为lambda表达式的时候,可以将函数后小括号省略掉。

在Kotlin中常用的启动协程的方式有三种。第一种是上图中的runBlocking,它只会用在协程和线程的交接点,也就是通常只用于启动最外层协程。第二种是launch,用于在协程内部再启动一个协程。第三种是async/await,它不仅可以启动协程,还可以得到执行的结果。

这是前面示例中细分的两个函数调用。因为前两个方式都是耗时操作,所以要放在子线程中运行。但是在安卓中子线程无法做UI改变的操作,因此改变UI的时候还是要切换到主线程。setText方法的launch中有一个UI参数,这是Kotlin的协程提供的对象,表示在UI线程中启动协程,同时协程被中断以后的恢复也是在UI线程中。

在requestToken函数内部中的return@async标识用来表示返回的是async这个闭包的内部逻辑。Async的CommonPool参数是Kotlin提供的线程池的单例对象,有了这个参数后就可以从线程池中随机的取一个子线程,然后运行闭包中的逻辑。当网络请求操作执行完之后,await函数会将请求结果直接返回给requestToken。

协程的本质

一般直接将一个耗时方法写入在代码中其实是有问题的,轻则会UI卡顿,严重的话还会造成程序无响应。因此Kotlin协程库提供了一个关键字suspend,表示挂起指出该方法是一个协程方法不是直接运行在UI线程中。Suspend修饰的函数(或lambda)只能被suspend修饰的函数(或lambda)调用。

图中被suspend修饰的requestToken函数在被编译之后会变成下方这种形式。这种语法其实在Java中经常会用到,它返回一个Object,不过多另一个Continuation类型的泛型参数,表示协程方法requestToken最终会返回一个string的值。

Continuation是协程在代码上的映射,它本质上是个接口,Kotlin中每个协程的协程体都实现了这个接口。仔细看下该接口内部的代码就会发现这就是一个回调接口。协程的本质也就是一次回调,只不过通过语法糖的形式让它看起来像是顺序执行。

协程切换

上图中每个大括号所包含的范围是协程的执行过程。发生协程切换的时候会有一个suspend Point点,通常被称作协程的挂起点,比如从协程0到协程1发生一次协程切换的时候,协程0会被挂起,协程1得以执行。

前面第三方登录的协程方法在被编译完之后会在代码中出现上图所示的方法doResume。它有一个any参数,该参数类似于Java中Object。Kotlin中所有类都会有一个直接或间接的父类指向any,这里的any其实就是协程对象。

当前类继承自CoroutineImpl,CoroutineImpl是Continuation的实现类。方法内部的this指向doResume传入的参数,第一个协程启动的时候this.label默认为0,requestToken方法被调用同时label值被改为1,requestToken执行完之后会通过传入的this参数继续调用doResume方法。这时的label值已经变为了1,所以会执行协程的第二段操作,通过这样的一系列执行就完成了整个协程的切换。

方案:SPP+PHP

Kotlin提供了一个协程扩展库,可以直接返回Call类型的对象。这个对象的扩展形式就是图中展示的,可以看到它被静态的添加了个await扩展方法,这个await就是一个协程方法和之前所提到的await并无差别。

上图的代码中当网络请求被执行完之后会得到一个Call对象,通过调用它的await方法就能够获取到请求的返回值。

这是扩展方法的具体实现,整个函数只有一个函数体,内部启动了一个协程。Enqueue表示将请求加入到请求队列中,请求成功后会通过异步回调拿到执行结果。这里回调的时候又进一步调用了协程接口continuation的resume方法和resumeWithException方法。拿到这两个回调方法之后,编译器在编译的时候会直接在对应的位置触发接下来的代码。

以上为今天的分享内容,谢谢大家!

IT大咖说 | 关于版权

本文由“IT大咖说(ID:itdakashuo)”原创,转载时请注明作者、出处及微信公众号。投稿、约稿、转载请加微信:ITDKS10(备注:投稿),茉莉小姐姐会及时与您联系!

感谢您对IT大咖说的热心支持!

原文发布于微信公众号 - IT大咖说(itdakashuo)

原文发表时间:2018-08-15

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏ytkah

dedecms自增标签[field:global.autoindex/]的运用

  用bootstrap建站时用到幻灯片切换模块,里面有个active(下面代码中的data-slide-to="0"),其余的按顺序递增(1,2),如果用de...

3314
来自专栏漫漫深度学习路

tensorflow学习笔记(四十一):control dependencies

tf.control_dependencies()设计是用来控制计算流图的,给图中的某些计算指定顺序。比如:我们想要获取参数更新后的值,那么我们可以这么组织我们...

5649
来自专栏玩转JavaEE

ElementUI中tree控件踩坑记

vhr部门管理模块更新啦!为了让小伙伴们快速理解部门管理模块实现思路,我想通过3篇短文来给大家介绍下大致的实现思路和核心代码。 项目地址:https://git...

4806
来自专栏流媒体人生

Yate开发向导

Yate 的设计是为了提供一个可扩展性的电话引擎,试图以最简简洁的代码,在扩展所需功能与性能、稳定性之间达到最佳平衡。

1013
来自专栏程序员的知识天地

维护Python代码的5种工具

随着软件项目进入“维护模式”,对可读性和编码标准的要求很容易落空(甚至从一开始就没有建立过那些标准)。然而,在代码库中保持一致的代码风格和测试标准能够显著减轻维...

2502
来自专栏海说

6、Java包的命名与划分

包的命名与划分 (一)使用Java包的目的 在了解做一件事之前,需要了解做这件事的目的。而使用Java包的目的大概如下: 1    对类进行归类,便于开发查找。...

3040
来自专栏信安之路

通过POC来学习漏洞的原理

本文介绍的是 easyFTPServer 1.7.0.2 ‘Http’ remote Buffer Overflow 的漏洞执行流程,通过已知的 POC 来推断...

1500
来自专栏技术小黑屋

一个Android代码JIT友好度检测工具

利用周末的时间,写了一个检测Android代码JIT友好度的工具,取个名字为DroidJitChecker。希望可以帮助大家快速发现有坏味道的代码,并且及时修正...

1124
来自专栏Ryan Miao

Git 工作流的正确打开方式

前言 一直在使用git做版本控制,也一直工作很顺利,直到和别人发生冲突的时候。这才注意到git 工作流并不是那么简单。比如,之前遇到的清理历史。百度到的资料很...

3006
来自专栏为数不多的Android技巧

Android Studio你不知道的调试技巧

写代码不可避免有Bug,通常情况下除了日志最直接的调试手段就是debug;那么你的调试技术停留在哪一阶段呢?仅仅是下个断点单步执行吗?或者你知道 Evaluat...

771

扫码关注云+社区

领取腾讯云代金券