协程简单使用

协程

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

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

倒计时:

    // 不使用协程:每隔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}")
                }
            }
        }
    }

请求网络:

    // 不使用协程:
    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")
        }
    }

挂起与堵塞的区别

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

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

协程使用

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

定义上下文

上下文的指的是 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
}

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

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

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

或者使用协程或者 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
定义运行线程:

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

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

启动任务的方式主要是两种: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 {  } // 当任务结束时回调
    }

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 完成后获取返回值
        }
    }
线程切换

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

    private val scope = CoroutineScope(Dispatchers.Default)
    private fun testWithContext() {
        scope.launch { 
            delay(10000)//模仿耗时操作,比如下载,此时在子线程
            withContext(Dispatchers.Main) {
                // 根据下载内容显示UI,此时在主线程。
            }
            //运行在子线程,且会等待 withContext 执行完毕。
        }
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值