Kotlin协程异步任务

分享几个网络请求开发中常用的 flow 方法

一、基本任务

1. 倒计时任务

fun countDownFlow(dispatcher: CoroutineDispatcher,  // 指定协程运行的调度器scope: CoroutineScope,  // 指定作用域total: Int,  // 总计时数onTick: (Int) -> Unit,  // 倒计时时执行的任务onStart: (() -> Unit)? = null,  // 启动倒计时时执行onFinish: (() -> Unit)? = null  // 倒计时结束时执行
): Job {return flow {for (i in total downTo 0) {emit(i)if (i != 0) delay(1000)}}.flowOn(dispatcher).onEach { onTick.invoke(it) }.onStart { onStart?.invoke() }.onCompletion { onFinish?.invoke() }.launchIn(scope)
}

一个简单的倒计时任务,很好理解不做解析。适用于需要执行倒计时任务的场景例如闪屏页中倒计时完成后的页面跳转。要注意的一点是生命周期的管理,如果在别处提前执行了倒计时结束的任务的话记得手动取消这个任务,否则就会重复执行。倒计时间隔写死了1秒,可自行修改或者改为参数传入,附一个调用例子:

viewBinding.countDown.setOnClickListener {countDownJob?.let { it.cancel() }startActivity(Intent(this,MainActivity::class.java))finish()
}
countDownJob=countDownFlow(Dispatchers.Main,lifecycleScope,2,onTick = {viewBinding.countDown.text = "${it.plus(1).toString()} S"},onFinish = {startActivity(Intent(this,MainActivity::class.java))finish()}
)

2. 无限循环任务

