一文学会 使用Kotlin Coroutine协程

简介: 关于线程、协程两者的对比,可以简要叙述如下:线程:线程由操作系统调度,线程切换或线程阻塞由操作系统和CPU调度实现;协程:协程运行于操作系统的用户态,其依赖于线程来实现,通过用户态程序控制,尽量`减少或避免因线程阻塞造成的操作系统与CPU开销`。与线程相比不同点在于,`协程挂起时不需要阻塞其运行的线程`。`协成挂起`期间,其对应的`线程可以被分配其他协程任务来执行`,待`该协程任务挂起结束再次开始时,将该协成再次交由某个线程来继续执行`(挂起期间,`类似于将该协程任务添加到了某个任务队列中`)

协程的概念最早由Melvin Conway在1963年提出并实现,用于简化COBOL编译器的词法和句法分析器间的协作,当时其对协程的描述是“行为与主程序相似的子线程”。

协程可看作轻量级的线程,目前 PythongoKotlin 等开发语言均已支持协程,Java 可以通过第三方扩展的方式来使用协程。

关于线程、协程两者的对比,可以简要叙述如下:

  • 线程:

线程由操作系统调度,线程切换或线程阻塞由操作系统和CPU调度实现;

  • 协程:

协程运行于操作系统的用户态,其依赖于线程来实现,通过用户态程序控制,尽量减少或避免因线程阻塞造成的操作系统与CPU开销
与线程相比不同点在于,协程挂起时不需要阻塞其运行的线程协成挂起期间,其对应的线程可以被分配其他协程任务来执行,待该协程任务挂起结束再次开始时,将该协成再次交由某个线程来继续执行(挂起期间,类似于将该协程任务添加到了某个任务队列中)。

目前 Kotlin协程 在GitHub上的最新release版本为1.6.0,其对应的 GitHub源码地址为:https://github.com/Kotlin/kotlinx.coroutines

  • GlobalScope 使用简述
  • CoroutineScope 使用简述

一、GlobalScope 使用简述

GlobalScope 继承于 CoroutineScope (接口),其源码实现是一个全局的单例,因为是单例,其生命周期跟随与整个应用程序的生命周期;可使用 GlobalScope.launch 启动一个顶层协程。

  • GlobalScope 使用举例
  • GlobalScope 简要说明

1.1 GlobalScope 使用举例

引入依赖包:

首先需要引入 Kotlin协程 相关依赖库
目前 Kotlin协程 在GitHub上的最新release版本为1.6.0,其对应的 GitHub源码地址为:https://github.com/Kotlin/kotlinx.coroutines

// 协程核心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0"
// 协程Android库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0"

线程切换:

GlobalScope.launch 执行任务时,切换不同线程

// ------------应用举例-----------  
// GlobalScope 执行任务时,切换不同线程
//
// 1、启动一个协程 (主线程 执行)  
var job = GlobalScope.launch(context = Dispatchers.Main) {  
    // TODO Main线程
}

// 2、启动一个协程 (异步线程:线程数量默认为64)
var job = GlobalScope.launch(context = Dispatchers.IO) {
    // TODO 异步线程:线程数量默认为64
}

// 3、启动一个协程 (异步线程:线程的最大数量等于 CPU 内核数)  
var job = GlobalScope.launch(context = Dispatchers.Default) {  
    // TODO 异步线程:线程的最大数量等于 CPU 内核数
}

// 4、启动一个协程 (当前线程 执行)  
var job = GlobalScope.launch(context = Dispatchers.Unconfined) {  
    // TODO 当前线程执行
}

// 取消协程
// job.cancel()

GlobalScope.launch使用:

GlobalScope.launch 执行异步网络任务,返回结果更新UI界面。以下举例中,涉及到以下关键词或方法:

  • suspend 关键词:

当携程出现阻塞等待情况时,用于挂起当前的协程,并保存所有局部变量。

  • withContext 方法:

将当前协程 移至一个I/O线程中执行异步操作。

