所以我正在使用Coroutines和Okhttp来连接websocket。
// 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()
之类的方法,当调用viewModelScopeJob.cancel()
时应如何更改代码以取消重试?
您需要进行两个修复。
[首先,注册一个协程取消监听器,以取消OkHttp调用。您可以在Retrofit’s coroutine integration中看到一个示例。
continuation.invokeOnCancellation {
cancel()
}
接下来,当取消调用时,您需要中断线程睡眠。处理此问题的一种方法是使用EventListener。覆盖取消以中断OkHttp线程。您可以使用Thread.currentThread()
中的callStart()
保存对该线程的引用。您还应该覆盖callEnd()
和callFailed()
以清除该保存的参考。
Events页面上有更多有关如何注册事件侦听器工厂的信息,以便每个调用都具有自己的EventListener
实例。