kotlin 协程之Callback转挂起函数

前言

在 Kotlin 协程中可以通过挂起函数来实现异步操作的串行化,但是在日常开发场景中,大部分项目都是java和kotlin并存的,老旧的Java代码除非有需求,否则不会轻易改动重构。

即使项目是纯kotlin开发的,也会有一些java代码实现的三方库,

因此,我们很难规避掉所有的 Callback,而且Callback是一种非常常见的设计模式,我们经常会在项目中看到。

当然,在协程中,我们也可以使用Callback。

一个非常常见的Callback场景,例如App启动时获取一个远程配置数据,成功后做一些操作,类似这种已经存在的Callback形式的代码,我们想在协程中使用但是又不方便去把原始实现改成挂起函数,这时候我们就可以把Callback转挂起函数。

协程中调用Callbck

Callback形式的代码

    private fun fetchConfig(block: (JSONObject?) -> Unit) {
        val jsParams = JSONObject()
        jsParams.put(HttpApiConstant.HTTP_PARAMS, JSONObject())

        HttpManager.httpGet(
            url,
            object : IHttpCallback {
                override fun onComplete(response: String) {                 
                    jsonConfig = runCatching { JSONObject(response) }.getOrNull()
                    block(jsonConfig)
                }

                override fun onError(response: Array<out String>) {
                    block(null)
                }
            })
    }

在协程中使用:

        CoroutineScope().launch {
            //fetchDataControlConfig是一个Callback形式的方法,这里就形成了嵌套,很难受
            fetchDataControlConfig { result->
                //doSomething
            }
           
        }

在协程中使用Callback风格的代码打破了协程的顺序性和可读性,写起来很难受。因此,我们如果能把Callback转换成挂起函数,那么,在协程中调用就很舒服了。

suspendCoroutine 就提供了一种将传统回调风格的异步操作转换为协程风格的挂起函数的方式。

Callback转挂起函数 suspendCoroutine

suspendCoroutine 是 Kotlin 协程中的一个重要函数,它可以在协程中直接调用传统的基于回调的异步操作,例如通过回调函数(callback)来获取结果。

先看下 suspendCoroutine 的源码
在这里插入图片描述

通过注释和源码可以得知

  1. suspendCoroutine 是一个内联的挂起函数,它接收一个lambda表达式,作用是将传入的lambda表达式转换为一个挂起函数。
  2. lambda表达式的参数是一个 Continuation 对象,之前的文章中也提到过,Continuation
    是一个接口,内部持有了一个 CoroutineContext 类型的变量以及提供了一个抽象的 resumeWith 方法。它也是挂起和恢复的桥梁。
  3. suspendCoroutine 函数的返回值是一个泛型,这个泛型就是传入的lambda表达式的返回值。
  4. Continuation.resume 或者 Continuation.resumeWith(Result.success(result)) 函数用于恢复协程的执行,表示异步操作正常完成。
  5. Continuation.resumeWithException 或者 Continuation.resumeWith(Result.failure(e))
    函数用于恢复协程的执行,并抛出异常,表示异步操作出现异常。

来使用suspendCoroutine函数来把之前的Callback转换成挂起函数。

suspend fun fetchConfig(): JSONObject? = suspendCoroutine { continuation ->
    fetchConfig { result ->
        continuation.resume(result)
    }
}

然后我们就可以这样使用了:

        CoroutineScope().launch {
            val json = fetchConfig()
            //doSomething
        }

这样,我们就把Callback转换成了挂起函数,在协程中使用起来更加优雅。

对于异常的处理,我们可以使用Continuation.resumeWithException函数来处理。

callback 代码示例


    interface Callback {
        fun success(result: String)

        fun error(e: Exception)
    }
    
    private fun callbackFun(callback: Callback) {
        ThreadPoolManager.instance.ioThreadPoolExecutor.execute {
            try {
                //doSomething
                Logger.i("callbackFun 开始")
                Thread.sleep(2000)
                callback.success("成功")
            } catch (e: Exception) {
                callback.error(e)
            }
        }
    }

转换成挂起函数

    private suspend fun callbackToSuspend() = suspendCoroutine {
        callbackFun(object : Callback {
            override fun success(result: String) {
                Logger.i("success回调","resume result:$result")
                it.resume(result)
            }

            override fun error(e: Exception) {
                Logger.i("error回调","resumeWithException:${e.message}")
                it.resumeWithException(e)
            }
        })
    }

使用起来也是非常简单。

            CoroutineScope(Dispatchers.IO).launch {
                
                    val result = callbackToSuspend()
                    Logger.i("callbackToSuspend:$result")

            }

执行结果:

在这里插入图片描述

来试试异常的情况:

    private fun callbackFun(callback: Callback) {
        ThreadPoolManager.instance.ioThreadPoolExecutor.execute {
            try {
                //doSomething
                Logger.i("callbackFun 开始")
                Thread.sleep(2000)
                throw Exception("自定义异常")
                callback.success("成功")
            } catch (e: Exception) {
                callback.error(e)
            }
        }
    }

使用

            CoroutineScope(Dispatchers.IO).launch {
                try {
                    val result = callbackToSuspend()
                    Logger.i("callbackToSuspend:$result")
                } catch (e: Exception) {
                    //异常处理
                    Logger.i("异常处理:${e.message}")
                }
            }

执行结果:

在这里插入图片描述

suspendCancellableCoroutine

suspendCancellableCoroutinesuspendCoroutine 类似,不同之处在于 suspendCancellableCoroutine 是可以被取消的, suspendCoroutine 是不会被取消的。

我们都知道线程是可以通过 Thread.interrupt() 方法来中断线程的,内部做一些代码实现来配合 interrupt 来中断线程。
协程也有类似的功能,可以通过 cancel()方法来取消协程。协程的取消也是一个比较重要的点,我们后面再来详细讲解。

目前只需要知道,suspendCancellableCoroutine 是可以被取消的即可。

代码示例:

        private suspend fun callbackToSuspend() = suspendCancellableCoroutine {
            callbackFun(object : Callback {
                override fun success(result: String) {
                    //正常返回数据
                    it.resume(result)
                }
                override fun error(e: Exception) {
                    //异常返回
                    it.resumeWithException(e)
                }
            })
            it.invokeOnCancellation {
                //协程被取消的回调
            }
        }

如果你希望你的挂起函数支持取消,那么,就使用suspendCancellableCoroutine 即可。

总结

通过 suspendCoroutinesuspendCancellableCoroutine函数,我们可以将传统的Callback形式的异步操作转换成协程风格的挂起函数,以便于在协程中使用串行的方式编写异步操作的代码,以保持协程的顺序性和可读性,同时也可以很好的处理异常和取消操作。简单好用!

下一篇:kotlin协程之runBlocking


感谢阅读,觉有有帮助点赞支持,如果有任何疑问或建议,欢迎在评论区留言。如需转载,请注明出处:喻志强的博客,谢谢!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

XeonYu

码字不易,鼓励随意。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值