// ------------应用举例-----------  
// GlobalScope 执行异步网络任务,返回结果更新UI界面
//
class GlobalScopeActivity : AppCompatActivity() {
    //
    lateinit var job: Job;
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // GlobalScope.launch 使用  
         launchGlobalScope();  
    }  
  
    /**  
      * GlobalScope.launch 使用 
     */
    private fun launchGlobalScope() {  
        // GlobalScope.launch 使用
        job = GlobalScope.launch(Dispatchers.Main) {
            // TODO 执行主线程任务                 // main thread
            // getNetData 异步获取网络数据
            val netStr: String = getNetData()   // IO thread
            // 回到主线程
            textView?.text = netStr           // main thread
        }
    }
    
    /**
     * 异步获取网络数据
     * suspend 关键词,当携程出现阻塞等待情况时,用于挂起当前的协程,并保存所有局部变量
     */
    private suspend fun getNetData(): String {
        // withContext 将当前协程 移至一个I/O线程中执行异步操作
        return withContext(context = Dispatchers.IO) {
            // TODO IO线程 网络请求
            // 返回值为String的Http同步网络请求
            HttpAgent.get_Sync(
                "https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=18701636688", null, null,
                String::class.java
            )
        }
    }
}

GlobalScope.async使用:

GlobalScope.async 执行异步网络任务,返回结果更新UI界面。以下举例中,涉及到以下关键词和方法:

  • async 方法:

async 方法会启动一个新的协程;

  • await 关键词:

async 方法启动的协程,可以使用一个名为 await 的关键词,等待耗时方法返回执行结果。

// ------------应用举例-----------  
// GlobalScope.async 执行异步网络任务,返回结果更新UI界面
//
class GlobalScopeActivity : AppCompatActivity() {
    //
    lateinit var job: Job;
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // GlobalScope.async 使用
        asyncGlobalScope()
    }

    // ---------------------GlobalScope.async-----------------------
    /**
     * GlobalScope.async 使用:
     * async 方法会启动一个新的协程,并使用一个名为 await 的关键词,等待耗时方法执行结束的返回结果。
     */
    private fun asyncGlobalScope() {
        // GlobalScope.async 使用
        job = GlobalScope.launch(Dispatchers.Main) {
            // TODO 执行主线程任务                 // main thread
            // 第一个异步网络请求
            val taobaoData = async(Dispatchers.IO) { // IO thread
                // TODO IO线程 网络请求
                // 返回值为String的Http同步网络请求
                HttpAgent.get_Sync(
                    "https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=18701636688", null, null,
                    String::class.java
                )
            }
            // 第二个异步网络请求
            val baiduData = async(Dispatchers.IO) { // IO thread
                // TODO IO线程 网络请求
                // 返回值为String的Http同步网络请求
                HttpAgent.get_Sync(
                    "https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=18701636688", null, null,
                    String::class.java
                )
            }
            // 待两个结果都返回后
            val resultData: String = (taobaoData.await() + baiduData.await())

            // 展示UI
            textView?.text = resultData           // main thread
        }
    }

}

1.2 GlobalScope 简要说明

不建议直接使用 GlobalScope ?

GlobalScope 的源码实现是一个全局的单例( Kotlin 中单例对象通过 object 关键字实现),其对应的源码如下:

// GlobalScope 源码:GlobalScope为单例
public object GlobalScope : CoroutineScope {  
    override val coroutineContext: CoroutineContext  
        get() = EmptyCoroutineContext  
}

因为GlobalScope是一个单例,又因为 GlobalScope 对象并未与Android应用生命周期组件相关联,因此需要自己管理 GlobalScope 所创建的 Coroutine。
否则通过 GlobalScope 启动的协程,其生命周期将与整个Android应用程序的生命周期相同,只要整个应用程序还在运行且协程的任务还未结束,协程就可以一直运行。
因此,一般不建议直接使用 GlobalScope 来创建 Coroutine协程。

协程不会阻塞线程?

文章一开始说过:
协成挂起期间,其对应的线程可以被分配其他协程任务来执行,待该协程任务挂起结束再次开始时,将该协成再次交由某个线程来继续执行(挂起期间,类似于将该协程任务添加到了某个任务队列中)。

这里写一段代码来验证一下:

