用通俗易懂 + Android 开发实战的方式,详细讲解 Kotlin Flow 中的 retryWhen 操作符

一、一句话理解 retryWhen

retryWhen 是 Flow 的"智能重试"机制:当流抛出异常时,你可以决定"要不要重试"以及"重试几次"。

它就像一个"故障处理员":

  • Flow 执行出错了(比如网络请求失败)
  • retryWhen 问你:"要不要再试一次?"
  • 你根据错误类型、重试次数等条件,返回 true(重试)或 false(放弃)

二、为什么需要它?(对比普通 retry

Kotlin Flow 还有一个简单的 retry(n)

复制代码
flow.retry(3) // 出错就重试最多 3 次,无条件

但现实开发中,你往往需要更精细的控制,比如:

  • 只对网络超时 重试,不对认证失败重试
  • 重试时加延迟(避免雪崩)
  • 最多重试 2 次
  • 用户点击"重试按钮"后再试(手动重试)

👉 这时候就要用 retryWhen


三、基本语法

复制代码
flow.retryWhen { cause: Throwable, attempt: Long ->
    // cause:抛出的异常
    // attempt:当前是第几次重试(从 0 开始)
    // 返回 true:重试
    // 返回 false:不再重试,异常向上抛出
}

⚠️ 注意:retryWhen 的 lambda 是 suspend 函数 ,你可以在里面 delay()


四、Android 实战例子 📱

场景:网络请求失败时智能重试

假设你有一个 Repository 方法:

复制代码
suspend fun fetchUser(): User {
    // 模拟网络请求,可能抛出 IOException(网络问题)或 AuthException(登录过期)
}

在 ViewModel 中用 Flow 包装它:

复制代码
val userFlow = flow {
    emit(fetchUser()) // 可能抛异常
}

现在加上 retryWhen

复制代码
val userWithRetry = userFlow.retryWhen { cause, attempt ->
    val maxRetries = 2

    // 1. 如果重试次数超过上限,不再重试
    if (attempt >= maxRetries) {
        return@retryWhen false
    }

    // 2. 只对特定异常重试(比如网络问题)
    if (cause is IOException || cause is SocketTimeoutException) {
        // 3. 重试前等一会儿(指数退避)
        delay((1000 * (attempt + 1)).toLong()) // 第1次等1秒,第2次等2秒
        return@retryWhen true // 重试!
    }

    // 其他异常(如认证失败)不重试
    false
}

✅ 这样:

  • 网络抖动?自动重试 2 次,每次间隔更长
  • Token 过期?直接报错,让用户重新登录

五、配合 UI:显示"重试按钮"

有时候你不想自动重试,而是等用户点击"重试"按钮再试。

这时候可以把重试逻辑和 UI 事件结合:

复制代码
class UserViewModel : ViewModel() {
    private val _retrySignal = MutableSharedFlow<Unit>()
    
    val uiState = flow {
            emit(fetchUser())
        }
        .retryWhen { cause, _ ->
            // 等待用户点击"重试"
            _retrySignal.first() // 挂起,直到收到信号
            true // 收到信号就重试
        }
        .catch { error ->
            // 如果最终还是失败(比如用户没点重试),转为 Error 状态
            emit(UiState.Error(error))
        }
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), UiState.Loading)

    fun onRetryClicked() {
        viewModelScope.launch {
            _retrySignal.emit(Unit) // 发送重试信号
        }
    }
}

UI 中:

复制代码
when (uiState) {
    is UiState.Error -> Button(onClick = { viewModel.onRetryClicked() }) { Text("重试") }
}

✅ 这是 "手动重试" 的经典模式!


六、注意事项 & 常见坑

1. ❌ 不要无限重试!
复制代码
retryWhen { _, _ -> true } // 永远重试 → 死循环!

✅ 一定要加次数限制或条件判断。

2. ⏱️ 重试前加 delay

否则可能瞬间发起大量请求,压垮服务器。

3. 🧪 只对"可恢复的错误"重试
  • ✅ 可重试:网络超时、503 服务不可用
  • ❌ 不可重试:401 未授权、400 参数错误、数据不存在
4. 🔁 retryWhen 会重新执行整个上游 Flow

比如:

复制代码
flow {
    println("开始请求") // 每次重试都会打印!
    emit(api.call())
}.retryWhen { ... }

七、对比其他重试方式

方式 特点 适用场景
retry(n) 简单,无条件重试 n 次 快速原型、简单任务
retryWhen 灵活,可判断异常、加延迟、手动触发 Android 生产环境推荐
手动循环 + try/catch 完全控制 复杂逻辑,但代码啰嗦

八、总结一句话

retryWhen 是 Flow 中实现"智能、可控重试"的核心操作符,特别适合 Android 开发中处理网络请求失败、数据库临时错误等场景,让你的 App 更健壮、用户体验更好。

配合 catchStateFlow 和 UI 重试按钮,你可以构建出非常专业的错误处理流程!


现在使用 Retrofit + Coroutines + Flow 做网络层,retryWhen 几乎是必备技能!

相关推荐
词元Max15 分钟前
2.5 Python 类型注解与运行时类型检查
开发语言·python
福楠30 分钟前
现代C++ | C++14甜点特性
linux·c语言·开发语言·c++
charlie11451419132 分钟前
嵌入式C++教程实战之Linux下的单片机编程:从零搭建 STM32 开发工具链(4)从零构建 STM32 构建系统
linux·开发语言·c++·stm32·单片机·学习·嵌入式
钰fly36 分钟前
Halcon联合编程适应图像的方法(picture)
开发语言·前端·javascript
束尘38 分钟前
Vue3一键复制图片到剪贴板
开发语言·javascript·vue.js
老王熬夜敲代码43 分钟前
LangGraph的状态
开发语言·langchain
2401_8274999944 分钟前
python核心语法03-数据存储容器
开发语言·python
AC赳赳老秦1 小时前
自媒体博主:OpenClaw多Agent协同,实现选题-创作-审核全流程自动化
运维·服务器·开发语言·人工智能·自动化·媒体·openclaw
酉鬼女又兒1 小时前
零基础快速入门前端DOM 节点操作核心知识点及蓝桥杯 Web 应用开发考点解析(可用于备赛蓝桥杯Web应用开发)
开发语言·前端·javascript·职场和发展·蓝桥杯
木易 士心1 小时前
Android应用启动流程源码级解析
android