fun infiniteLoop(scope: CoroutineScope,  // 指定作用域dispatcher: CoroutineDispatcher=Dispatchers.IO,  // 指定调度器,默认为IO线程intervalMillis: Long,  // 循环间隔action: () -> Unit  // 循环任务
): Job {return scope.launch(dispatcher) {while (isActive) {action()delay(intervalMillis) // 指定间隔}}
}

二、异步任务

1. 简单请求

在实现方法之前不妨先想一想我们在开发中通常会遇到哪些异步情景,首先想到的肯定就是最简单也是最基本的异步请求任务了,例如:在子线程中执行耗时请求并在请求完成获取结果后通知主线程更新UI,再细致一些还能加上请求之前弹出loading,请求完成后隐藏loading... 那么此时我们就有了一个大致的请求方法结构了:

suspend fun <T> requestFlowResponse1(before: (() -> Unit)? = null,  // 前置准备after: (() -> Unit)? = null,  // 后置工作request: suspend () -> Response<T>?,  // 请求errorHandler: ((Throwable) -> Unit)? = null,  // 错误处理timeout: Long = 10  // 指定超时限制
): Flow<Response<T>?> {val flow = flow {// 设置超时val response = withTimeout(timeout * 1000) {request()}if(response!=null){if (!response.isSuccessful()) {throw Throwable(response.errorMsg)}}else{throw Throwable("无法获取数据")}emit(response)}.flowOn(Dispatchers.IO).onStart {Log.e(TAG, "请求数据1")before?.invoke()  // 前置准备,一般是弹出加载框}// 请求完成:成功/失败.onCompletion {if(it==null){Log.e(TAG, "请求完成1")after?.invoke()}else{Log.e(TAG, "请求异常1")}}// 捕获异常.catch { e ->Log.e(TAG, "请求异常1")e.printStackTrace()errorHandler?.invoke(e)}return flow
}

在这里我根据接口文档定义了一个响应体对象结构以方便异步方法兼容更多的情景,其余部分还可以根据实际情况自行修改/优化

public class Response<T> {public T data = null;public int code = 0;public String message = "";public boolean isSuccessful(){return code==200;}@Overridepublic String toString() {return "Response{" +"code=" + code +", message='" + message + '\'' +", data=" + data +'}';}
}

2. 复合请求

某些时候我们会遇到稍微复杂一些的情况,例如我们需要请求多个接口之后再根据获取的结果进行下一步处理,这时我们就可以利用 combine 方法把多个请求结合起来

suspend fun <T,R> requestFlowConcurrent1(requests: List<suspend () -> Response<T>?>,requestBefore: (() -> Unit)? = null,requestAfter: (() -> Unit)? = null,requestErrorHandler: ((Throwable) -> Unit)? = null,before: (() -> Unit)? = null,after: (() -> Unit)? = null,errorHandler:((Throwable)->Unit)? = null,parser:(Array<Response<T>?>) -> R?
) : R? {var result:R? = nullval flows = requests.map { request ->requestFlowResponse1(requestBefore, requestAfter, request, requestErrorHandler)}combine(flows){ results ->Log.e(TAG, "执行数据处理2")for ((index, baseResponse) in results.withIndex()) {Log.i(TAG, "$index : $baseResponse" )}parser.invoke(results)}.flowOn(Dispatchers.IO).onStart {Log.e(TAG, "任务启动2")before?.invoke()}.onCompletion {if(it==null){Log.e(TAG, "任务完成2")after?.invoke()}else{Log.e(TAG, "任务异常2")}}.flowOn(Dispatchers.Main).catch {Log.e(TAG, "任务异常2")errorHandler?.invoke(it)}.collect {Log.e(TAG, "输出结果2: ${it.toString()}", )result = it}return result
}

3. 简单多任务

再次扩展一下,让我们把他改造为不仅仅局限于网络请求而是适用于其他所有的耗时任务的方法,例如:我需要先从网络请求获取某个数据然后再从数据库中取出某个数据最后再把做运算然后切回主线程更新UI。这时因为从数据库或是其他方式取出来的对象不符合之前定义 Response 对象所以原本的简单请求方法也就不适用了,我们需要稍微改造一下:

suspend fun <T:Any?> requestFlowResponse2(before: (() -> Unit)? = null,  // 前置准备after: (() -> Unit)? = null,  // 后置工作request: suspend () -> T,  // 请求errorHandler: ((Throwable) -> Unit)? = null,  // 错误处理timeout: Long = 10
): Flow<T> {val flow = flow {// 设置超时val response = withTimeout(timeout * 1000) {request()}if(response!=null){emit(response)}else{throw Exception("无法获取数据")}}.flowOn(Dispatchers.IO).onStart {Log.e(TAG, "请求数据3")before?.invoke()  // 前置准备,一般是弹出加载框}// 请求完成:成功/失败.onCompletion {if(it==null){Log.e(TAG, "请求成功3")after?.invoke()}else{Log.e(TAG, "请求异常3")}}.flowOn(Dispatchers.Main)// 捕获异常.catch { e ->Log.e(TAG, "请求错误3")e.printStackTrace()errorHandler?.invoke(e)}return flow
}

一般情况下三个耗时任务已经能够满足大部分的使用情景了

suspend fun <L:Any?, X:Any?, T:Any?, R> requestFlowConcurrent2(request1: suspend () -> L,request2: (suspend () -> X?)?=null,request3: (suspend () -> T?)?=null,requestBefore: (() -> Unit)? = null,requestAfter: (() -> Unit)? = null,requestErrorHandler: ((Throwable) -> Unit)? = null,before: (() -> Unit)? = null,after: (() -> Unit)? = null,errorHandler:((Throwable)->Unit)? = null,parser: (L, X?, T?)->R?
):R? {var result:R? = nullval flow1 = requestFlowResponse2(requestBefore, requestAfter, request1, requestErrorHandler)val flow2 = if(request2!=null){requestFlowResponse2(requestBefore, requestAfter, request2, requestErrorHandler)}else{flow { emit(null) }}val flow3 = if(request3!=null){requestFlowResponse2(requestBefore, requestAfter, request3, requestErrorHandler)}else{flow { emit(null) }}combine(flow1,flow2,flow3){ l,x,t ->Log.i(TAG, "执行数据处理4\nl:$l \nx:$x \nt:$t" )parser.invoke(l,x,t)}.flowOn(Dispatchers.IO).onStart {Log.e(TAG, "任务启动4")before?.invoke()}.onCompletion {if(it==null){Log.e(TAG, "任务完成4")after?.invoke()}else{Log.e(TAG, "任务异常4")}}.flowOn(Dispatchers.Main).catch {Log.e(TAG, "任务异常4")errorHandler?.invoke(it)}.collect {Log.e(TAG, "输出结果4: ${it.toString()}", )result = it}return result
}

模拟使用例子:
从本地数据库获取用户列表,从网络请求获取热键列表,再将用户数量和热键的数量相加输出最终结果

viewModelScope.launch {val data = requestFlowConcurrent4<MutableList<User>?, BaseResponse<MutableList<Hotkey>>,Unit,Int>(request1 = {userService.getLocalUsers()},request2 = {userService.getHotkey()},request3 = null,requestBefore = {loge("请求开始")},requestAfter = {loge("请求完成")},requestErrorHandler = {loge("请求失败:${it.message}")},before = {loge("任务启动")},after = {loge("任务完成")},errorHandler = {loge("任务失败:${it.message}")},parser = { l,x,t ->var a = 0;var b = 0;l?.let { a=it.size }x?.let {if(it.isSuccessful){b=it.data.size}}return@requestFlowConcurrent4 a+b})data?.let { total.postValue(it) }}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/20592.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Mac安装Docker Desktop搭建K8s集群,解决镜像无法下载的问题

使用 Docker Desktop可以在本地方便地搭建出 K8s集群&#xff0c;但开启 K8s集群后往往会遇到 K8s 镜像拉取失败问题&#xff0c;本文旨在解决该问题&#xff0c;从而在本地搭建 K8s 集群。 安装Docker Desktop 安装 Docker Desktop 建议安装历史版本, 不建议安装最新版。因为…

【Leecode】Leecode刷题之路第54天之旋转矩阵

题目出处 54-螺旋矩阵-题目出处 题目描述 个人解法 思路&#xff1a; todo代码示例&#xff1a;&#xff08;Java&#xff09; todo复杂度分析 todo官方解法 54-旋转矩阵-官方解法 方法1&#xff1a;模拟 思路&#xff1a; 代码示例&#xff1a;&#xff08;Java&#xff…

【YOLOv8】安卓端部署-1-项目介绍

【YOLOv8】安卓端部署-1-项目介绍 1 什么是YOLOv81.1 YOLOv8 的主要特性1.2 YOLOv8分割模型1.2.1 YOLACT实例分割算法之计算掩码1.2.1.1 YOLACT 的掩码原型与最终的掩码的关系1.2.1.2 插值时的目标检测中提取的物体特征1.2.1.3 coefficients&#xff08;系数&#xff09;作用1.…

Cesium教程01_实现Cartesian3 三维坐标操作

在 Vue 项目中使用 Cesium 实现 Cartesian3 三维坐标操作 目录 一、引言二、Cesium 与 Cartesian3 的优势三、示例应用&#xff1a;在地图上标注和计算距离 1. 项目结构2. 主要代码实现3. 运行与效果 四、代码讲解与扩展 1. Cartesian3 的基础操作2. 距离计算与中点标注 五、…

Qt5-雷达项目

界面: widget.h #ifndef WIDGET_H #define WIDGET_H#include <QTimer> #include <QWidget>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent nullptr);~Widget(); pr…

A040-基于springboot的智能停车计费系统设计与实现

&#x1f64a;作者简介&#xff1a;在校研究生&#xff0c;拥有计算机专业的研究生开发团队&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的网站项目。 代码可以查看文章末尾⬇️联系方式获取&#xff0c;记得注明来意哦~&#x1f339; 赠送计算机毕业设计600…

数据结构初识

目录 1.初识 2.时间复杂度 常见时间复杂度举例&#xff1a; 3.空间复杂度 4.包装类&简单认识泛型 4.1装箱和拆箱 5.泛型 6.泛型的上界 7.泛型方法 8.List接口 1.初识 1.多画图 2.多思考 3.多写代码 4.多做题 牛客网-题库/在线编程/剑指offer 算法篇&#xff1a…

CUDA HOME does not exist, unable to compile CUDA op(s),已解决

有一个服务器上没有/usr/loacl/cuda&#xff0c;我也没有权限在这个目录装cuda&#xff0c;使用pip装完torch&#xff0c;llama factory使用时出现&#xff1a; 应该是本地没有nvcc相关执行文件。 先使用了&#xff1a; conda install -c cudatoolkit-dev不管用&#xff0c; …

杰发科技AC7801——ADC定时器触发的简单使用

使用场景 在需要多次采样结果的情况下&#xff0c;比如1s需要10w次的采样结果&#xff0c;可以考虑使用定时器触发采样&#xff0c;定时器设置多少的时间就会多久采样转换一次。 再加上使用dma&#xff0c;采样的结果直接放在dma的数组里面。 实现了自动采样&#xff0c;自动…

【有啥问啥】基于文本的图像检索(Text-Based Image Retrieval, TBIR)技术详解

基于文本的图像检索&#xff08;Text-Based Image Retrieval, TBIR&#xff09;技术详解 1. 背景理论知识 1.1 什么是基于文本的图像检索&#xff08;TBIR&#xff09;&#xff1f; 基于文本的图像检索&#xff08;Text-Based Image Retrieval&#xff0c;简称TBIR&#xff…

探索PyMuPDF:Python中的强大PDF处理库

文章目录 **探索PyMuPDF&#xff1a;Python中的强大PDF处理库**第一部分&#xff1a;背景第二部分&#xff1a;PyMuPDF是什么&#xff1f;第三部分&#xff1a;如何安装这个库&#xff1f;第四部分&#xff1a;至少5个简单的库函数使用方法第五部分&#xff1a;结合至少3个场景…

HarmonyOS Next 关于页面渲染的性能优化方案

HarmonyOS Next 关于页面渲染的性能优化方案 HarmonyOS Next 应用开发中&#xff0c;用户的使用体验至关重要。其中用户启动APP到呈现页面主要包含三个步骤&#xff1a; 框架初始化页面加载布局渲染 从页面加载到布局渲染中&#xff0c;主要包含了6个环节&#xff1a; 执行页…

已解决centos7 yum报错:cannot find a valid baseurl for repo:base/7/x86_64的解决方案

出现cannot find a valid baseurl for repo:base/7/x86_64错误通常是由于YUM仓库源无法找到或无法访问&#xff0c;导致YUM无法正常工作。这种情况常见于CentOS 7系统。解决这个问题需要检查几个方面&#xff0c;如网络连接、DNS设置和YUM仓库源配置。 &#x1f9d1; 博主简介&…

架构图解析:如何构建高效的微服务系统

在当今的数字化浪潮中&#xff0c;构建高效、灵活且可扩展的系统已成为企业的重要目标。微服务架构作为一种先进的软件设计模式&#xff0c;通过将复杂的应用程序分解为一系列小型、独立的服务&#xff0c;显著提升了系统的灵活性、可扩展性和维护性。本文将通过解析微服务系统…

Label-studio-ml-backend 和YOLOV8 YOLO11自动化标注,目标检测,实例分割,图像分类,关键点估计,视频跟踪

这里写目录标题 1.目标检测 Detection2.实例分割 segment3.图像分类 classify4.关键点估计 Keypoint detection5.视频帧检测 video detect6.视频帧分类 video classify7.旋转目标检测 obb detect8.替换yolo11模型 给我点个赞吧&#xff0c;谢谢了附录coco80类名称 笔记本 华为m…

恒利联创携手Pearson VUE 亮相第62届高博会

2024年11月15日-17日&#xff0c;第62届中国高等教育博览会&#xff08;简称“高博会”&#xff09;在重庆举行&#xff0c;恒利联创携手全球领先的考试服务提供商Pearson Vue Certiport共同亮相&#xff0c;为中国院校展现并提供数字化职业技能的教育平台及学练考体系。 作为P…

linux复习2:简单命令简述

cp 复制单个文件 cp file.txt /path/to/destination/ 将 file.txt 复制到指定的目标目录。 复制多个文件 cp file1.txt file2.txt /path/to/destination/ 将 file1.txt 和 file2.txt 复制到指定的目标目录。 复制目录&#xff08;递归复制&#xff09; cp -r /path/to/source…

【逆向篇】抓取微信小程序源码 (附加逆向工具wxappUnpacker和使用方法)

抓取微信小程序源码附加逆向工具wxappUnpacker 文章目录前言一、工具准备1 解密工具2 逆向工具 二、解密小程序1.确认小程序包位置2.打开一个小程序3.解密小程序包 三、逆向小程序1、检查nodejs2、安装依赖3、正式逆向 该文章只是学习作用&#xff0c;如果侵权请联系删除&…

【C++】拷贝构造

一种特殊的构造函数&#xff0c;用自身这种类型来构造自身 Student stu1; Student stu2stu1;//调用拷贝构造如果类中没有自定义拷贝构造&#xff0c;类中会自动提供一个默认拷贝构造如果类中定义了自定义拷贝构造&#xff0c;类中不会提供默认拷贝构造 自定义拷贝构造 类名(…

C++的IO流

目录 1. C语言的输入与输出 2. 流是什么 3. CIO流 3.1 C标准IO流 3.2 C文件IO流 4 stringstream的简单介绍 1. 将数值类型数据格式化为字符串 2. 字符串拼接 3. 序列化和反序列化结构数据 1. C语言的输入与输出 C语言中我们用到的最频繁的输入输出方式就是scanf ()与printf()。…