您当前的位置: 首页 >  kotlin
  • 0浏览

    0关注

    674博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

kotlin中将回调改写为协程

沙漠一只雕得儿得儿 发布时间:2020-12-26 14:57:47 ,浏览量:0

回调的写法在Java中再常见不过了。但是它却有着不小的隐患:

  1. 嵌套太多,成为“回调地狱”;
  2. 传入的callback如果是Activity会引起泄露;
  3. 代码阅读起来不直观。
在Kotlin中如何将异步回调转换为同步请求:

主要靠两个函数:

不支持取消的suspendCoroutine{}

支持取消的suspendCancellableCoroutine{};

回调结果回传使用的continuation.resumeWithException,continuation.resume;continuation.resumeWith

 回调函数转协程通常使用两个协程相关的类:suspendCancellableCoroutine和suspendCoroutine,前者可以通过cancel()方法手动取消协程的执行,而suspendCoroutine没有该方法,调用cancel()后协程不再往下执行,抛出 CancellationException 异常,但是程序不会崩溃,这样会更加安全,通常推荐使用suspendCancellableCoroutine

举例一:OkHttp 的网络请求转换为挂起函数
suspend fun  Call.await(): T = 
suspendCoroutine { continuation ->
    enqueue(object : Callback {
        override fun onResponse(call: Call, response: Response) { 
            if (response.isSuccessful) {
                continuation.resume(response.body()!!)
            } else {
                continuation.resumeWithException(ErrorResponse(response))
            }
        }
        override fun onFailure(call: Call, t: Throwable) { 
            continuation.resumeWithException(t)
        } 
    })
}

上面的await()是一个Call的拓展函数调用时,使用 suspendCoroutine{} 将请求挂起,然后执行enqueue将网络请求放入队列中,当请求成功时,耗时操作完成后,通过cont.resume(response.body()!!)来恢复之前的协程。resume传递执行结果,resumeWithExeption传递异常。

调用处写法如下:这里假设Service.loadData()会返回一个Call对象

GlobalScope.launch(Dispatchers.Main) {
    try {
        /**
          这里假设Service.loadData()会返回一个Call对象.
        **/
        val result = Service.loadData().await()

    } catch (e: Exception) {
        userNameView.text = "Get User Error: $e"
    }
}
举例二:支持取消的挂起函数
import kotlinx.coroutines.suspendCancellableCoroutine
import retrofit2.Call
import retrofit2.Callback
import retrofit2.HttpException
import retrofit2.Response
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException

suspend fun  Call.await(): T = suspendCancellableCoroutine { continuation ->
    continuation.invokeOnCancellation {
        cancel()
    }

    enqueue(object : Callback {
        override fun onFailure(call: Call, t: Throwable) {
            continuation.resumeWithException(t)
        }

        override fun onResponse(call: Call, response: Response) {
            response.takeIf { it.isSuccessful }?.body()?.also {
                continuation.resume(it)
            } ?: continuation.resumeWithException(HttpException(response))
        }

    })
}

支持挂起函数的取消和不支持取消的差异点在于:

  1. 使用suspendCancellableCoroutine{}
  2. 需要调用Call的取消方法cancel(),也就是被扩展的取消方法,这里的cancel是retrofit中的。

 调用方法,最后可以不加cancelAndJoin(),加上的话会立即取消掉而没有任何效果:

GlobalScope.launch(Dispatchers.Main) {
    try {
        /**
          这里假设Service.loadData()会返回一个Call对象.
        **/
        val result = Service.loadData().await()

    } catch (e: Exception) {
        userNameView.text = "Get User Error: $e"
    }
}.cancelAndJoin()
举例三:将耗时计算的回调转化为协程写法

类似的,有个 calcSlowlySync 为耗时方法,改写后如下:

suspend fun calcSlowlySync(inp: Int): Int =
    suspendCoroutine { cont ->
        calcSlowly(inp, object: CalcTaskCallback {
            override fun onSuccess(result: Int) {
                cont.resume(result)
            }

            override fun onFailure(code: Int, msg: String) {
                cont.resumeWithException(Exception("code=$code, msg=$msg"))
            }
        })
    }

调用写法如下:

CoroutineScope(Dispatchers.Main).launch {
    try {
        val result = calcSlowlySync(100)
        println("result=$result")
    } catch (e: Exception) {
        LogUtils.e(TAG, "result exception: ", e)
    }
}

注意如下几点即可:

1、在耗时方法fun前需要添加suspend关键字,表示挂起函数,标注这里是一个耗时操作;

2、在回调函数成功或者失败时,需要将结果返回出去,利用resume传递执行结果,resumeWithExeption传递异常;

3、在外面使用的地方,需要在协程作用域中调用,例如例子中的CoroutineScope(Dispatchers.Main).launch{}内;

4、异步回调的异常处理改写为同步后是通过try{}catch(){}代码块进行捕捉的:一个异步的请求异常,我们只需要在我们的代码中捕获就可以了,这样做的好处就是,请求的全流程异常都可以在一个 try...catch... 当中捕获,那么我们可以说真正做到了把异步代码变成了同步的写法。

参考:破解 Kotlin 协程(4) - 异常处理篇 - 腾讯云开发者社区-腾讯云

关注
打赏
1657159701
查看更多评论
立即登录/注册

微信扫码登录

0.0368s