• 协程简单使用


    协程

    使用协程的优势:让代码更简洁地处理异步操作,可以用写同步代码的方式执行异步代码,避免嵌套回调地狱,提高代码可读性和复用性。

    下面是一些使用协程和不使用协程的例子:

    倒计时

        // 不使用协程:每隔1秒输出 4 3 2 1 0
        private val MSG_COUNT_DOWN = 1
        private val handler = object : Handler(Looper.getMainLooper()) {
            override fun handleMessage(msg: Message) {
                if (msg.what == MSG_COUNT_DOWN) {
                    val second = msg.arg1 //倒计时时间
                    //使用这个倒计时时间,比如将其展示到UI上,此处为主线程。
                    Log.i("TEST", "second: $second")
                    if (second > 0) sendMessageDelayed(Message.obtain().apply {
                        this.what = MSG_COUNT_DOWN
                        this.arg1 = second - 1
                    }, 1000)
                }
                super.handleMessage(msg)
            }
        }
    
        private fun startCountDown(second : Int) {
            handler.sendMessage(Message.obtain().apply {
                this.what = MSG_COUNT_DOWN
                this.arg1 = second - 1
            })
        }
        
        
        // 使用协程:每隔1秒输出 4 3 2 1 0
        private fun startCountDown(second : Int) {
            GlobalScope.launch { //启动一个协程,此时运行在子线程
                repeat(second) { //重复 second 次
                    delay(1000) // 挂起 1 秒,此处挂起不会堵塞线程
                    withContext(Dispatchers.Main) { // 切换到主线程
                        //使用这个倒计时时间,比如将其展示到UI上,此处为主线程。
                        Log.i("TEST", "second: ${second - it - 1}")
                    }
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    请求网络:

        // 不使用协程:
        okhttp.newCall(request).enqueue(object : Callback {
                override fun onFailure(call: Call, e: IOException) {
                    
                }
    
                override fun onResponse(call: Call, response: Response) {
                    // 获取结果
                }
            })
            
            
        // 使用协程:
        GlobalScope.launch { //启动一个协程,此时运行在子线程
            val result = okhttp.newCall(request).execute() // 在子线程堵塞耗时
            withContext(Dispatchers.Main) { // 切换到主线程
                //使用这个result,比如将其展示到UI上,此处为主线程。
                Log.i("TEST", "result: $result")
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    挂起与堵塞的区别

    挂起与线程堵塞不同,挂起是基于协程的运行逻辑的,挂起仅仅是暂停协程任务。
    只有挂起函数进行挂起,挂起函数会带有 suspend 标志,比如 delay(),除此之外,只有挂起函数才能调用挂起函数。

       // 需要有 suspend 标签才能调用 delay
       public suspend fun invodeDelay() {
           delay(10000)
       }
    
    • 1
    • 2
    • 3
    • 4

    协程使用

    我们可以从上面的例子中拆分出几个使用协程的步骤:

    定义上下文

    上下文的指的是 CoroutineContext 类,它包含了运行线程、工作状态等信息,我们通常会通过 CoroutineScope 类使用它。

    public interface CoroutineScope {
        /**
         * The context of this scope.
         * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
         * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
         *
         * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
         */
        public val coroutineContext: CoroutineContext
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在上面的例子中,我们使用了 GlobalScope,这个 scope 是进程默认创建的全局 scope,默认使用 Dispatchers.Default 这个线程池,这个线程池里的线程都是子线程,用于处理 cpu 密集型工作。
    使用这个 scope 的时候我们需要时刻注意在不需要的时候取消启动的任务,否认容易引起内存泄漏。因为这个 scope 的生命周期是跟随整个应用进程的生命周期的。

    除此之外,我们也可以 new 一个 scope,如下:

        // 创建了一个默认运行在 Dispatchers.Default 下的 scope,可以通过 scope.cancel() 方法取消基于这个 scope 启动的任务
        private val scope = CoroutineScope(Dispatchers.Default)
    
    • 1
    • 2

    或者使用协程或者 Android 提供的 scope:

        // 默认运行在主线程,且启动的任务的失败不会影响其他任务。
        private val scope = MainScope()
        
        // 跟随 Fragment 生命周期的 scope,会在 activity destroy 时取消所有任务。
        private val scope = lifecycleScope
        
        // 跟随 Fragment 生命周期的 scope,会在 Fragment destroy 时取消所有任务。
        private val scope = lifecycleScope
        
        // 跟随 Fragment view 生命周期的 scope,会在 Fragment view destroy 时取消所有任务。
        private val scope = viewLifecycle.coroutineScope
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    定义运行线程:

    运行线程主要有5类:可以在 scope 创建时或者任务启动时传入。

        Dispatchers.Default // 子线程池,主要用于处理 cpu 密集任务
        Dispatchers.IO // 子线程池,主要用于处理 io 密集任务
        Dispatchers.Main // 主线程
        Dispatchers.Unconfined // 不指定运行线程,这个使用比较少,了解即可
        newSingleThreadContext("TAG") // 创建一个子线程作为任务的运行线程
    
    • 1
    • 2
    • 3
    • 4
    • 5
    启动任务:

    启动任务的方式主要是两种:launch 和 async

    launch:

    在调用 launch 时,我们可以传入指定的 CoroutineContext,它会将这个 context 和调用 launch 的 scope 的 context 进行组合。另外,launch 的 block 是运行在挂起函数里的,因此在里面可以随意的调用挂起函数。
    调用 launch 后,我们可以获得一个 Job 类的实例,可以根据这个实例与 launch 启动的任务进行协作。

        private val scope = CoroutineScope(Dispatchers.Default)
        private fun testLaunch() {
            val job = scope.launch {
                delay(10000)
                // 运行在 Dispatchers.Default 线程池中
            }
            scope.launch(Dispatchers.Main) {
                // 运行在主线程中
                job.join() //会挂起等待 job 执行完成
                // 10 秒后执行到这
            }
            // job.cancel() // 取消协程任务 
            // job.isActive // 查询任务是否已完成
            // job.invokeOnCompletion {  } // 当任务结束时回调
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    async

    async 大体与 launch 类似,不同点在于它启动的任务是可以获取结果的,类似于 Java 的 Fuature。它的返回值是一个 Deferred 类实例,它继承自 Job,在 Job 的基础上添加了获取 async 结果的函数。

        private val scope = CoroutineScope(Dispatchers.Default)
        private fun testAsync() {
            val deferred = scope.async {
                delay(10000)
                return@async System.currentTimeMillis()
            }
            scope.launch { 
                val timeMillis = deferred.await() //挂起等待 deferred 完成后获取返回值
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    线程切换

    协程中线程切换主要是通过 withContext() 函数操作的,它可以传入一个 CoroutineContext 并使 block 中的代码运行在这个 context 环境中。

        private val scope = CoroutineScope(Dispatchers.Default)
        private fun testWithContext() {
            scope.launch { 
                delay(10000)//模仿耗时操作,比如下载,此时在子线程
                withContext(Dispatchers.Main) {
                    // 根据下载内容显示UI,此时在主线程。
                }
                //运行在子线程,且会等待 withContext 执行完毕。
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
  • 相关阅读:
    Spring源码该如何阅读?十年架构师带来的Spring源码解析千万不要错过!
    d中声明式gui
    计算机毕业设计家校通微信小程序源码
    CSS 对齐、组合选择符、伪类、伪元素、导航栏
    js - leetcode-爬楼梯
    黑马瑞吉外卖之套餐信息的删除
    CVE-2017-12615 PUT方法漏洞复现
    docker 安装MySQL8以上
    基于Java的毕业设计选题管理系统设计与实现(源码+lw+部署文档+讲解等)
    美国就业报告后美元小幅下跌 南非兰特走强
  • 原文地址:https://blog.csdn.net/weixin_37077539/article/details/127847370