Android Kotlin中协程详解

博主前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住也分享一下给大家,

👉点击跳转到教程

前言

Kotlin协程介绍:

Kotlin 协程是 Kotlin 语言中的一种用于处理异步编程的机制。它提供了一种轻量级的线程替代方案,允许你以更简洁和可读的方式编写并发代码。

使用Kotlin协程需要引入Kotlin协程依赖包,这里引入的Kotlin核心依赖包,需要根据当前项目使用的Kotlin版本来引入,我使用的Kotlin版本为1.4.32所以引入的Kotlin核心依赖包版本为:1.4.3

cpp 复制代码
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3'

具体可以去到Maven Repository去查找使用对应的版本。

一、GloabalScope

1、使用GlobalScope构建协程。

cpp 复制代码
		GlobalScope.launch {
            Log.d("协程 当前线程:", Thread.currentThread().name)
        }

输出日志

2、launch中的代码段是执行在子线程中的,如果需要在开启协程的时候指定线程,

可以设置Dispatchers参数值。下面以开启协程并使其在I/O线程中执行为例。

cpp 复制代码
		GlobalScope.launch(Dispatchers.IO) {
            Log.d("Dispatchers.IO 当前线程:", Thread.currentThread().name)
        }

输出日志

3、取消协程 launch()返回一个Job对象,如果协程执行了一个耗时任务,如果耗时任务还未执行完,这是Activity被销毁,这是需要执行job.cancel(),来取消协程。终止后续代码的执行。

cpp 复制代码
		val job = GlobalScope.launch(Dispatchers.IO) {
            Log.d("Dispatchers.IO 当前线程:", Thread.currentThread().name)
        }
        //取消协程应该放在恰当的位置
        job.cancel()

二、CoroutineScope

1、通过CoroutineScope创建协程在实际项目中,用的较为广泛。

cpp 复制代码
		val job = Job()
        CoroutineScope(job).launch {
            //逻辑处理
        }
        job.cancel()

2、async,使用async来获取协程的执行结果。

详解async函数

aysnc函数同样可以构建一个协程作用域,并返回Deferred对象。但是与Coroutine-Scope函数不同的

是,async函数必须在协程作用域中才能调用

cpp 复制代码
   		val job = Job()
        CoroutineScope(job).launch {
            //逻辑处理
            val result = async {
                //模拟耗时操作
                delay(3000)
                "操作成功"
            }.await()
            Log.d(TAG, result)
        }

输出日志,通过delay()函数,让协程延迟3秒执行。

实际使用过程中,可能会出现各种意外的情况,导致发生异常。这里举一个例子。

这里报的异常想必大家都知道 java.lang.ArithmeticException: divide by zero,不能除以0

但是我下面的做法并不能正确的捕获异常依然会报错,导致程序异常退出。

