白玩 一 记录retrofit+okhttp+flow 及 kts的全局配置

先回忆下flow吧!

flow是啥

Flow 是 Kotlin 协程框架中的一个异步数据流处理组件,专为响应式编程设计,适用于需要连续或异步返回多个值的场景,如网络请求、数据库查询、传感器数据等

  • 1 ‌异步流(Asynchronous Streams)
    • Flow 允许以非阻塞方式处理一系列值或事件,适用于大量数据或涉及 IO 操作的场景
    • 与 suspend 函数不同,Flow 可以返回多个值,而 suspend 函数仅能返回单个计算结果
  • 2 冷流(Cold Flow)与热流(Hot Flow)
    • 冷流‌:仅在收集器(collect)订阅后才会开始发射数据
      ‌ - 热流‌(如 SharedFlow):创建后立即发射数据,无论是否有收集器订阅
  • 3 声明式 API‌
    • 提供丰富的操作符(如 map、filter、reduce),支持链式调用
  • 4 协程集成‌
    • Flow 基于协程,支持结构化并发、取消操作和背压管理

flow的创建

  • 使用 flow{} 构建器
val numberFlow = flow {emit(1)delay(100) // 模拟耗时操作emit(2)
}
  • 使用 flowOf 快速创建
val fixedFlow = flowOf(1, 2, 3)
  • 集合转 Flow
val listFlow = listOf("A", "B", "C").asFlow()
高级创建方式

(1) 基于回调的 API 转换
将回调式 API(如网络请求)封装为 Flow‌

