• 一文学会 使用Kotlin Coroutine协程


    协程的概念最早由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技术小馆“,如果文章对您有帮助,欢迎关注我的公众号。
    欢迎关注我的公众号

  • 相关阅读:
    2022918 #29 愿灰飞烟灭 我的每个昨天
    2022917 #28 请允许我独自探访这场戏的结局
    2022911/12 #27 自弹 自唱 自赏 不如自封为王
    2022912 #26 新的开始
    EBS:导入弹性域关键字的值(FND_FLEX_LOADER_APIS.up_value_set_value)
    EBS:已知科目组合ID,查询科目组合值
    EBS:值集获取段限定词SQL
    EBS:资产期间状态查询
    Oracle Linux 7u2 启动错误 XFS_WANT_CORRUPTED_GOTO
    EBS:事物处理活动TRANSACTION_ACTION_ID
  • 原文地址:https://www.cnblogs.com/xiaxveliang/p/15986513.html
Copyright © 2020-2023  润新知