秒懂Kotlin之小白都看的懂的协程教程(part1)

前言

使用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一个协程代码块

    例如:launchasync

  • 协程dispatcher
    它负责具体执行线程。使用协程builder构建了协程后,总的有人去执行啊,这个任务就是由dispatcher完成的,它会调度线程来运行协程代码。

    例如:Dispatchers.DefaultDispatchers.IODispatchers.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动画。

秒懂Kotlin之小白都看的懂的协程教程(part1)

第一步:引入协程相关的库

    //协程相关
    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项目一切都给你整的明明白白的…

上一篇:深入浅出协程、线程和并发问题


下一篇:[20191113]oracle共享连接模式端口2.txt