如何实现Retrofit顺序执行Upbit两次API请求解决异步时序问题
Upbit 串行请求实现方案
当前实现的核心问题
你现有代码的逻辑错位来自三个核心设计缺陷,和Retrofit本身的异步机制特性直接相关:
- 时序不匹配:Retrofit的
enqueue()是非阻塞异步方法,调用后不会等待请求返回就直接继续执行后续代码,导致第二次ticker请求发出时,第一次请求的返回值还没到达,传入的markets参数始终为空 - 变量作用域混乱:
getMarketsUpbit()内部单独声明了一个局部的var markets,完全遮蔽了外部全局的同名字段,就算第一次请求解析出了正确的交易对字符串,也不会被后续的ticker请求使用 - 状态同步链路断裂:用
PublishSubject做值传递的设计,只是在第一次请求返回时异步更新全局变量,完全没有控制第二次请求的触发时机,本质上和手动给全局变量赋值没有区别,无法解决异步时序差的问题 - 资源冗余:每次请求、每个交易所都重新新建Retrofit实例,既没必要也会增加额外的性能开销。
可落地的实现方案
方案1:嵌套回调(无额外依赖,改造成本最低)
核心思路是把第二次请求的触发逻辑放到第一次请求的成功回调里,从机制上保证第二次请求一定会拿到第一次的返回值:
// 全局复用单个Retrofit实例,不要重复构建 private val upbitService = Retrofit.Builder() .baseUrl("https://api.upbit.com/v1/") .addConverterFactory(GsonConverterFactory.create()) .build() .create(RetrofitService::class.java) private val parser = DataParser() fun getData(exchange: String) { // 其他交易所的原有逻辑保持不变 when(exchange) { coinone -> fetchCoinoneData() bithumb -> fetchBithumbData() huobi -> fetchHuobiData() // Upbit走单独的串行请求逻辑 upbit -> fetchUpbitData() } } private fun fetchUpbitData() { // 第一步:先请求全量交易对列表 upbitService.getMarketsUpbit().enqueue(object : retrofit2.Callback<Any> { override fun onResponse(call: retrofit2.Call<Any>, response: retrofit2.Response<Any>) { if (!response.isSuccessful) { println("Upbit交易对列表请求失败") return } // 解析得到拼接好的markets查询参数 val marketsParam = parser.parseUpbitMarkets(response.body().toString()) // 第二步:拿到markets参数后,再发起行情请求 upbitService.getTickersUpbit(marketsParam).enqueue(object : retrofit2.Callback<Any> { override fun onResponse(call: retrofit2.Call<Any>, response: retrofit2.Response<Any>) { if (!response.isSuccessful) { println("Upbit行情请求失败") return } // 解析最终结果 coinInfos.value = parser.getParsedData( marketsParam, response.body().toString() ) } override fun onFailure(call: retrofit2.Call<Any>, t: Throwable) { println("Upbit行情请求失败:${t.message}") } }) } override fun onFailure(call: retrofit2.Call<Any>, t: Throwable) { println("Upbit交易对列表请求失败:${t.message}") } }) }
方案2:协程实现(推荐,代码简洁无嵌套)
Android官方推荐的协程方案,可以用接近同步代码的写法实现异步串行调用,完全避免时序错位:
- 先给Retrofit接口的方法加上
suspend修饰,Retrofit会自动处理协程的挂起逻辑interface RetrofitService { @GET("market/all") suspend fun getMarketsUpbit(): Any @GET("ticker") suspend fun getTickersUpbit(@Query("markets") markets: String): Any } - 用协程作用域执行请求流程:
private fun fetchUpbitData() { // 在对应生命周期的协程作用域内执行,比如viewModelScope viewModelScope.launch { try { // 挂起等待第一个请求返回 val marketsResponse = upbitService.getMarketsUpbit() val marketsParam = parser.parseUpbitMarkets(marketsResponse.toString()) // 拿到参数后执行第二个请求 val tickerResponse = upbitService.getTickersUpbit(marketsParam) coinInfos.value = parser.getParsedData(marketsParam, tickerResponse.toString()) } catch (e: Exception) { println("Upbit请求流程失败:${e.message}") } } }
方案3:RxJava链式实现(适配现有RxJava技术栈)
如果已经引入了RxJava,不需要再用全局变量和PublishSubject做状态同步,直接用操作符把两次请求串成完整的链路:
upbitService.getMarketsUpbit() .flatMap { marketsResp -> val marketsParam = parser.parseUpbitMarkets(marketsResp.toString()) // 第一次请求成功后触发第二次请求,透传解析好的markets参数 upbitService.getTickersUpbit(marketsParam).map { tickerResp -> marketsParam to tickerResp } } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { (marketsParam, tickerResp) -> coinInfos.value = parser.getParsedData(marketsParam, tickerResp.toString()) }, { error -> println("Upbit请求失败:${error.message}") } )
内容的提问来源于stack exchange,提问作者OverEasy
相关产品推荐
相关产品推荐

