Kotlin开发 协程

前言

  这篇博客不讲解协程原理,本着快速学习,快速理解,快速使用方式来讲解协程.

kotlin协程是什么? 它其实是类似android的Handler或者java的RxJava. 本质就是为了处理各个线程上的工作协调. 在实际的Android开发最经常的情况就是需要让子线程耗时处理的数据结果发布到主线程上的UI. 所以在需求的本质上就是一个线程+观察者模式的组合. 而观察者模式归根结底就是接口. 所以不管是Handler还是RxJava的实现都需要写大量的模版代码,而kotlin的协程更加简化.

参考 http://www.kotlincn.net/docs/reference/coroutines/coroutines-guide.html

依赖

    //kotlin协程
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9")

简单demo快速了解GlobalScope.launch

请注意,此代码是在Android平台下运行的,所以我不需要让主线程等待了.

    fun demo() {
        GlobalScope.launch {
            delay(1000)//非堵塞线程的延迟一秒钟
            Log.e(TAG, "当前线程id = " + Thread.currentThread().id)
        }
        Log.e(TAG, "主线程id = " + mainLooper.thread.id)
    }

结果

2021-10-15 15:23:38.687 21612-21612/com.example.myapplication E/ytzn: 主线程id = 2
2021-10-15 15:23:39.690 21612-21758/com.example.myapplication E/ytzn: 当前线程id = 3011

指定协程的线程类型

    /**
     * 线程类型
     */
    fun threadType() {
        GlobalScope.launch(Dispatchers.Default) {//默认线程
            Log.e(TAG, "Default当前线程id = " + Thread.currentThread().id)
        }
        GlobalScope.launch(Dispatchers.IO) {//IO线程
            Log.e(TAG, "IO当前线程id = " + Thread.currentThread().id)
        }
        GlobalScope.launch(Dispatchers.Main) {//主线程
            Log.e(TAG, "Main当前线程id = " + Thread.currentThread().id)
        }
        GlobalScope.launch(Dispatchers.Unconfined) {//不限于任何特定线程,就是创建的地方是什么线程,那么内部就是就是什么线程
            Log.e(TAG, "Unconfined当前线程id = " + Thread.currentThread().id)
        }
    }

结果:

2021-10-23 17:59:51.986 29732-29768/com.example.myapplication E/ytzn: Default当前线程id = 951
2021-10-23 17:59:51.987 29732-29769/com.example.myapplication E/ytzn: IO当前线程id = 952
2021-10-23 17:59:51.992 29732-29732/com.example.myapplication E/ytzn: Unconfined当前线程id = 2
2021-10-23 17:59:51.997 29732-29732/com.example.myapplication E/ytzn: Main当前线程id = 2

协程的启动模式

一共有四种

        CoroutineStart.DEFAULT  //默认,会立即启动协程
        CoroutineStart.LAZY     //被动,需要调用start方法才能执行
        CoroutineStart.ATOMIC   //原子性,立即调度执行,并且开始执行前无法被取消,直到执行完毕或者遇到第一个挂起点suspend
        CoroutineStart.UNDISPATCHED //立即在当前线程执行协程体内容

DEFAULT

    fun default() {
        GlobalScope.launch(start = CoroutineStart.DEFAULT) {
            Log.e("ytzn", "执行协程1")
        }

        GlobalScope.launch {
            Log.e("ytzn", "执行协程2")
        }
        //上面2个协程是一样的,在launch不传入启动模式时,默认DEFAULT模式
    }

LAZY

    fun lazy() {
        val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
            Log.e("ytzn", "执行协程")
        }
        job.start() //需要调用start方法才能启动
        job.cancel() //也可以取消
    }

suspend关键字

suspend关键字在协程开发中非常重要,在协程里是无法调用常规函数方法的会出现报错,所以,在协程里调用的函数方法需要增加suspend关键字.

suspend意思挂起,意思是在协程里调用此方法,将会优先执行此suspend函数方法的内部代码,将外部协程线程挂起.处理完成后在执行外部协程.下面的代码将验证这种说法:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btn1.setOnClickListener {
            GlobalScope.launch {
                launch {
                    delay(500)
                    Log.e("ytzn","第二个执行");
                }
                Log.e("ytzn","首先执行,验证不会被launch挂起")
                Log.e("ytzn","第三个执行 获取随机值= " + getRandomValue())
                Log.e("ytzn","最后执行,验证会被suspend函数方法挂起")
            }
        }
    }

    suspend fun getRandomValue(): Int {
        delay(1000)//请注意这里是等待了1秒
        return (1..10).random()
    }

请注意,在getRandomValue函数方法里是写了delay(1000) 等待一秒的,并且delay方法并不堵塞线程.但是外部的最后执行的log,依然是最后执行的.这就验证了suspend方法会挂起外部协程 .

结果:

2021-11-06 16:27:32.881 28729-28789/com.example.myapplication E/ytzn: 首先执行,验证不会被launch挂起
2021-11-06 16:27:33.386 28729-28807/com.example.myapplication E/ytzn: 第二个执行
2021-11-06 16:27:33.889 28729-28790/com.example.myapplication E/ytzn: 第三个执行 获取随机值= 2
2021-11-06 16:27:33.889 28729-28790/com.example.myapplication E/ytzn: 最后执行,验证会被suspend函数方法挂起

