协程与网络请求的结合已经不是新鲜事物,那网络请求的结果是如何在协程中回调的呢?
本文简单探讨使用suspendCoroutine
,suspendCancellableCoroutine
,CompletableDeferred
在请求中的回调,如有不足欢迎评论纠正或补充。
一、suspendCoroutine
suspendCoroutine
可以暴露协程的回调Continuation
,这样我们就可以通过这个回调设置网络请求的返回,先看下面的代码,是一个简单的okhttp
请求:
class MainOneActivity : AppCompatActivity() {
private var startTime: Long = 0
private var endTime: Long = 0
private val mainViewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//打印协程名称
System.setProperty("kotlinx.coroutines.debug", "on")
//感谢wanandroid api
val url = "https://www.wanandroid.com//hotkey/json"
//简单的okhttp网络请求
val client = OkHttpClient()
val request = Request.Builder().url(url).build()
val call = client.newCall(request)
//记录开始时间
startTime = System.currentTimeMillis()
//开启协程发起网络请求
mainViewModel.viewModelScope.launch {
val deprecated = async(Dispatchers.IO) {
//协程中网络数据的回调
callbackData(call)
}
//异常的回调
deprecated.invokeOnCompletion {
endTime = System.currentTimeMillis()
//打印异常信息
log("invokeOnCompletion: ${endTime - startTime}ms error: $it")
}
//获取到数据的回调
deprecated.await().let {
endTime = System.currentTimeMillis()
//打印正常信息
log("await: ${endTime - startTime}ms data: $it")
}
}
}
/**
* 协程中网络数据的回调
*/
private suspend fun callbackData(call: Call): String =
suspendCoroutine { continuation ->
//okhttp网络请求
call.enqueue(object : okhttp3.Callback {
override fun onFailure(call: Call, e: IOException) {
//回调异常信息
continuation.resumeWithException(e)
}
override fun onResponse(call: Call, response: Response) {
val data = response.body?.string() ?: ""
//回调正常信息
continuation.resume(data)
//打印网络请求的结果
log("onResponse: $data")
}
})
}
/**
* 打印日志
*/
fun log(msg: String) {
Log.d("LOG_PRINT",
"""
-
内容:$msg,
线程:${Thread.currentThread().name}
""".trimIndent())
}
}
整个代码比较简单,就是通过协程发起网络请求,然后回调网络请求的结果,其中需要注意的点是通过ViewModel
开启的协程作用域(主线程),然后通过async
开启子线程的协程作用域,等待子线程的协程作用域挂起恢复后,结果会回调到主线程。具体注释已经很清晰,看一下日志输出的结果:
LOG_PRINT: -
内容:invokeOnCompletion: 342ms error: null, //没有异常信息
线程:DefaultDispatcher-worker-1 @coroutine#2
LOG_PRINT: -
内容:onResponse: {"data":[{"id":6,"link":""....略...}, //网络请求成功
线程:OkHttp https://www.wanandroid.com/...
LOG_PRINT: -
内容:await: 346ms data:{"data":[{"id":6,"link":""....略...}, //主线程回调网络请求的结果
线程:main @coroutine#1
成功的实现了整个网络请求的回调过程,接下来测试一下取消网络请求,代码如下:
//开启协程发起网络请求
mainViewModel.viewModelScope.launch {
val deprecated = async(Dispatchers.IO) {
//协程中网络数据的回调
callbackData(call)
}
delay(100) //<-------------------------------变化在这里
deprecated.cancel() //<-------------------------------变化在这里
//异常的回调
deprecated.invokeOnCompletion {
if(deprecated.isCancelled){ //<-------------------------------变化在这里
//协程被取消的时候取消网络请求
call.cancel() //<-------------------------------变化在这里
}
endTime = System.currentTimeMillis()
log("invokeOnCompletion: ${endTime - startTime}ms error: $it")
}
//获取到数据的回调
deprecated.await().let {
endTime = System.currentTimeMillis()
log("await: ${endTime - startTime}ms data: $it")
}
}
日志输出:
LOG_PRINT: -
内容:invokeOnCompletion: 598ms error: kotlinx.coroutines.JobCancellationException: DeferredCoroutine was cancelled; job="coroutine#2":DeferredCoroutine{Cancelled}@26b4072,
线程:DefaultDispatcher-worker-1 @coroutine#2
LOG_PRINT: -
内容:onResponse: data:{"data":[{"id":6,"link":""....略...}, //网络请求仍然成功了,且耗时没有减少
线程:OkHttp https://www.wanandroid.com/...
一般情况下都是在invokeOnCompletion
中监听协程的取消回调,所以把网络请求的取消写在了回调里面,但是发现网络请求仍然执行了,整个耗时并没有减少,所以在平时项目中应该谨慎使用。相对于suspendCoroutine
,其实有一个可取消的回调函数可以用,那就是suspendCancellableCoroutine
。
二、suspendCancellableCoroutine
这里直接看使用suspendCancellableCoroutine
取消网络请求的代码,其他基本雷同:
class MainTwoActivity : AppCompatActivity() {
private var startTime: Long = 0
private var endTime: Long = 0
private val mainViewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//打印协程名称
System.setProperty("kotlinx.coroutines.debug", "on")
//感谢wanandroid api
val url = "https://www.wanandroid.com/article/top/json"
val client = OkHttpClient()
val request = Request.Builder().url(url).build()
val call = client.newCall(request)
//记录开始时间
startTime = System.currentTimeMillis()
//开启协程发起网络请求
mainViewModel.viewModelScope.launch {
val deprecated = async {
callbackWithCancelData(call)
}
//延迟100毫秒后取消网络请求
delay(100)
deprecated.cancel() <---------------------------取消协程
//异常的回调
deprecated.invokeOnCompletion {
endTime = System.currentTimeMillis()
log("invokeOnCompletion: ${endTime - startTime}ms error: $it ")
}
//获取到数据的回调
deprecated.await().let {
endTime = System.currentTimeMillis()
log("await: ${endTime - startTime}ms data: $it ")
}
}
}
/**
* 协程中网络数据的回调
*/
private suspend fun callbackWithCancelData(call: Call): String =
suspendCancellableCoroutine { continuation -> <--------------------改为suspendCancellableCoroutine
call.enqueue(object : okhttp3.Callback {
override fun onFailure(call: Call, e: IOException) {
continuation.resumeWithException(e)
}
override fun onResponse(call: Call, response: Response) {
val data = response.body?.string() ?: ""
continuation.resume(data)
log("onResponse: $data")
}
})
//取消协程回调(suspendCoroutine 没有这个api)
continuation.invokeOnCancellation { <---------------------------取消协程的回调
//取消网络请求
call.cancel()
}
}
/**
* 打印日志
*/
fun log(msg: String) {
Log.d("LOG_PRINT",
"""
-
内容:$msg,
线程:${Thread.currentThread().name}
""".trimIndent())
}
}
输出日志:
LOG_PRINT: -
内容:invokeOnCompletion: 112ms error: kotlinx.coroutines.JobCancellationException: DeferredCoroutine was cancelled; job="coroutine#2":DeferredCoroutine{Cancelled}@f0d4e55 ,
线程:main @coroutine#1
可以看到网络请求被取消了,且耗时只有112毫秒,所以suspendCancellableCoroutine
更适合我们在协程取消时需要同步取消其他任务的需求。为什么suspendCoroutine
没有invokeOnCancellation
这个api,而suspendCancellableCoroutine
有呢? 对比源码看一下:
public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->
val safe = SafeContinuation(c.intercepted())
block(safe)
safe.getOrThrow()
}
}
public suspend inline fun <T> suspendCancellableCoroutine(
crossinline block: (CancellableContinuation<T>) -> Unit
): T =
suspendCoroutineUninterceptedOrReturn { uCont ->
val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
cancellable.initCancellability()
block(cancellable)
cancellable.getResult()
}
原来suspendCoroutine
的协程回调对象是Continuation
, 而suspendCancellableCoroutine
的协程回调对象是CancellableContinuation
,继续追踪CancellableContinuation
的invokeOnCancellation
方法,源码如下:
/**
* ....略...
* the handler will be invoked as soon as this
* continuation is cancelled.
* ....略...
*/
public fun invokeOnCancellation(handler: CompletionHandler)
可以看到api的介绍中,如果协程被取消会尽快的回调这个函数,于是我们就可以在这个api中做协程取消的同步动作了。
接下来看一下CompletableDeferred
。
三、CompletableDeferred
直接看使用CompletableDeferred
取消网络请求的代码,如下:
class MainThreeActivity : AppCompatActivity() {
private var startTime: Long = 0
private var endTime: Long = 0
private val mainViewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//打印协程名称
System.setProperty("kotlinx.coroutines.debug", "on")
//感谢wanandroid api
val url = "https://www.wanandroid.com/article/top/json"
val client = OkHttpClient()
val request = Request.Builder().url(url).build()
val call = client.newCall(request)
//记录开始时间
startTime = System.currentTimeMillis()
//开启协程发起网络请求
mainViewModel.viewModelScope.launch {
//协程中网络数据的回调
val deprecated = callbackWithDeferredCancelData(call) <-----------网络请求
//延迟100毫秒后取消协程
delay(100)
deprecated.cancel() <-----------取消协程
//异常的回调
deprecated.invokeOnCompletion {
//取消协程
if (deprecated.isCancelled) { <-----------监听取消协程
//取消网络请求
call.cancel() <-----------取消网络请求
}
endTime = System.currentTimeMillis()
log("invokeOnCompletion: ${endTime - startTime}ms error:$it")
}
//获取到数据的回调
deprecated.await().let {
endTime = System.currentTimeMillis()
log("await: ${endTime - startTime}ms data: $it ")
}
}
}
/**
* 协程中网络数据的回调(不需要是一个挂起函数)
*/
private fun callbackWithDeferredCancelData(call: Call): CompletableDeferred<String> {
return CompletableDeferred<String>().also { deferred -> <-----------使用CompletableDeferred
call.enqueue(object : okhttp3.Callback {
override fun onFailure(call: Call, e: IOException) {
deferred.completeExceptionally(e) <-----------回调数据
}
override fun onResponse(call: Call, response: Response) {
val data = response.body?.string() ?: ""
if (response.isSuccessful) {
deferred.complete(data) <-----------回调数据
} else {
deferred.completeExceptionally(Exception()) <-----------回调数据
}
log("onResponse: $data ")
}
})
}
}
/**
* 打印日志
*/
fun log(msg: String) {
Log.d(
"LOG_PRINT",
"""
-
内容:$msg,
线程:${Thread.currentThread().name}
""".trimIndent()
)
}
}
日志输出:
LOG_PRINT: -
内容:invokeOnCompletion: 110ms error:kotlinx.coroutines.JobCancellationException: Job was cancelled; job=CompletableDeferredImpl{Cancelled}@f0d4e55,
线程:main @coroutine#1
可以看到CompletableDeferred
也能及时的回调协程取消的操作,协程取消后,网络请求也取消了。
四、总结
文章是借用网络请求来理解协程的回调,以及取消协程应该注意的问题,可以举一反三以点带面来思考其他场景协程的使用。 协程想要学精还是挺难的,需要一点点积累,一点点总结。如果有发现错误或者不足欢迎指出。
最后
如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。
如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。
全套视频资料:
一、面试合集
二、源码解析合集
三、开源框架合集
欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓
PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题