协程与网络恳求的结合现已不是新鲜事物,那网络恳求的成果是如何在协程中回调的呢?

本文简略探讨运用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,继续追踪CancellableContinuationinvokeOnCancellation办法,源码如下:

/**
 * ....略...
 * 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也能及时的回调协程撤销的操作,协程撤销后,网络恳求也撤销了。

四、总结

文章是借用网络恳求来理解协程的回调,以及撤销协程应该注意的问题,能够触类旁通以点带面来思考其他场景协程的运用
协程想要学精还是挺难的,需求一点点堆集,一点点总结。如果有发现过错或者缺乏欢迎指出。

个人学习笔记