// 举例 启动一个协程 (IO异步线程)
GlobalScope.launch(context = Dispatchers.IO) {
    // 启动协程
    Log.d(TAG, "[GlobalScope] start ")
    // 挂起 2 秒钟
    delay(2000)
    // 继续协程
    Log.d(TAG, "[GlobalScope] currThread: " + Thread.currentThread().name)

    // 启动协程
    launch {
        Log.d(TAG, "[launch A] Begin")
        delay(400)
        Log.d(TAG, "[launch A] currThread: " + Thread.currentThread().name)
        Log.d(TAG, "[launch A] end")
    }
    // 启动协程
    launch {
        Log.d(TAG, "[launch B] Begin")
        delay(300)
        Log.d(TAG, "[launch B] currThread: " + Thread.currentThread().name)
        Log.d(TAG, "[launch B] end")
    }
    // 结束协程
    Log.d(TAG, "[GlobalScope] end ")
}

// 运行结果如下:
// [GlobalScope] start 
// [GlobalScope] currThread: DefaultDispatcher-worker-1
// [launch A] Begin
// [launch B] Begin
// [Coroutine] end 
// [launch B] currThread: DefaultDispatcher-worker-1
// [launch B] end
// [launch A] currThread: DefaultDispatcher-worker-1
// [launch A] end

通过以上运行结果,可以看出 launch A被挂起时,其对应的线程 DefaultDispatcher-worker-1 开始执行 launch B 相关任务。delay()方法并未阻塞其对应的执行线程。

二、CoroutineScope 使用简述

前边说过 不建议直接使用 GlobalScope,GlobalScope是一个单例,其生命周期与Android应用生命周期相同,而且并未与Android生命周期组件(Activity、Service等进行关联),其声明周期需要研发人员自己管理
之前并未提及建议的协程使用方式,这一节给出对应的代码使用方式举例:

CoroutineScope 使用举例:

通过 CoroutineScope 来实现一个自己的协程作用域,通过launch启动一个协程,通过调用 scope.cancel() 方法,可以取消该 scope 下所有正在进行的任务。

// ------------应用举例-----------  
// CoroutineScope 执行异步网络任务,返回结果更新UI界面
//
class CoroutineScope01Activity : AppCompatActivity() {
    // Job 对象
    lateinit var scope: CoroutineScope

    // Activity的onCreate方法
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // 创建 CoroutineScope (用于管理CoroutineScope中的所有携程)
        scope = CoroutineScope(Job() + Dispatchers.Main)

        // 获取网络数据,更新UI
        asyncCoroutine();
    }

    override fun onDestroy() {
        super.onDestroy()
        // 当 Activity 销毁的时候取消该 Scope 管理的所有协程。
        scope.cancel()

    }

    /**
     * CoroutineScope 使用
     */
    private fun asyncCoroutine() {
        // CoroutineScope 的 launch 方法
        scope.launch(Dispatchers.Main) {
            // TODO 执行主线程任务                 // main thread
            // 第一个异步网络请求
            val taobaoData = async(Dispatchers.IO) { // IO thread
                // TODO IO线程 网络请求
                // 返回值为String的Http同步网络请求
                HttpAgent.get_Sync(
                    "https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=18701636688", null, null,
                    String::class.java
                )
            }
            // 第二个异步网络请求
            val baiduData = async(Dispatchers.IO) { // IO thread
                // TODO IO线程 网络请求
                // 返回值为String的Http同步网络请求
                HttpAgent.get_Sync(
                    "https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=18701636688", null, null,
                    String::class.java
                )
            }
            // 待两个结果都返回后
            val resultData: String = (taobaoData.await() + baiduData.await())

            // 展示UI
            textView?.text = resultData           // main thread
        }
    }
}

Activity 实现 CoroutineScope 接口

// ------------应用举例-----------  
// CoroutineScope 执行异步网络任务,返回结果更新UI界面
//
// Activity 实现 CoroutineScope 接口
class CoroutineScopeActivity : AppCompatActivity(), CoroutineScope {

    // Job 对象
    lateinit var job: Job;
    // 重写 CoroutineScope 接口中的属性
    override val coroutineContext: CoroutineContext
        get() = Dispatchers.Main + job

    // Activity的onCreate方法
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Log.d(TAG, "---onCreate---")

        // 创建 Job (用于管理CoroutineScope中的所有携程)
        job = Job()

