第 37 课2026.08.22
网络短暂中断等临时故障可能很快恢复。与其立即展示错误,可以先自动重试;同时限制次数并筛选异常类型,避免无限重试或掩盖程序错误。
第 37 课:retryWhen 有限重试
知识点
使用 retryWhen() 在 Flow 遇到可恢复异常时,进行有限次数的延迟重试。
用途
网络短暂中断等临时故障可能很快恢复。与其立即展示错误,可以先自动重试;同时限制次数并筛选异常类型,避免无限重试或掩盖程序错误。
核心概念
retryWhen要放在catch前面:先尝试恢复,最终失败再交给catch。- 回调参数
cause是本次异常,attempt是重试编号,从0开始。 - 返回
true会重新收集上游,返回false会把异常继续传给下游。 - 回调是挂起函数,可以调用
delay()实现重试间隔。 attempt < 2表示最多重试 2 次;加上首次请求,总共最多尝试 3 次。
示例代码
下面只重试 TemporaryNetworkException,两次重试前分别等待 500 毫秒和 1 秒:
kotlin
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.retryWhen
import kotlinx.coroutines.flow.stateIn
data class Item(val id: Long, val name: String)
class TemporaryNetworkException(message: String) : Exception(message)
interface ItemRepository {
fun observeItem(id: Long): Flow<Item?>
}
sealed interface DetailUiState {
data object Loading : DetailUiState
data class Content(val item: Item) : DetailUiState
data class Error(val message: String) : DetailUiState
}
class DetailViewModel(
repository: ItemRepository,
itemId: Long,
) : ViewModel() {
val uiState: StateFlow<DetailUiState> =
repository.observeItem(itemId)
.map<Item?, DetailUiState> { item ->
if (item == null) {
DetailUiState.Error("数据不存在")
} else {
DetailUiState.Content(item)
}
}
.retryWhen { cause, attempt ->
val canRetry =
cause is TemporaryNetworkException && attempt < 2
if (canRetry) {
delay((attempt + 1) * 500L)
}
canRetry
}
.catch { error ->
emit(DetailUiState.Error(error.message ?: "加载失败"))
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = DetailUiState.Loading,
)
}第一次失败时 attempt == 0,等待 500 毫秒;第二次失败时 attempt == 1,等待 1 秒。再次失败时 attempt == 2,不再重试,异常由 catch 转成错误状态。
5 分钟练习
修改示例中的 retryWhen:
- 仍然只重试
TemporaryNetworkException。 - 最多重试 3 次。
- 每次重试前固定等待 1 秒。
参考答案
kotlin
.retryWhen { cause, attempt ->
val canRetry =
cause is TemporaryNetworkException && attempt < 3
if (canRetry) {
delay(1_000L)
}
canRetry
}这里最多重试 3 次,加上首次请求,总共最多尝试 4 次。
与上一课的联系
上一课用 catch() 把 Flow 的最终异常转换成可渲染的错误状态;今天在它前面加入 retryWhen(),先尝试恢复临时故障,重试耗尽或遇到不可恢复异常后,仍由 catch() 统一收尾。