GlobalScope.async 带返回值协程

async方法其实与launch方法是一样的,唯一区别是async可以返回值.如果需要async返回值则必须在协程内部

代码:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btn1.setOnClickListener {
            GlobalScope.launch {
                val random = async {
                    return@async (1..10).random()
                }.await() //请注意别忘记了await方法,否则下面的log打印不会等待此返回值
                Log.e("ytzn", "random = $random")
            }
        }
    }

结果:

2021-11-06 17:27:20.125 10056-10125/com.example.myapplication E/ytzn: random = 10

异常抛出

    /**
     * 异常处理
     */
    suspend fun demo() {
        GlobalScope.launch(Dispatchers.Default) {
            throw RuntimeException()
        }
        val job = GlobalScope.async(Dispatchers.Default) {
            throw RuntimeException()
        }
        try {
            job.await()
        } catch (e: RuntimeException) {
        }
    }

协程的取消

在等待状态下的取消

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        btn1.setOnClickListener {
            GlobalScope.launch {
                val job = launch {
                    Log.e("ytzn","A")
                    delay(1000)
                    Log.e("ytzn","B")
                    delay(1000)
                    Log.e("ytzn","C 因为被取消所以不会被打印")
                }
                delay(1500)
                Log.e("ytzn","取消协程")
                job.cancel()
            }
        }
    }

结果:

2021-11-09 21:21:56.459 8982-9270/com.example.myapplication E/ytzn: A
2021-11-09 21:21:57.469 8982-9269/com.example.myapplication E/ytzn: B
2021-11-09 21:21:57.968 8982-9269/com.example.myapplication E/ytzn: 取消协程

堵塞线程runBlocking

runBlocking在Android开发下不太重要,但是在其他开发环境下相当重要,它是堵塞主线程使其存活的重要方法.

调用了 runBlocking 的主线程会一直阻塞直到 runBlocking 内部的协程执行完毕。此方法android开发下一般是不会使用的,因为Android开发最不能做的事情就是让主线程堵塞.但是在下面的代码里是堵塞了Android的主线程,在实际项目里请勿操作

    fun demo1() {
        GlobalScope.launch { // 在后台启动一个新的协程并继续
            delay(1000L)
            Log.e(TAG, "Hello World!")
        }
        val dateFormat = SimpleDateFormat("HH:mm:ss", Locale.getDefault())
        Log.e(TAG, "Time1 = " + dateFormat.format(System.currentTimeMillis())) //主线程中的代码会立即执行
        runBlocking {     // 但是这个表达式阻塞了主线程
            delay(2000L)  // ……堵塞2秒
            Log.e(TAG, "主线程id = " + mainLooper.thread.id)
            Log.e(TAG, "当前线程id = " + Thread.currentThread().id)
            Log.e(TAG, "Time2 = " + dateFormat.format(System.currentTimeMillis()))
        }
        Log.e(TAG, "Time3 = " + dateFormat.format(System.currentTimeMillis()))
    }

结果:

2021-10-18 21:03:37.265 27646-27646/com.example.myapplication E/ytzn: Time1 = 21:03:37
2021-10-18 21:03:38.265 27646-27715/com.example.myapplication E/ytzn: Hello World!
2021-10-18 21:03:39.271 27646-27646/com.example.myapplication E/ytzn: 主线程id = 2
2021-10-18 21:03:39.272 27646-27646/com.example.myapplication E/ytzn: 当前线程id = 2
2021-10-18 21:03:39.274 27646-27646/com.example.myapplication E/ytzn: Time2 = 21:03:39
2021-10-18 21:03:39.277 27646-27646/com.example.myapplication E/ytzn: Time3 = 21:03:39

协程作用域coroutineScope

 除了由不同的构建器提供协程作用域之外,还可以使用 coroutineScope 构建器声明自己的作用域。它会创建一个协程作用域并且在所有已启动子协程执行完毕之前不会结束

    fun demo1() {
        GlobalScope.launch { // 在后台启动一个新的协程并继续
            launch {
                delay(1000)
                Log.e(TAG,"最后执行,因为等待的时间最长")
            }
            Log.e(TAG,"首先执行")
            coroutineScope { // 创建一个协程作用域
                launch {
                    delay(500)
                    Log.e(TAG,"第三个执行")
                }
                Log.e(TAG,"第二个执行")
            }
            Log.e(TAG,"第四个执行,需要等待coroutineScope协程作用域结束后执行")

        }
    }

结果:

2021-10-20 14:17:26.837 7519-7553/com.example.myapplication E/ytzn: 首先执行
2021-10-20 14:17:26.838 7519-7553/com.example.myapplication E/ytzn: 第二个执行
2021-10-20 14:17:27.340 7519-7554/com.example.myapplication E/ytzn: 第三个执行
2021-10-20 14:17:27.341 7519-7554/com.example.myapplication E/ytzn: 第四个执行,需要等待coroutineScope协程作用域结束后执行
2021-10-20 14:17:27.839 7519-7553/com.example.myapplication E/ytzn: 最后执行,因为等待的时间最长

End

原文地址:https://www.cnblogs.com/guanxinjing/p/15411291.html