        // 获取网络数据,更新UI
        asyncCoroutine();
    }

    override fun onDestroy() {
        super.onDestroy()
        // 当 Activity 销毁的时候取消该 Scope 管理的 job。
        // 这样该 Scope 内创建的子 Coroutine 都会被自动的取消。
        job.cancel()

    }

    /**
     * CoroutineScope 使用
     */
    private fun asyncCoroutine() {
        // CoroutineScope 的 launch 方法
        job = launch(Dispatchers.Main) {
            // TODO 执行主线程任务                 // main thread
            // 第一个异步网络请求
            val taobaoData = async(Dispatchers.IO) { // IO thread
                // TODO IO线程 网络请求
                // 返回值为String的Http同步网络请求
                HttpAgent.get_Sync(
                    "https://tcc.taobao.com/cc/json/mobile_tel_segment.htm?tel=18701636688", null, null,
                    String::class.java
                )
            }
            // 第二个异步网络请求
            val baiduData = async(Dispatchers.IO) { // IO thread
                // TODO IO线程 网络请求
                // 返回值为String的Http同步网络请求
                HttpAgent.get_Sync(
                    "https://www.baifubao.com/callback?cmd=1059&callback=phone&phone=18701636688", null, null,
                    String::class.java
                )
            }
            // 待两个结果都返回后
            val resultData: String = (taobaoData.await() + baiduData.await())

            // 展示UI
            textView01?.text = resultData           // main thread
        }
    }
}

三、源码下载

源码下载地址如下:
https://download.csdn.net/download/aiwusheng/84095682

参考

developer kotlin coroutines:
https://developer.android.google.cn/kotlin/coroutines?hl=zh-cn#kts

Kotlin CoroutineScope:
http://blog.chengyunfeng.com/?p=1086

= THE END =

文章首发于公众号”CODING技术小馆“,如果文章对您有帮助,欢迎关注我的公众号。
文章首发于公众号”CODING技术小馆“,如果文章对您有帮助,欢迎关注我的公众号。
文章首发于公众号”CODING技术小馆“,如果文章对您有帮助,欢迎关注我的公众号。

