协程和Okhttp中的取消

时间:2020-03-11 09:57:48

标签: kotlin okhttp kotlin-coroutines coroutinescope

因此,我正在使用Coroutines和Okhttp连接网络套接字。

我做了什么

// initialise okhttp
fun provideOkHttpClient(): OkHttpClient {
        return OkHttpClient.Builder()
            .addInterceptor(RetryInterceptor())
            .build()
}

// RetryInterceptor.kt
class RetryInterceptor : Interceptor {

    companion object {
        private const val RETRIES_LIMIT = 4
    }

    override fun intercept(chain: Interceptor.Chain): Response {
        val request = chain.request()
        var retries = 0

        var response: Response?
        response = sendRequest(chain, request)

        while (response == null && retries <= RETRIES_LIMIT) {
            retries++
            val sleepTimer = 2.toDouble().pow(retries.toDouble())
            Log.d("OkhttpClient", "Connection failed, retry in ${sleepTimer}s")
            Thread.sleep(sleepTimer.toLong() * 1000)
            response = sendRequest(chain, request)
        }

        return response ?: Response.Builder()
            .request(request)
            .code(400)
            .build()
    }

    private fun sendRequest(chain: Interceptor.Chain, request: Request): Response? {
        val response: Response
        return try {
            response = chain.proceed(request)
            if (!response.isSuccessful) null else response
        } catch (e: IOException) {
            null
        }
    }
}

// define a exception handler
val handler = CoroutineExceptionHandler { _, throwable ->
        when (throwable) {
            is CancellationException -> {
                // cancel the socket connection here
                Log.d("CancellationException", "cancelled")
            }
            else -> onRegisterError(
                throwable.localizedMessage ?: "Coroutine Error"
            )

        }
    }

// Then inside ViewModel, fire up the okhttp client
val viewModelScopeJob = viewModelScope.launch(context = handler) {

            val someOtherJob = otherContext.launch {
                // launch suspend fun connectSocket()
            }

        }
// Then call cancel inside ViewModel like this:
viewModelScopeJob.cancel()

问题

viewModelScopeJob是父作业,在调用cancel()时,应取消其子作业并调用CancellationException,但不是。

问题

因此协程作业不会因为拦截器内部的Thread.sleep()不合作而被取消。

我的问题是:由于RetryInterceptor位于单独的类中,因此我无法使用delay()之类的方法,当{ {1}}被叫?

1 个答案:

答案 0 :(得分:0)

您需要进行两个修复。

首先,注册一个协程取消监听器,以取消OkHttp调用。您可以在Retrofit’s coroutine integration中看到一个示例。

continuation.invokeOnCancellation {
  cancel()
}

接下来,您需要在取消调用时中断线程睡眠。一种解决方法是使用EventListener。覆盖取消以中断OkHttp线程。您可以使用Thread.currentThread()中的callStart()保存对该线程的引用。您还应该覆盖callEnd()callFailed()来清除保存的参考。

Events页上有更多有关如何注册事件监听器工厂的信息,以便每个调用都有自己的EventListener实例。