协程与网络恳求的结合现已不是新鲜事物,那网络恳求的成果是如何在协程中回调的呢?
本文简略探讨运用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
也能及时的回调协程撤销的操作,协程撤销后,网络恳求也撤销了。
四、总结
文章是借用网络恳求来理解协程的回调,以及撤销协程应该注意的问题,能够触类旁通以点带面来思考其他场景协程的运用。
协程想要学精还是挺难的,需求一点点堆集,一点点总结。如果有发现过错或者缺乏欢迎指出。