cpp 复制代码
		//错误写法
		val job = Job()
        CoroutineScope(job).launch {
            try {
                //逻辑处理
                val result = async {
                    //模拟耗时操作
                    delay(3000)
                    "操作成功" + 6 / 0
                }.await()
                Log.d(TAG, result)
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }

这是因为在协程作用域外层是无法捕获到协程异常的,这是因为已

经超出了协程作用域的范围,try catch必须包裹ascync函数开启的协程作用域。

cpp 复制代码
		val job = Job()
        CoroutineScope(job).launch {

            //逻辑处理
            val result = async {
                try {
                    //模拟耗时操作
                    delay(3000)
                    "操作成功" + 6 / 0
                } catch (e: Exception) {
                    "结果异常"
                }
            }.await()
            Log.d(TAG, result)
        }

3.await()方法,await()方法会阻塞当前协程(launch启动的外层协程),而不是 async 启动的内部协程,

cpp 复制代码
		/**
           启动 launch 协程。
           在 launch 协程中,首先启动第一个 async 协程,开始耗时操作。
           调用 await() 时,launch 协程会被暂停,直到第一个 async 协程完成。
           之后,继续执行外层 launch 协程,并启动第二个 async 协程。
           调用第二个 async 的 await(),再次暂停外层的 launch 协程,直到第二个 async 协程完成。
           一旦两个 async 协程都完成,launch 协程继续执行剩余逻辑。
         */
        val job = Job()
        CoroutineScope(job).launch {
            val startTime = System.currentTimeMillis()
            //逻辑处理
            val result = async {
                //模拟耗时操作
                delay(3000)
                "操作成功"
            }.await()
            //逻辑处理
            val result2 = async {
                //模拟耗时操作
                delay(3000)
                "获取成功"
            }.await()
            Log.d(TAG, "执行结果:$result - $result2")
            val endTime = System.currentTimeMillis()
            Log.d(TAG, "执行时间: ${endTime - startTime}")
        }

输出结果

针对上述情况,我们可以在用到执行结果的时候调用wait()方法,这样就可以让result和result2同时执行了。

cpp 复制代码
		val job = Job()
        CoroutineScope(job).launch {
            val startTime = System.currentTimeMillis()
            //逻辑处理
            val result = async {
                //模拟耗时操作
                delay(3000)
                "操作成功"
            }
            //逻辑处理
            val result2 = async {
                //模拟耗时操作
                delay(3000)
                "获取成功"
            }
            Log.d(TAG, "执行结果:${result.await()} - ${result2.await()}")
            val endTime = System.currentTimeMillis()
            Log.d(TAG, "执行时间: ${endTime - startTime}")
        }

输出结果:

执行时间节省了3秒左右,因为程序同时调用了result和result2的await方法,这样result和result2相当于并行的关系,在实际项目中常有需要合并不同接口执行结果的需求,这时就 可以采用这种方式来提高运行效率。

三、withContext

1、通过withContext来构建协程作用域,withCotext是一个挂起函数。

首先来讲一下挂起函数。

在CoroutineScope(job).launch开启的协程中,通过会进行IO操作,或者网络请求的操作,为方便阅读通过会抽取到一个方法中,这里我声明了loadData()

cpp 复制代码
		val job = Job()
        CoroutineScope(job).launch {
            loadData()
        }
        
        fun loadData() {
        delay(2000)
        Log.d(TAG, "--loadData--")
   		}

delay(2000)这个函数会报一个错误,Suspend function 'delay' should be called only from a coroutine or another suspend function。意思就是挂起函数delay()应该在协程作用域,或者另一个挂起函数中被调用。因此这里的loadData()函数,必须加上suspend。

那么这里可能就有人要问了,这个挂起函数有什么用呢,在实际项目中,我们可以封装一些网络请求,IO操作等耗时的功能封装在挂起函数,这样别人看到之后也能明白,这些挂起函数是要在协程中进行使用的。

所以suspend在Kotlin协程中起到的仅仅是提醒作用。

2、withCotext函数用法

cpp 复制代码
		val job = Job()
        CoroutineScope(job).launch {
            val result = withContext(Dispatchers.IO) {
            	delay(2000)
                "获取成功"
            }
            Log.d(TAG, "$result")
        }

输出日志

withContext函数同样是一个挂起函数,需要在协程中或者另一个挂起函数中调用,withCotext函数会将最后一行执行结果作为返回值。

与async函数不同的是,withContext函数会强制要求传入一个线程参数,参数值类型有

Dispatchers.Default、Dispatchers.IO、Dispatchers.Main这三种,Dispatchers.Default常用于计算密集

型任务,Dispatchers.IO常用于网络请求、文件读写等操作,Dispatchers.Main则表示程序在主线程

中执行,所以当开启协程的时候,协程作用域中的代码不一定是执行在子线程的,这取决于这个线

程参数的值。

现在我们如何使用协程更优雅的操作UI呢

这里使用CoroutineScope开启Main协程,通过withContext开启I/O协程,当withContext协程作用域代码执行结束时,会继续回到Main协程执行UI的代码逻辑。示例代码如下:

cpp 复制代码
		val job = Job()
        CoroutineScope(job).launch(Dispatchers.Main) {
            val result = getResult()
            showUI(result)
            val result2 = getResult2()
            showUI(result2)
        }

    private suspend fun getResult(): String {
        return withContext(Dispatchers.IO) {
            delay(2000)
            "操作成功"
        }
    }

    private suspend fun getResult2(): String {
        return withContext(Dispatchers.IO) {
            delay(4000)
            "获取成功"
        }
    }

    private fun showUI(result: String) {
        Log.d(TAG, "showUI: ")
        tv_result.text = result
    }

输出日志:

从上述代码中可以看出,即使程序需要多次切换协程,也不需要像使用线程一样层层嵌套,这样就

实现了使用协程更优雅地实现异步任务。

相关推荐
Mr. zhihao6 分钟前
装饰器模式详解:动态扩展对象功能的优雅解决方案
java·开发语言·装饰器模式
zyhomepage7 分钟前
科技的成就(六十四)
开发语言·人工智能·科技·算法·内容运营
Ethan Wilson13 分钟前
C++/QT可用的websocket库
开发语言·c++·websocket
小宇1 小时前
The valid characters are defined in RFC 7230 and RFC 3986
java·开发语言·后端·tomcat
尘浮生1 小时前
Java项目实战II基于Spring Boot的美食烹饪互动平台的设计与实现(开发文档+数据库+源码)
java·开发语言·数据库·spring boot·微信小程序·小程序·美食
杨荧1 小时前
【JAVA毕业设计】基于Vue和SpringBoot的校园美食分享平台
java·开发语言·前端·vue.js·spring boot·java-ee·美食
-seventy-1 小时前
Android 玩机知识储备
android
糊涂君-Q1 小时前
Python小白学习教程从入门到入坑------第十九课 异常模块与包【下】(语法基础)
开发语言·python·学习·程序人生·改行学it
CYRUS STUDIO1 小时前
frida脚本,自动化寻址JNI方法
android·运维·自动化·逆向·移动安全·jni·frida
爱编程的小新☆1 小时前
Java篇图书管理系统
java·开发语言·学习