秒懂Kotlin之小白都看的懂的协程教程(part1) 超、凢脫俗 2021-06-10 20:40 219阅读 0赞 # 前言 # 使用kotlin有一段时间了,但在自己入门协程的时苦于小白一看就懂的资料太少,入门很艰难。所以俺就暗暗下决心一定要以一贯的秒懂作风填补上国内这个空白,一来可以帮助我们可爱的猿猿们,二来也可以加深自己对协程的理解。至于更加高级的用法,那就是入门后的修炼了,能练到第几层全凭天赋和努力了… 阅读本文后你会有如下收获: * 对协程有一个清楚的理解,认识可能不太深刻,但是你会非常清楚,至于深度就需要靠你后天发育了。 * 清楚协程用来解决什么问题 * 掌握kotlin协程的入门用法 * 增强你的求学信心 # 简介 # 要学习协程,首先应该明白它是用来解决什么问题的?其次要明白它到底是什么?最后才是基于某种语言的实现和使用方法。再次强调一下,知道使用某个知识来解决某个问题,比掌握了这个知识而不知道其有什么用更加重要! ## 协程解决什么问题 ## 简单来说:协程主要用来解决异步编程问题。 **异步编程**是一个非常大的话题,简单来说就是**不阻塞程序**。大白话就是**别卡**,我们现在干什么都怕卡:打游戏怕卡、听音乐怕卡、看电影怕卡、秒杀商品怕卡…。从上古时期程序员鼻祖就已经在和它缠斗了,经过几代程序员的努力发展出了几个对付它的大招: * Threading 多线程 * Callbacks 回调 * Futures, Promises 这两个一般不翻译 * Reactive Extensions Rx系列, 例如rxjava * Coroutines 协程 以上方法的详情可以参考[Asynchronous Programming Techniques][] 有的同学产生了好奇:当我手机网络不好时,我的微信页面上一直在转菊花,这是不是由于腾讯码农太菜没有使用异步编程导致的呢?腾讯码农:纳尼?屏幕上不是有个菊花在转嘛,哪里卡了?难不成还的给你放一段提前下载好的日本小电影?你还说你喜欢欧美的?就一菊花,爱看不看… ## 什么是协程 ## 先上个比较学术的定义吧,如果只是为了应用而理解的话,我觉得还是后面白话解释更适合: > 协程就像非常轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程 Coroutines = Co + Routines , Co是Cooperation,即协作;Routines 的意思是Functions,即方法。连起来就是多个方法互相协作来完成一个任务,这些方法可以被暂停(suspend)也可以被恢复(resume)。协程其是以线程为基础的,可以看做是一个线程的**管理框架**。 协程最初被提出是在20世纪60年代,我的妈呀可真够久远的,可以说是IT上古时期的产物了,此时就连大名鼎鼎的**C**语言还没出生呢。当时协程之所有市场主要是因为计算机操作系统调度算法不成熟,没有发展出抢占式调度算法,而主要使用协作式算法,再加上不支持多线程的缺陷,使得协程找到了自己的位置。计算机一次只能干一件事,假设你现在要一边听音乐(TaskA)一边敲代码(TaskB),那最好的做法就是执行一会A,然后让出执行时间给B,执行一会B再把CPU时间让给A。那要是A突然有一天发疯不配合,老子今天就是不配合,就是不让出执行时间,那么B任务肯定被阻塞了,就是卡了。。。 所以A和B就好比两个**Routines**,需要**Cooperation** 来共同完成一个任务,简称**Coroutines**。后来抢占式算法加上多线程的出现,协程就被打入冷宫了,这一打就是50多年啊,最近几年由于多线程高并发中存在的一些问题,例如线程太重了,而且很多时候还不够用,它又换发了第二春。 ### 实例 ### 让我们举个程序上的例子吧,毕竟你是程序猿,代码才是你最好的语言 有个方法`getAndShowName()`作用为:调用网络Api然后将内容打印出出来,如下所示 suspend fun getAndShowName(){ val name= requestApi() changeUi(name) } 调用 getAndShowName() doOtherTask() 因为网络请求`requestApi()`是耗时操作,如果同步执行的话程序会被阻塞,因为`changeUi(name)`要等`requestApi()`的结果返回才能继续执行,`changeUi(name)`等结果是正常的,因为有依赖,但是`getAndShowName()`下面的`doOtherTask()`不依赖`getAndShowName()`结果,所以不应该被阻塞。 协程是这么干的:当执行到`requestApi()`时,发现是耗时方法,就把包含它的`getAndShowName()`方法suspend,让线程去执行其后面的内容,当网络请求完成后再resume方法`getAndShowName()`继续执行其里面的 `changeUi()`方法,给人的感觉就是代码就这么顺序执行下来了… ## 优势 ## * 可以以同步的方式写异步代码 * 轻量级,在服务器端表现的非常抢眼,但是在客户端,例如Android,中意义一般 * 编程模型和API和普通方法一样。 这一点我非常喜欢,不用学习新的东西,却掌握了一种新的技能,想想你入门RxJava时候那个痛苦,大部分人现在也没有完全掌握RxJava的那些API. # Kotlin中的协程 # 在理解了上面的内容后,就是Kotlin协程登场的时候了: ## 基本概念 ## 要想上手Kotlin协程必须清楚几个概念 * **协程scope** 协程代码都必须包含在一个范围内,就好比说:只有在这个圈里可以使用协程。这个scope用于跟踪管理其内部的协程。 例如:协程标准库中的`GlobalScope` 、Android的`viewModelScope` * **协程builder** 其协程代码与非协程代码的桥梁。其负责在非协程代码里启动协程,即在协程scope里build一个协程代码块 例如:`launch`与`async` * **协程dispatcher** 它负责具体执行线程。使用协程builder构建了协程后,总的有人去执行啊,这个任务就是由dispatcher完成的,它会调度线程来运行协程代码。 例如:`Dispatchers.Default`、`Dispatchers.IO`,`Dispatchers.Main` 清楚以上3个概念就可以轻松上手协程写代码了,一段协程代码,以上3个角色缺一不可。 ## 协程初体验 ## 我第一次写协程代码的的时的切身体验是这样的:老子要写协程了…咦,我日,怎么写呢?就是完全不知道怎么下笔那种感觉,看了一堆别人的博客,还是迷迷糊糊… 所以我现在要分步骤教你如何下笔,等你真的入门了,后天发育全看你的操作骚不骚了… 其实写一个协程就简单的**3**步: **第一步**:找一个协程scope,自己定义(实现`CoroutineScope`接口)或者使用库中已经提供的。 我这里使用Android jetpack中的`viewModelScope` **第二步**:选择一个协程builder 我这里选择`launch` **第三步**:选择一个协程dispatcher 我这里选择`Dispatchers.IO` **在scope实例上调用builder,将dispatcher作为builder的参数即可** viewModelScope.launch(Dispatchers.IO) { //这里可以调用suspend函数,也可以调用普通函数 val name= requestApi() changeUi(name) } 就这样我就构建并启动了一个协程,我们可以在里面执行协程方法了,就是那些使用`suspend`标记的普通方法。 //耗时网络请求 suspend fun requestApi():String{ delay(2_000) return "ShuSheng007" } fun changeUi(name:String){ println("欢迎:$name") } 难吗?so easy 有没有?让我们按照上面的步骤在Android中演示一下: ## 协程在Android中的实战 ## 我们在android中使用kotlin协程实现一个获取网络数据并展示的一个小Demo。 先看一下效果图,从网络上获取信息并展示,同时不阻塞UI动画。 ![在这里插入图片描述][20201024173622252.gif_pic_center] 第一步:引入协程相关的库 //协程相关 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9" //viewModel implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" 第二步:在ViewModel中编写网络请求 class CoroutinesViewModel : ViewModel() { //用于更新UI val nameLiveData = MutableLiveData<String>() //Main-safe, 意味着可以直接从UI线程启动 fun checkTheWomen() { // Dispatchers.Main可以省略 viewModelScope.launch(Dispatchers.Main) { //这块指定网络请求使用IO线程 val name = withContext(Dispatchers.IO) { searchFromNet() } nameLiveData.value = name } } //耗时网络请求 private suspend fun searchFromNet(): String { Log.d("coroutine","searchFromNet: ${Thread.currentThread().name}") delay(3_000) return "ShuSheng007媳妇" } } 第三步:在Activity中发起请求 class CoroutinesActivity : AppCompatActivity() { private lateinit var viewModel: CoroutinesViewModel private lateinit var tvWomanName: TextView private lateinit var btnSearch: Button override fun onCreate(savedInstanceState: Bundle?) { ... viewModel = ViewModelProvider(this).get(CoroutinesViewModel::class.java) ... viewModel.nameLiveData.observe(this) { //当网络请求结果回来后更新UI tvWomanName.text = it ... } btnSearch.setOnClickListener { //从UI线程发起网络请求 viewModel.checkTheWomen() ... //播放图片动画,证明UI没有被阻塞 animateImage(findViewById<ImageView>(R.id.img_my_wife)) } } fun animateImage(img: ImageView) { ... } } 让我复盘一下这个简单的程序是如何执行的,因为这真的太重要了,理解了这块,后期发展就会顺利很多。 1. 点击查询按钮,在UI线程调用`checkTheWomen()` 并发起UI动画,如果此方法是同步的话,UI动画会被阻塞 2. `checkTheWomen()`启动协程,并在IO线程上调用方法`searchFromNet()`,由于其是是`suspend`的,随即在UI线程上被挂起,UI线程继续去执行动画 3. 在UI线程上挂起的方法`searchFromNet()`找了一条IO线程执行网络请求,执行完后带着结果被在UI线程上恢复执行 4. 于是在UI线程上设置LiveData,通知到Activity去更新那个名字 可以看到在Android中使用协程非常的简单舒服,因为Android主推kotlin,主推协程,通过Jetpack项目一切都给你整的明明白白的… [Asynchronous Programming Techniques]: https://kotlinlang.org/docs/tutorials/coroutines/async-programming.html [20201024173622252.gif_pic_center]: /images/20210527/773e37cf389449a48f4b1a32befdbe20.png
还没有评论,来说两句吧...