写点什么

“吹 Kotlin 协程的,可能吹错了,谈谈我认为的高级 Android 开发到底应该是怎样的

用户头像
Android架构
关注
发布于: 刚刚


目录

前言

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 类封装的”工具包“而已。但目前还没有得到证明,我们不妨继续往下看。


_3_Kotlin 协程运行在单线程里面吗?




这里我截了一个百度搜索 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 这些的心态来学习协程这个工具包就好了,不要想那么多复杂的东西来扰乱自己的思路。


_4_Kotlin 协程有那么好用吗




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


okhttp 异步请求


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


fun enqueue() {


使用协程的 okhttp 同步请求


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


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


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


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


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() {


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

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
“吹Kotlin协程的,可能吹错了,谈谈我认为的高级Android开发到底应该是怎样的