fun fetchDataFlow() = callbackFlow {val callback = object : DataCallback {override fun onData(value: String) {trySend(value) // 发送数据到 Flow}override fun onComplete() {close() // 关闭 Flow}}registerCallback(callback)awaitClose { unregisterCallback(callback) } // 确保资源释放
}

(2) 从挂起函数生成
通过 channelFlow 或 flow 结合挂起函数实现复杂逻辑‌

fun pollUpdates() = flow {while (true) {val updates = fetchUpdates() // 挂起函数emit(updates)delay(5000) // 间隔轮询}
}

Flow 中处理背压(Backpressure)的核心策略

  1. 缓冲机制‌
    通过 buffer() 设置缓冲区容量,允许生产者和消费者异步执行,缓解数据积压压力‌
flow { repeat(100) { emit(it) }
}.buffer(50) // 设置50容量的缓冲区.collect { /* 处理数据 */ }

‌2. 合并策略
使用 conflate() 跳过中间值,仅保留最新数据‌

flow { emit(1); delay(10); emit(2); emit(3) 
}.conflate().collect { println(it) } // 输出:1 → 3(跳过2)

‌3. 最新值优先
collectLatest 取消未完成的任务,立即处理最新发射值‌

flow { emit("A"); delay(100); emit("B") 
}.collectLatest { value ->delay(200)  // 处理"A"时被"B"中断println(value) // 仅输出"B"
}
  1. 调度优化
    利用 flowOn 切换协程上下文,分散计算负载‌
flow { /* 密集计算 */ }.flowOn(Dispatchers.Default) // 在后台线程生产.collect { /* 主线程消费 */ }

创建操作符‌

flow{}‌
基础构建器,通过 emit 发射数据,支持挂起操作‌:

flow { emit(1); delay(100); emit(2) }

flowOf‌
快速创建固定值序列的 Flow‌:

flowOf("A", "B", "C")

asFlow‌
将集合(如 List)转换为 Flow‌:

listOf(1, 2, 3).asFlow()

转换操作符‌

map‌
对每个元素进行转换‌:

flowOf(1, 2).map { it * 10 } // 输出 10, 20

filter‌
按条件过滤元素‌:

flowOf(1, 2, 3).filter { it % 2 == 0 } // 输出 2

transform‌
灵活转换,可多次发射值‌:

flowOf("Hi").transform { emit(it.uppercase()); emit(it.length) }

组合操作符‌

zip‌
合并两个 Flow 的对应元素‌:

flowOf(1, 2).zip(flowOf("A", "B")) { num, str -> "$num$str" } // 输出 1A, 2B

flatMapConcat‌
顺序展开嵌套 Flow‌:

flowOf(1, 2).flatMapConcat { flowOf(it, it * 2) } // 输出 1, 2, 2, 4

终端操作符‌

collect‌
触发流执行并处理数据‌:

flowOf(1).collect { println(it) }

first/last‌
获取首个或末尾元素‌:

flowOf(1, 2).first() // 返回 1

reduce‌
累积计算(如求和)‌:

flowOf(1, 2, 3).reduce { acc, v -> acc + v } // 输出 6

背压处理操作符‌

buffer‌
设置缓冲区缓解生产消费速度差异‌:

flow { emit(1) }.buffer(10)

conflate‌
跳过中间值,保留最新数据‌:

flow { emit(1); emit(2) }.conflate() // 仅处理 2

其他实用操作符‌

take‌
限制收集的元素数量‌:

flowOf(1, 2, 3).take(2) // 输出 1, 2

onEach‌
在每次发射时执行副作用(如日志)‌:

flowOf(1).onEach { println("发射: $it") }

开整

调用retrofit+okhttp

导包

// 网络请求api("com.google.code.gson:gson:2.8.6")api("com.squareup.retrofit2:retrofit:2.9.0")api("com.squareup.retrofit2:converter-gson:2.9.0")api("com.squareup.retrofit2:converter-scalars:2.0.0")api("com.squareup.okhttp3:okhttp:3.14.9")api("com.squareup.okhttp3:logging-interceptor:3.12.2")api("com.squareup.okio:okio:1.17.4")

open class HttpCreater private constructor() {val timeOut: Long = 60 //30秒超时val baseUrlInterceptor: BaseUrlInterceptor = BaseUrlInterceptor()var okhttpClient: OkHttpClientvar downOkHttpClient: OkHttpClient//日志拦截var loggingInterceptor = HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger {override fun log(message: String) {//打印retrofit日志Log.i("log_http", "retrofitBack = $message")}})init {loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY//用于请求okhttpClient = OkHttpClient.Builder().connectTimeout(timeOut, TimeUnit.SECONDS).readTimeout(timeOut, TimeUnit.SECONDS).writeTimeout(timeOut, TimeUnit.SECONDS).addInterceptor(baseUrlInterceptor).addInterceptor(loggingInterceptor).build()//用于下载  因为请求的okhttpClient 有日志拦截器 会先拦截请求结果 所以专门创建个用于下载的downOkHttpClient = OkHttpClient.Builder().connectTimeout(timeOut, TimeUnit.SECONDS).readTimeout(timeOut, TimeUnit.SECONDS).writeTimeout(timeOut, TimeUnit.SECONDS).build()}companion object {open val instance by lazy (LazyThreadSafetyMode.SYNCHRONIZED){HttpCreater()}}/*** 设置baseUrl 对应的token*/open fun setToken(baseUrl: String,token:String){baseUrlInterceptor.setToken(baseUrl,token)}/*** 获取请求的retrofit  调用的时候创建 传入baseUrl  因为我们项目连了好几个服务器*/open fun getRetrofit(baseUrl:String) : Retrofit{val gson: Gson = GsonBuilder().setLenient().create();return  Retrofit.Builder().client(okhttpClient).baseUrl(baseUrl)
//            .addConverterFactory(GsonConverterFactory.create(gson)) //配置转化库 Gson解析失败,不报错崩溃.addConverterFactory(ScalarsConverterFactory.create()) //返回字符串.build()}/*** 下载的tokenretrofit*/open fun getDownRetrofit(baseUrl:String) : Retrofit{return  Retrofit.Builder().client(downOkHttpClient).baseUrl(baseUrl).addConverterFactory(ScalarsConverterFactory.create()) //配置转化库 Gson解析失败,不报错崩溃.build()}
}

请求拦截器 用于获取token后设置后,自动将token设置到请求头

class BaseUrlInterceptor:Interceptor {val tokenMap = mutableMapOf<String,String>()override fun intercept(chain: Interceptor.Chain): Response {// 获取requestval request = chain.request()val builder = request.newBuilder()builder.addHeader("Content-Type", "application/json; charset=UTF-8")builder.addHeader("Accept", "application/json;versions=1")val httpUrl = request.url().url().hostLog.i("log_http","httpUrl>>${httpUrl}")if(!tokenMap.get(httpUrl).isNullOrEmpty()){builder.addHeader("Authorization", "Bearer ${tokenMap.get(httpUrl)}")}return chain.proceed(builder.build())}fun setToken(baseUrl: String, token: String) {tokenMap.put(baseUrl,token)}
}

添加请求接口

interface NetApi {@GET("/article/list/{path}/json")suspend fun getList(@Path("path") page:Int):String}

NetRepository flow调用

class NetRepository private constructor(){val service by lazy { HttpCreater.instance.getRetrofit("https://www.wanandroid.com").create(NetApi::class.java) }companion object{val instance by lazy { NetRepository() }}fun getList():Flow<String> = flow {val result = service.getList(0)Log.i("zq_demo","flow  result>>${result}")emit(result)}
}

测试

测试代码

findViewById<TextView>(R.id.tv_hello).setOnClickListener {Log.i("zqq_demo","tv_hello")lifecycleScope.launch {Log.i("zqq_demo","lifecycleScope")netRepository.getList().onEach {Log.i("zqq_demo","onEach>>${it}")}.collect{Log.i("zqq_demo","collect>>${it}")}}}

结果
在这里插入图片描述

其他

1 我们可以使用配置文件配置baseurl
创建config.gradle.kts
在这里插入图片描述
添加测试代码

val myProperty: String by extra("Hello, World!")

使用 app下
在这里插入图片描述
添加

// 加载 config.gradle.kts
apply(from = "${rootDir}/config.gradle.kts")
// 使用 myProperty
println(extra["myProperty"]) // 输出: Hello, World!

运行
在这里插入图片描述
接下来创建baseUrl
config.gradle.kts:

val baseUrl: String by extra("https://www.wanandroid.com")

app下 build.gradle.kts

plugins {alias(libs.plugins.android.application)alias(libs.plugins.jetbrains.kotlin.android)
}
// 加载 config.gradle.kts
apply(from = "${rootDir}/config.gradle.kts")
android {defaultConfig {buildConfigField("String",name = "baseUrl", value = "\"${extra["baseUrl"]}\"")}//这个别忘记添加buildFeatures {buildConfig = true // 启用 BuildConfig 功能}
}

然后打印

Log.i("zqq_demo","baseUrl>>${BuildConfig.baseUrl}")

在这里插入图片描述
这样上边NetRepository 就可以使用

class NetRepository private constructor(){val service by lazy { HttpCreater.instance.getRetrofit(BuildConfig.baseUrl).create(NetApi::class.java) }

当然你可以定义版本号 applicationId 其他的key versionCode versionName 签名文件路径 等等配置都可以

使用旧的config.gradle

config.gradle如下

ext {test = "hello"android = [hello = "hello"]
}

项目的build.gradle.kts

plugins {alias(libs.plugins.android.application) apply falsealias(libs.plugins.jetbrains.kotlin.android) apply falsealias(libs.plugins.android.library) apply false
}apply(from = "${rootDir}/config.gradle")

app的build.gradle.kts

println("${(rootProject.extra["android"] as Map<String,Any>).get("hello")}")  defaultConfig {applicationId = "com.zqq.demo"minSdk = 24targetSdk = 34versionCode = 1versionName = "1.0"testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"buildConfigField("String",name = "hello", value = "\"${rootProject.extra["test"]}\"")}buildFeatures {buildConfig = true // 启用 BuildConfig 功能}

可以去看 式封装:Kotlin+协程+Flow+Retrofit+OkHttp +Repository,倾囊相授,彻底减少模版代码进阶之路

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

犯罪现场三维还原:科技助力刑侦变革

在刑侦领域&#xff0c;犯罪现场的准确还原对于案件侦破起着至关重要的作用。传统的现场记录方式&#xff0c;如拍照、绘图等&#xff0c;虽然能获取一定信息&#xff0c;但难以全面、直观地呈现现场全貌&#xff0c;容易遗漏关键细节&#xff0c;且在后期分析和信息传达上存在…

go-admin 构建arm镜像

目录 1、 go-admin Dockerfile 2、docker build go-admin 3、settings.yml 4、go-admin-ui Dockerfile 5、docker build go-admin-ui 6、go-admin.yaml 7、go-admin-ui.yaml 1、 go-admin Dockerfile # 构建阶段:使用 Go 1.24 版本(支持远程调试) FROM golang:1.24-…

深入浅出:C++ STL简介与学习指南

目录 前言 STL的版本演变 STL六大组件 STL的重要性 如何学习STL STL的缺陷 总结 前言 什么是STL&#xff1f; STL&#xff08;Standard Template Library&#xff0c;标准模板库&#xff09;是C标准库的核心组成部分&#xff0c;它不仅是一个可复用的组件库&#xff0c;更是一…

Mysql事务原理

脏读(Dirty Read) 某个事务已更新一份数据&#xff0c;另一个事务在此时读取了同一份数据&#xff0c;由于某些原因&#xff0c;前一个进行了RollBack&#xff0c;则后一个事务所读取的数据就会是不正确的。 不可重复读(Non-repeatable read) 在一个事务的两次查询之中数据不一…

小红书笔记详情API指南

一、引言小红书作为中国领先的社交电商平台&#xff0c;拥有超过4.8亿用户(2025年Q2数据)&#xff0c;其开放平台已成为品牌营销与数据挖掘的重要渠道‌1。通过笔记详情API获取数据&#xff0c;可以帮助商家、品牌方和数据分析人员了解用户反馈、市场趋势和消费需求‌。这些数据…

VS+Qt中使用QCustomPlot绘制曲线标签(附源码)

在qt中我们常常会使用数据来绘制曲线&#xff0c;常用的的绘制方法用QCutomPlot、QChart和QPrinter。有时我们会根据需要在曲线进行二次绘制&#xff0c;包括对曲线打标签&#xff0c;显示某个点的值等功能。本文主要为大家介绍在QCustomPlot中使用QCPItemTracer和QCPItemText绘…

Spring Boot项目生产环境部署完整指南

在Spring Boot应用开发完成后&#xff0c;如何将其稳定、高效地部署到生产环境是每个开发者都需要掌握的关键技能。本文将详细介绍Spring Boot项目的多种部署方案&#xff0c;从传统部署到现代化容器部署&#xff0c;选择最适合的部署策略。 1. 部署前的准备工作 1.1 项目打包优…

微信小程序中实现页面跳转的方法

微信小程序中页面跳转主要有两种方式&#xff1a;声明式导航&#xff08;通过组件实现&#xff09;和编程式导航&#xff08;通过API实现&#xff09;。两种方式适用于不同场景&#xff0c;以下详细说明。一、声明式导航&#xff08;navigator组件&#xff09;通过小程序内置的…

从0开始学linux韦东山教程Linux驱动入门实验班(7)

本人从0开始学习linux&#xff0c;使用的是韦东山的教程&#xff0c;在跟着课程学习的情况下的所遇到的问题的总结,理论虽枯燥但是是基础。本人将前几章的内容大致学完之后&#xff0c;考虑到后续驱动方面得更多的开始实操&#xff0c;后续的内容将以韦东山教程Linux驱动入门实…

国内AI IDE竞逐:腾讯CodeBuddy、阿里通义灵码、字节跳动TRAE、百度文心快码

国内AI IDE竞逐&#xff1a;腾讯CodeBuddy、阿里通义灵码、字节跳动TRAE、百度文心快码 随着人工智能技术的不断发展&#xff0c;各大科技公司纷纷推出自家的AI IDE&#xff0c;推动软件开发进入全新的智能化时代。腾讯的 CodeBuddy IDE、阿里云的 通义灵码 AI IDE、字节跳动的…

git rebase使用教程 以及和merge的区别

Merge和Rebase概念概述 rebase 和 merge 相似&#xff0c;但又不完全相同&#xff0c;本质上都是用来合并分支的命令&#xff0c;区别如下 merge合并分支会多出一条merge commit记录&#xff0c;而rebase不会merge的提交树是非线性的&#xff0c;会有分叉&#xff0c;而rebase的…

React中的合成事件解释和理解

什么是合成事件&#xff08;Synthetic event&#xff09;?它和原生事件有什么区别?解题思路:解释合成事件&#xff0c;然后对比原生事件&#xff0c;然后再说他的优势1.一致性 在 react里面&#xff0c;这个合成事件是非常重要的&#xff0c;因为它就是为了解决浏览器之间与事…

【Python系列】使用 memory_profiler 诊断 Flask 应用内存问题

博客目录一、内存分析的重要性二、memory_profiler 基础使用安装与基本配置理解分析报告三、在 Flask 应用中使用 memory_profiler装饰视图函数使用 mprof 进行长期监控四、高级内存分析技巧精确测量代码块内存定期内存采样结合 objgraph 分析对象引用五、常见内存问题及解决方…

vue3【组件封装】超级表单 S-form.vue

最终效果 代码实现 components/SUI/S-form.vue <script lang"ts" setup> import type { FormInstance } from "element-plus";// 使用索引签名定义对象类型 type GenericObject {[key: string]: any; };const props defineProps<{Model?: Gen…

Android Studio Memory Monitor内存分析核心指标详解

Depth、Native Size、Shallow Size、Retained Size 解析 一、指标定义与对比指标定义计算逻辑重要性Shallow Size对象自身实例占用的内存基本类型字段大小 引用指针 内存对齐对象的基础内存成本Retained Size回收该对象可释放的总内存量&#xff08;含所有依赖对象&#xff0…

vue中使用wavesurfer.js绘制波形图和频谱图(支持.pcm)

新的实现方式&#xff1a;vue使用Canvas绘制频谱图 安装wavesurfer.js npm install wavesurfer.js第一版&#xff1a; 组件特点&#xff1a; 一次性加载好所有的数据&#xff1b; <template><div class"audio-visualizer-container"><div class&…

go mod教程、go module

什么是go mod go mod 是go语言的包管理工具&#xff0c;类似java 的maven&#xff0c;go mod的出现可以告别goPath&#xff0c;使用go module来管理项目&#xff0c;有了go mod账号就不需要非得把项目放到gopath/src目录下了&#xff0c;你可以在磁盘的任何位置新建一个项目 go…

150-SWT-MCNN-BiGRU-Attention分类预测模型等!

150-SWT-MCNN-BiGRU-Attention分类预测模型!基于多尺度卷积神经网络(MCNN)双向长短期记忆网络(BiGRU)注意力机制(Attention)的分类预测模型&#xff0c;matlab代码&#xff0c;直接运行使用&#xff01;1、模型介绍&#xff1a;针对传统方法在噪声环境下诊断精度低的问题&#…

MySQL数据一致性与主从延迟深度解析:从内核机制到生产实践

在高并发分布式系统中&#xff0c;数据一致性与复制延迟如同硬币的两面。本文深入剖析MySQL持久化机制与主从同步原理&#xff0c;并提供可落地的调优方案。一、数据持久化核心机制&#xff1a;双日志协同 1. Redo Log&#xff1a;崩溃恢复的生命线刷新策略&#xff08;innodb_…

【I】题目解析

目录 单选题 多选题 判断题 单选题 1.reg[7:0]A; A2hFF;则A&#xff08;&#xff09; A.8b11111110 B.8b03 C.8b00000011 D.8b11111111 C 2hFF实际上等效于2位二进制2b11&#xff0c;赋值给8位寄存器A之后&#xff0c;低位赋值&#xff0c;高位补0 A8b00000011 AMD FPG…