Kotlin协程:Flow的异常处理

news/2024/7/10 19:42:05 标签: android, binder, Framework, kotlin, 互联网

示例代码如下:

kotlin">kotlin">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方法用于捕获上游流产生的异常,代码如下:

kotlin">kotlin">publicfun<T> Flow<T>.catch(action: suspendFlowCollector<T>.(cause: Throwable) -> Unit): Flow<T> =
    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方法来处理,代码如下:

kotlin">kotlin">internalsuspendfun<T> Flow<T>.catchImpl(
    collector: FlowCollector<T>
): Throwable? {
    // 保存下游流执行抛出的异常var fromDownstream: Throwable? = nulltry {
        // 触发上游流的执行
        collect {
            try {
                // 将上游流发送的值作为参数,触发下游流执行
                collector.emit(it)
            } catch (e: Throwable) { // 如果下游流在执行中发生异常,保存并抛出
                fromDownstream = e
                throw 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方法用于在上游的流全部执行完毕后最后执行,代码如下:

kotlin">kotlin">publicfun<T> Flow<T>.onCompletion(
    action: suspendFlowCollector<T>.(cause: Throwable?) -> Unit
): Flow<T> = unsafeFlow { // 创建一个Flow对象try {
        // 触发上游流的执行// this表示下游的FlowCollector
        collect(this)
    } catch (e: Throwable) {// 如果下游发生异常// 将异常封装成ThrowingCollector类型的FlowCollector,并回调参数action,
        ThrowingCollector(e).invokeSafely(action, e)
        // 抛出异常throw e
    }
    // 如果正常执行结束,会走到这里val sc = SafeCollector(this, currentCoroutineContext())
    try {
        // 回调执行参数action
        sc.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对象不会对执行的上下文进行检查,代码如下:

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

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

2. ThrowingCollector类

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

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

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

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

三. retryWhen方法

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

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

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

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


http://www.niftyadmin.cn/n/127752.html

相关文章

【C++】30h速成C++从入门到精通(继承)

继承的概念及定义继承的概念继承&#xff08;inheritance&#xff09;机制是面向对象程序设计使代码可以复用的重要手段&#xff0c;它允许程序员在保持原有类特性的基础上进行扩展&#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承呈现了面向对象程序…

字节3次都没裁掉的7年老测试。掌握设计业务与技术方案,打开上升通道!

前言职场中的那些魔幻操作&#xff0c;研发最烦的是哪个&#xff1f;“面对业务需求的时候&#xff0c;可能都听过这样一句话&#xff1a;这个很简单&#xff0c;直接开发&#xff0c;三天内上线&#xff1b;”朋友说&#xff1a;“产品听了流泪&#xff0c;测试见了崩溃&#…

在VM16.X虚拟机上安装Android-X86 9.0

前提&#xff1a;电脑已安装好VM虚拟机&#xff0c; 第一步&#xff1a;下载好安卓9.0系统 https://www.fosshub.com/Android-x86.html 第二步&#xff1a;开始安装 1、新建虚拟机&#xff08;按图片上的点一下步&#xff09; 点浏览&#xff0c;确定虚拟机位置 处理器配置建…

11.Maxwell 部署

Maxwell 部署 一、环境准备 1、确保服务器上已经安装好了zookeeper、kafka、MySQL软件&#xff1b; &#xff08;1&#xff09;启动zookeeper: /usr/app/zookeeper3.4/bin/zkServer.sh start&#xff08;2&#xff09;启动三台主题的kafka 启动&#xff1a;bin/kafka-serv…

Failed to configure a DataSource: ‘url‘ attribute

一 完整的错误信息 *************************** APPLICATION FAILED TO START *************************** Description: Failed to configure a DataSource: url attribute is not specified and no embedded datasource could be configured. Reason: Failed to dete…

五、spring-data-mongodb中Java字段映射注解

Spring Data MongoDB通过注解可以很方便的将Java对象映射到MongoDB文档结构中。常用的字段映射注解主要有: Document 注在实体类上&#xff0c;把ava类声明为mongodb的文档&#xff0c;​如果没有设置 collection 值&#xff0c;则对应mongo中和 Java 类名相同的 collection​…

封装几个有用的 Vue3 组合式API

本文将介绍如何使用Vue3来封装一些比较有用的组合API,主要包括背景、实现思路以及一些思考。 就我自己的感觉而言,Hook与Composition API概念是很类似的,事实上在React大部分可用的Hook都可以使用Vue3再实现一遍。 为了拼写方便,下文内容均使用Hook代替Composition API。相…

2023网络规划师论文怎么准备

网络规划设计师是软考高级考试科目之一&#xff0c;也是比较难的科目&#xff0c;据官方数据统计网规每年的通过率很低&#xff0c;而且每年只有下半年11月份考一次&#xff0c;如果是直接裸考&#xff0c;估计很悬哦~ 参加考试获得证书的过程就是一个学习网络规划系统知识的过…