目录
相关文章
|
15天前
|
存储 Java 调度
Android面试题之Kotlin 协程的挂起、执行和恢复过程
了解Kotlin协程的挂起、执行和恢复机制。挂起时,状态和上下文(局部变量、调用栈、调度器等)被保存;挂起点通过`Continuation`对象处理,释放线程控制权。当恢复条件满足,调度器重新分配线程,调用`resumeWith`恢复执行。关注公众号“AntDream”获取更多并发知识。
26 2
|
1月前
|
移动开发 Android开发 开发者
构建高效Android应用:Kotlin与协程的完美融合
【5月更文挑战第25天】 在移动开发的世界中,性能和响应性是衡量应用质量的关键指标。随着Kotlin的流行和协程的引入,Android开发者现在有了更强大的工具来提升应用的性能和用户体验。本文深入探讨了Kotlin语言如何与协程相结合,为Android应用开发带来异步处理能力的同时,保持代码的简洁性和可读性。我们将通过实际案例分析,展示如何在Android项目中实现协程,以及它们如何帮助开发者更有效地管理后台任务和用户界面的流畅交互。
|
1月前
|
移动开发 数据库 Android开发
构建高效Android应用:探究Kotlin的协程优势
【5月更文挑战第22天】随着移动开发技术的不断进步,Android平台的性能优化已经成为开发者关注的焦点。在众多提升应用性能的手段中,Kotlin语言提供的协程概念因其轻量级线程管理和异步编程能力而受到广泛关注。本文将深入探讨Kotlin协程在Android开发中的应用,以及它如何帮助开发者构建出更高效、响应更快的应用,同时保持代码的简洁性和可读性。
|
1月前
|
移动开发 监控 Android开发
构建高效安卓应用:Kotlin 协程的实践与优化
【5月更文挑战第16天】 在移动开发领域,性能优化一直是开发者们追求的重要目标。特别是对于安卓平台来说,由于设备多样性和系统资源的限制,如何提升应用的响应性和流畅度成为了一个关键议题。近年来,Kotlin 语言因其简洁、安全和高效的特点,在安卓开发中得到了广泛的应用。其中,Kotlin 协程作为一种轻量级的并发解决方案,为异步编程提供了强大支持,成为提升安卓应用性能的有效手段。本文将深入探讨 Kotlin 协程在安卓开发中的应用实践,以及通过合理设计和使用协程来优化应用性能的策略。
31 8
|
1月前
|
移动开发 Android开发 开发者
构建高效安卓应用:Kotlin 协程的实践指南
【5月更文挑战第18天】 随着移动开发技术的不断进步,安卓平台亟需一种高效的异步编程解决方案来应对日益复杂的应用需求。Kotlin 协程作为一种新兴的轻量级线程管理机制,以其简洁的语法和强大的功能,成为解决这一问题的关键。本文将深入探讨Kotlin协程在安卓开发中的实际应用,从基本概念到高级技巧,为开发者提供一份全面的实践指南,旨在帮助读者构建更加高效、稳定的安卓应用。
|
1月前
|
移动开发 安全 Android开发
构建高效Android应用:Kotlin与协程的完美结合
【5月更文挑战第17天】 在移动开发领域,性能优化和流畅的用户体验是关键。对于Android平台而言,Kotlin语言凭借其简洁性和功能安全性成为开发的首选。与此同时,协程作为一种新的并发处理方式,在简化异步编程方面展现出巨大潜力。本文将深入探讨如何通过Kotlin语言以及协程技术,提升Android应用的性能和响应能力,并确保用户界面的流畅性。
|
1月前
|
移动开发 数据库 Android开发
构建高效Android应用:Kotlin与协程的完美结合
【5月更文挑战第7天】 在移动开发领域,性能优化和资源管理始终是核心议题。随着Kotlin语言的普及,其提供的协程特性为Android开发者带来了异步编程的新范式。本文将深入探讨如何通过Kotlin协程来优化Android应用的性能,实现流畅的用户体验,并减少资源消耗。我们将分析协程的核心概念,并通过实际案例演示其在Android开发中的应用场景和优势。
|
1月前
|
移动开发 数据处理 Android开发
构建高效Android应用:Kotlin的协程与Flow的使用
【5月更文挑战第23天】 在移动开发领域,性能优化和异步编程一直是核心议题。随着Kotlin语言在Android开发中的普及,其提供的协程(coroutines)和流式编程(Flow)功能为开发者带来了革命性的工具,以更简洁、高效的方式处理异步任务和数据流。本文将深入探讨Kotlin协程和Flow在Android应用中的实际应用,以及它们如何帮助开发者编写更加响应迅速且不阻塞用户界面的应用程序。我们将通过具体案例分析这两种技术的优势,并展示如何在现有项目中实现这些功能。
|
1月前
|
算法 安全 Android开发
深入理解操作系统的内存管理机制构建高效Android应用:Kotlin的协程优势
【4月更文挑战第30天】 在现代计算机系统中,操作系统的内存管理是确保系统高效、稳定运行的关键。本文将探讨操作系统内存管理的核心技术,包括内存分配、虚拟内存、分页和分段等概念,以及它们是如何协同工作以提高内存利用率和系统性能的。通过对这些技术的详细分析,我们可以更好地理解操作系统背后的原理,并评估不同内存管理策略对系统行为的影响。 【4月更文挑战第30天】 在移动开发领域,尤其是针对Android平台,性能优化和流畅的用户体验始终是开发者追求的核心目标。随着Kotlin语言的普及,协程作为其在异步编程领域的杀手锏特性,已经逐渐成为提高应用性能和简化代码结构的重要工具。本文将深入探讨Kotli
|
1月前
|
数据处理 调度 Android开发
构建高效Android应用:探究Kotlin的协程优势
【4月更文挑战第28天】 随着移动应用开发日趋复杂,传统的线程和异步处理模型在处理并发任务时显得笨重且易出错。本文深入分析了Kotlin协程作为一种轻量级线程管理方案的优势,探讨了其在Android应用中实现流畅用户界面和提升性能的潜力。通过对协程原理的剖析以及与现有技术的比较,揭示了如何利用协程优化应用结构、简化代码逻辑,并提高应用的响应性和可维护性。