将传统回调方式网络请求改造成 Kotlin 协程(Coroutine)的形式

传统的网络请求,往往采用回调的方式接收响应结果,比如 onSuccess 和 onFailed 分别对应成功和失败的情况,而现在由于 Kotlin 协程的出现,我们只需要一行代码即可实现网络请求,非常简洁。
下面将以和风天气SDK 为例,将其提供的回调方式的网络请求改为协程的方式实现。
先来看一下正常使用和风天气SDK请求天气数据的例子
/**
* 传统回调方式
*/
fun getNowWeatherWithCallback(
context: Context,
listener: HeWeather.OnResultWeatherNowBeanListener
) {
HeWeather.getWeatherNow(context, "", Lang.ENGLISH, Unit.METRIC, object : HeWeather.OnResultWeatherNowBeanListener {
override fun onSuccess(now: Now?) {
listener.onSuccess(now)
}
override fun onError(error: Throwable) {
listener.onError(error)
}
})
}
代码很简单,通过调用 SDK 中的 HeWeather.getWeatherNow 发起网络请求,通过注册 HeWeather.OnResultWeatherNowBeanListener 接口接收请求结果。
那么改造成协程后效果如何呢!
val now = getNowWeatherWithCoroutine(this@HandleActivity)
没错,仅需一行代码,就可以实现网络请求,那么是如何实现的呢?
将传统回调的方式改造成协程的方式,关键使用 suspendCoroutine 方法。
suspend fun getNowWeather(context: Context): Now? {
return suspendCoroutine { continuation ->
}
}
这个方法提供一个参数 continuation ,先来看下它的实现。
/**
* Interface representing a continuation after a suspension point that returns a value of type `T`.
*/
@SinceKotlin("1.3")
public interface Continuation<in T> {
/**
* The context of the coroutine that corresponds to this continuation.
*/
public val context: CoroutineContext
/**
* Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
* return value of the last suspension point.
*/
public fun resumeWith(result: Result<T>)
}
里面有一个关键的方法 resumeWith ,通过注释可以知道,它的作用是:
恢复执行相应的协程,将成功或失败的结果作为上次挂起点的返回值
知道了上面的方法,就可以这么使用:
suspend fun getNowWeather(context: Context): Now? {
return suspendCoroutine { continuation ->
HeWeather.getWeatherNow(context, "", Lang.ENGLISH, Unit.METRIC, object : HeWeather.OnResultWeatherNowBeanListener {
override fun onSuccess(now: Now?) {
continuation.resume(now)
}
override fun onError(error: Throwable) {
continuation.resumeWithException(error)
}
})
}
}
可以看到在之前的回调方法 onSuccess 和 onError 中分别调用了 continuation.resume(now) 和 continuation.resumeWithException(error)
其中 resume 是用来恢复成功的情况,它的实现如下:
/**
* Resumes the execution of the corresponding coroutine passing [value] as the return value of the last suspension point.
*/
@SinceKotlin("1.3")
@InlineOnly
public inline fun <T> Continuation<T>.resume(value: T): Unit =
resumeWith(Result.success(value))
而 resumeWithException 是用来恢复失败的情况,它的实现如下:
/**
* Resumes the execution of the corresponding coroutine so that the [exception] is re-thrown right after the
* last suspension point.
*/
@SinceKotlin("1.3")
@InlineOnly
public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
resumeWith(Result.failure(exception))
可以看到它们分别调用了 Continuation 的 resumeWith 。
那么问题来了,上面所说的挂起点是在哪呢,没错就是最开始调用的 suspendCoroutine 的方法,来看下它的实现。
/**
* Obtains the current continuation instance inside suspend functions and suspends
* the currently running coroutine.
*
* In this function both [Continuation.resume] and [Continuation.resumeWithException] can be used either synchronously in
* the same stack-frame where the suspension function is run or asynchronously later in the same thread or
* from a different thread of execution. Subsequent invocation of any resume function will produce an [IllegalStateException].
*/
@SinceKotlin("1.3")
@InlineOnly
public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T =
suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->
val safe = SafeContinuation(c.intercepted())
block(safe)
safe.getOrThrow()
}
好了,既然已经改造完毕了,该如何使用呢?
细心的同学肯定发现了,在 getNowWeather 方法前多了一个新的关键字 suspend ,意味挂起的意思,而带有这个关键字的函数,我们称它为挂起函数,挂起函数有个特殊的地方,它的调用必须在 CoroutineScope 中进行,否则IDE会报错。
一般情况下使用最原始的 CoroutineScope 是没有问题的,但是它并不会帮我们处理生命周期等情况,所以更推荐使用 lifecycleScope ,它是 lifecycle-runtime-ktx 库中提供的扩展函数。
private fun getWeatherWithCoroutine() {
lifecycleScope.launch {
val now = getNowWeather(this@MainActivity)
// do something ...
}
}
可以看到非常简单的就完成了网络请求,并且消除了回调。
那么问题来了,网络请求失败的情况该如何处理呢?答案就在上面回调 onError 时调用的 resumeWithException 方法,它会抛出一个异常,那么自然而然,我们捕获它就可以进行失败的情况的处理。
private fun getWeatherWithCoroutine() {
lifecycleScope.launch {
try {
val now = getNowWeather(this@HandleActivity)
// success, do something ...
} catch (e: Throwable) {
// failed, do something ...
}
}
}
❤️ done