Android开发中有一个典型场景:网络恳求失利后重试:一般的逻辑是弹出一个Dialog提醒用户“网络恳求失利”,并提供重试的按钮。

Kotlin协程利用CoroutineContext实现网络请求失败后重试逻辑

如果当前页面只有一个网络恳求,那么逻辑就很简单了:只需要再调用一下建议这个网络恳求的办法就能够了。而当一个页面有多个网络恳求时,我常用的办法为失利回调加状况,依据不同的状况调用不同的办法。但是这个办法不免有些繁琐,也有点不安全。首先,你要额外的增加状况,并将它传来传去。有些情况下,你甚至还需要从头初始化网络恳求参数。更要命的是:你还要办理这个状况,一旦办理不善,就会导致调用了不该调用的办法,引进严峻的BUG。

直到有一天我看到CoroutineExceptionHandler,灵光突现——能够运用协程上下文(可在我之前的博客Kotlin中的协程、上下文和作用域中了解更过关于协程和上下文的信息)来保存将来或许需要重试的网络恳求和Request数据,这样就能处理上面的问题了。

因为我所开发的大多数项目都是采用ViewModel完成网络恳求逻辑和UI层的解耦,而网络恳求基本上是采用Coroutine+Retrofit的办法完成的,基本上都是运用viewModelScope。

viewModelScope.launch() {
	request()
}

viewModelScope本质上是一个ViewModel的扩展函数,利用它能够便捷的在ViewModel创立协程,具体的代码就不展开了。默认情况下,它的CoroutineContext由Job和CoroutineDispatcher组成。而协程的上下文本质上便是一个完成了key-value拜访的办法的链表结构。咱们能够通过承继AbstractCoroutineContextElement的办法完成自定义的CoroutineContext上下文:

class RetryCallback(val callback: () -> Unit) : AbstractCoroutineContextElement(RetryCallback) {
    companion object Key : CoroutineContext.Key<RetryCallback>
}

紧接着,当网络恳求产生反常时凭借CoroutineExceptionHandler获取到咱们需要从头执行的操作:

val coroutineExceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
     val callback = coroutineContext[RetryCallback]
        ?.callback
}

紧接着,要将coroutineExceptionHandler增加到建议网络恳求的协程上下文里:

viewModelScope.launch(exceptionHandler
      + RetryCallback { request() }) { 
	request()
}

此时,只要在建议网络恳求的页面里获取到callback,并在点击重试按钮的时分调用它,就能完成重试的逻辑。

进一步对它进行封装并增加失利后主动重试逻辑,创立供ViewModel运用的接口,用来处理网络恳求过错的后续逻辑:

interface ViewModelFailed {
    /**
     * @param throwable:反常信息
     * @param callback:需要重试的函数
     * */
    fun requestFailed(throwable: Throwable, callback: () -> Unit)
}

为它创立扩展函数,用来创立CoroutineExceptionHandlerRetryCallback上下文实例:

/**
 * @param autoReTry:是否主动重试
 * @param callback:需要重试的函数
 * */
fun ViewModelFailed.initRetry(autoReTry: Boolean = false, callback: () -> Unit) =
    CoroutineExceptionHandler { coroutineContext, throwable ->
        val retryCallBack = {
            coroutineContext[RetryCallback]
                ?.callback?.invoke()
        }
        if (autoReTry) {
            //主动开端重试逻辑
            onRetry()
            retryCallBack.invoke()
        } else {
            //不主动开端重试,后续操作交给用户决议
            requestFailed(throwable) {
                retryCallBack
            }
        }
    } + RetryCallback(callback)

ViewModel需要完成ViewModelFailed接口,并在建议网络恳求的协程中调用initRetry办法增加反常处理上下文:


class MainViewViewModel : ViewModel(), ViewModelFailed {
    val liveData: MutableLiveData<BaseData> = MutableLiveData()
      /**
     * @param num:用来演示Request恳求数据
     * @param repeat:失利后主动重试的次数
     * */
    fun request(num: Int, repeat: Int = 0) {
        liveData.value = BaseData.loading()
        viewModelScope.launch(initRetry(repeat > 0) {
            request(num,repeat - 1)
        }) {
            liveData.value = BaseData.success(simulateHttp(num))
        }
    }
    private suspend fun simulateHttp(num: Int) = withContext(Dispatchers.IO) {
        //模拟网络恳求
        ...
    }
    override fun requestFailed(throwable: Throwable, callback: () -> Unit) {
        //处理失利逻辑
        dialog()
        //重试
        callback.invoke()
    }
    override fun onRetry() {
    }
}

最后附上完整代码和运用Demo