前言
在 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
的源码
通过注释和源码可以得知
suspendCoroutine
是一个内联的挂起函数,它接收一个lambda表达式,作用是将传入的lambda表达式转换为一个挂起函数。- lambda表达式的参数是一个 Continuation 对象,之前的文章中也提到过,
Continuation
是一个接口,内部持有了一个CoroutineContext
类型的变量以及提供了一个抽象的resumeWith
方法。它也是挂起和恢复的桥梁。 suspendCoroutine
函数的返回值是一个泛型,这个泛型就是传入的lambda表达式的返回值。Continuation.resume
或者Continuation.resumeWith(Result.success(result))
函数用于恢复协程的执行,表示异步操作正常完成。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
suspendCancellableCoroutine
与 suspendCoroutine
类似,不同之处在于 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
即可。
总结
通过 suspendCoroutine
和 suspendCancellableCoroutine
函数,我们可以将传统的Callback形式的异步操作转换成协程风格的挂起函数,以便于在协程中使用串行的方式编写异步操作的代码,以保持协程的顺序性和可读性,同时也可以很好的处理异常和取消操作。简单好用!
感谢阅读,觉有有帮助点赞支持,如果有任何疑问或建议,欢迎在评论区留言。如需转载,请注明出处:喻志强的博客,谢谢!