Kotlin协程:Flow的异常处理
创始人
2024-05-29 22:13:55
0

示例代码如下:

launch(Dispatchers.Main) {// 第一部分flow {emit(1)throw NullPointerException("e")}.catch {Log.d("liduo", "onCreate1: $it")}.collect {Log.d("liudo", "onCreate2: $it")}// 第二部分flow {emit(1)}.onCompletion {Log.d("liduo", "onCreate3: $it")}.collect {Log.d("liudo", "onCreate4: $it")}// 第三部分flow {emit(1)throw NullPointerException("e")}.retryWhen { cause, attempt ->cause !is NullPointerException && attempt <= 2}.collect {Log.d("liudo", "onCreate5: $it")}
}
复制代码

一.catch方法

catch方法用于捕获上游流产生的异常,代码如下:

publicfun Flow.catch(action: suspendFlowCollector.(cause: Throwable) -> Unit): Flow =flow { // 创建Flow对象// 触发上游流的执行,并捕获异常val exception = catchImpl(this)// 捕获到异常,则回调action处理if (exception != null) action(exception)}
复制代码

catch方法是Flow接口的扩展方法,并返回一个Flow类型的对象。在catch方法中,调用flow方法创建了一个Flow对象。

catch方法核心是通过catchImpl方法实现异常的捕获,如果成功捕获到异常,则回调参数action处理。这里参数action是FlowCollector接口的扩展方法,因此可以继续调用emit方法,向下游发送值。

1.catchImpl方法

当下游调用collect方法时,会触发catch方法创建的Flow对象的执行,并调用catchImpl方法来处理,代码如下:

internalsuspendfun Flow.catchImpl(collector: FlowCollector
): Throwable? {// 保存下游流执行抛出的异常var fromDownstream: Throwable? = nulltry {// 触发上游流的执行collect {try {// 将上游流发送的值作为参数,触发下游流执行collector.emit(it)} catch (e: Throwable) { // 如果下游流在执行中发生异常,保存并抛出fromDownstream = ethrow e}}} catch (e: Throwable) { // 这里捕获的异常,可能为上游流的异常——collect方法,// 也可能为下游流的异常——emit方法// 如果异常是下游流产生的异常,或者是协程取消时抛出的异常if (e.isSameExceptionAs(fromDownstream) || e.isCancellationCause(coroutineContext)) {throw e // 再次抛出,交给下游处理} else { // 如果是上游流的异常且不为协程取消异常return e // 成功捕获}}// 未捕获到异常,返回returnnull
}
复制代码

catchImpl方法是Flow接口的扩展方法,因此在调用collect方法时,会触发上游流的执行。catchImpl方法的核心在于:将上游发出的值传递给下游处理,并对这一过程进行了异常捕获操作。

二. onCompletion方法

onCompletion方法用于在上游的流全部执行完毕后最后执行,代码如下:

publicfun Flow.onCompletion(action: suspendFlowCollector.(cause: Throwable?) -> Unit
): Flow = unsafeFlow { // 创建一个Flow对象try {// 触发上游流的执行// this表示下游的FlowCollectorcollect(this)} catch (e: Throwable) {// 如果下游发生异常// 将异常封装成ThrowingCollector类型的FlowCollector,并回调参数action,ThrowingCollector(e).invokeSafely(action, e)// 抛出异常throw e}// 如果正常执行结束,会走到这里val sc = SafeCollector(this, currentCoroutineContext())try {// 回调执行参数actionsc.action(null)} finally {sc.releaseIntercepted()}
}
复制代码

onCompletion方法是Flow接口的扩展方法,因此在调用collect方法时,会触发上游流的执行。同时,传入this作为参数,this表示下游流调用collect方法时,传给unsafeFlow方法创建的Flow对象的类型为FlowCollector的对象。onCompletion方法的核心在于:将自身创建的Flow对象作为上游与下游的连接容器,只有当流全部执行完毕或执行过程中发生异常,collect方法才可以执行完成,继续向下执行。

1.unsafeFlow方法

unsafeFlow方法用于创建一个类型为Flow对象,与之前在Kotlin协程:Flow基础原理提到过的SafeFlow类相比,unsafeFlow方法创建的Flow对象不会对执行的上下文进行检查,代码如下:

@PublishedApiinternalinlinefununsafeFlow(@BuilderInferencecrossinline block: suspendFlowCollector.() -> Unit): Flow {// 返回一个匿名内部类returnobject : Flow {// 回调collect方法是直接执行blockoverridesuspendfuncollect(collector: FlowCollector) {collector.block()}}
}
复制代码

虽然onCompletion方法内部使用unsafeFlow方法创建Flow对象,但却使用了SafeCollector类。根据之前在Kotlin协程:Flow基础原理提到的,调用SafeCollector类的emit方法时,会对上下文进行检查。因此实际效果与使用SafeFlow类效果相同。

2. ThrowingCollector类

ThrowingCollector类也是一种FlowCollector,用于包裹异常。当调用它的emit方法时,会抛出包裹的异常,代码如下:

privateclassThrowingCollector(privateval e: Throwable) : FlowCollector {overridesuspendfunemit(value: Any?) {// 抛出异常throw e}
}
复制代码

为什么要重新创建ThrowingCollector对象,而不使用下游的FlowCollector对象呢?

为了防止当下游的流执行失败时,onCompletion方法的action参数执行时调用emit方法发送数据,这样会导致onCompletion方法作为在“finially代码块”使用时不是最后执行的方法。onCompletion方法搭配与catch方法,实现try-catch-finially代码块的效果。

三. retryWhen方法

retryWhen方法与catch方法类似,都可以用于捕获上游流产生的异常。但两者不同之处在于,retryWhen方法还可以根据“异常类型”和“重试次数”来决定是否要再次触发上游流的执行,而且当retryWhen方法不打算再次触发上游流的执行时,捕获的异常会被抛出,代码如下:

// 参数cause表示捕获到的异常// 参数attempt表示重试的次数// 参数predicate返回true表示重新触发上游流的执行publicfun Flow.retryWhen(predicate: suspendFlowCollector.(cause: Throwable, attempt: Long) -> Boolean): Flow =// 创建一个Flow对象flow {// 记录重试次数var attempt = 0L// 表示是否重新触发var shallRetry: Booleando {// 复位成falseshallRetry = false// 触发上游流的执行,并捕获异常val cause = catchImpl(this)// 如果捕获到异常if (cause != null) {// 用户判断,是否要重新触发if (predicate(cause, attempt)) {// 表示要重新触发shallRetry = true// 重试次数加1attempt++} else { // 如果用户不需要重新触发// 则抛出异常throw cause}}// 判断是否重新触发} while (shallRetry)}
复制代码

retryWhen方法是Flow接口的扩展方法。retryWhen方法的核心通过catchImpl方法实现对上游流的触发及异常捕获,并加入了由用户判断的重试逻辑实现。

点击下方卡片获取Android学习资料!

相关内容

热门资讯

团鞍山市委举行鞍山“青年影城”... 中国青年报客户端讯(中青报·中青网记者 王晨)7月5日,团辽宁省鞍山市委在铁西区苏宁影城举行鞍山“青...
上海游戏新规将打开外企增长空间 【#上海游戏新规将打开外企增长空间#】#上海占国内游戏产值三分之一#近日,上海市人民政府办公厅印发《...
里昂:升H&H国际控股... .ct_hqimg {margin: 10px 0;} .hqimg_wrapper {text-a...
新华财经晚报:香港目标今年内可... 转自:新华财经【重点关注】•国家外汇管理局:受汇率折算和资产价格变化等因素综合作用 6月外汇储备规模...
求灼眼之原罪降临全本(包括数个... 求灼眼之原罪降临全本(包括数个外传)哥们死心吧,奇迹的精着呢
江苏久吾高科两高管拟减持,王肖... 江苏久吾高科技股份有限公司于2025年7月7日发布公告,披露公司副总经理王肖虎、程军军的减持计划。减...
想问一下太上章里有没有风君子 ... 想问一下太上章里有没有风君子 书荒厉害暂时没出现这个人,但相关人物已经有了,就是“句芒”。估计在文末...
百裕制药盐酸决奈达隆片启动生物... 药物临床试验登记与信息公示平台数据显示,成都百裕制药股份有限公司的盐酸决奈达隆片在中国健康受试者空腹...
美农生物:唐旭减持计划实施完毕... 2025年7月7日,上海美农生物科技股份有限公司发布《关于持股5%以上股东的一致行动人减持股份计划期...
贵阳银行拟任董事梁诚因工作原因... 贵阳银行(601997)发布公告,公司拟任董事梁诚因工作原因辞去其拟担任的董事及董事会发展战略委员会...