目的
协程用起来很方便,可以将异步的代码完全平铺开,写成和同步一样的代码形式。但很多时候,很多方法是以回调的形式给出的,所以处理起来依旧不够优雅。
本篇文章的目的就是解决这个问题。
例子
原始形式
以OkHttp为例,OkHttp就是以回调的形式给出网络请求结果。通常我们的写法如下:
/**
* 原始写法
*/
private fun okHttpTest() {
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.d(TAG, "onFailure: ${e.message}")
}
override fun onResponse(call: Call, response: Response) {
Log.d(TAG, "onResponse: ${response.body()?.string()}")
nextEvent(response.message()) //下一步事件
}
})
}
/**
* 事件流事件
*/
private fun nextEvent(event: String) {
Log.d(TAG, "nextEvent: $event")
}
这么写完,依旧没有摆脱回调地狱,而且依旧无法同时并行完成事件流。
改造回调
/**
* 改造后的写法
*/
private suspend fun okHttpWithCoroutine(): Response = suspendCancellableCoroutine {
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
it.resumeWithException(e) //事件处理异常,协程回复,抛出异常
}
override fun onResponse(call: Call, response: Response) {
it.resume(response) //事件处理完毕,协程回复,返回结果
}
})
}
//改造后的使用
scope.launch {
val result = withContext(Dispatchers.IO) { //子线程运行网络任务
okHttpWithCoroutine().body()?.string()
} //在获取到结果之前,协程处于挂起状态,不会执行接下去的任务
result?.let { nextEvent(it) } //获取到结果,自动切换到主线程,执行下一步任务
}
上述代码的改造方法,可简单总结为:在需要返回结果的地方,调用resume()
方法;在需要抛出异常的地方,调用resumeWithException()
方法。
如果nextEvent()方法也是回调式的,可以同样改造成这样方式。
改造普通函数变成挂起函数,最常用的两个函数是
suspendCoroutine
与suspendCancellableCoroutine
,这两者几乎大同小异,都是接受一个lambda表达式作为参数。区别是,前者传入的Continuation,后者传入的是CancellableContinuation<T>
。后者可以执行continuation.invokeOnCancellation { }
函数,该函数会在协程被取消时执行。可用于一些如资源需要关闭、或者网络请求等场景,实现在协程被取消时,关闭资源or取消网络请求。
优化:并行处理事件流
上面的方式,两个任务依旧不是同步执行的,而仍旧是按照顺序执行的,效率上并未得到提升,只是代码上看着舒服了一些。
如需并行执行,可配合async{}
和wait()
,来同步执行两个任务。当两个任务都获取到结果后,再执行之后的任务。
scope.launch {
val first = async {
okHttpWithCoroutine().body()?.string()
}
val second = async {
nextEvent()
}
Log.d(TAG, "sync result : ${first.await()} -- ${second.await()}")
}