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

前言

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

如果当前页面只有一个网络请求,那么逻辑就很简单了:只需要再调用一下发起这个网络请求的方法就可以了。而当一个页面有多个网络请求时,我常用的办法为失败回调加状态,根据不同的状态调用不同的方法。但是这个方法不免有些繁琐,也有点不安全。首先,你要额外的增加状态,并将它传来传去。有些情况下,你甚至还需要重新初始化网络请求参数。更要命的是:你还要管理这个状态,一旦管理不善,就会导致调用了不该调用的方法,引入严重的BUG。

直到有一天我看到CoroutineExceptionHandler,灵光突现——可以使用协程上下文来保存将来可能需要重试的网络请求和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() {
        
    }

} 

最后

Android学习是一条漫长的道路,我们要学习的东西不仅仅只有表面的 技术,还要深入底层,弄明白下面的 原理,只有这样,我们才能够提高自己的竞争力,在当今这个竞争激烈的世界里立足。

人生不可能一帆风顺,有高峰自然有低谷,要相信,那些打不倒我们的,终将使我们更强大,要做自己的摆渡人。

我把自己这段时间整理的Android最重要最热门的学习方向资料放在了下方二维码中,里面还有不同方向的自学编程路线、面试题集合/面经、及系列技术文章等。**

资源持续更新中,欢迎大家一起学习和探讨。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值