Kotlin 协程Flow VS Rxjava2 (三) 实战

5,126 阅读2分钟

现在有一个需求:股票行情报价跟图表数据是由两个接口返回,产品想同一时间展示两个数据,另外五秒轮训一次接口,报错重试三次。

我们分别用协程,RxJava2和Flow分别处理一下数据处理的部分。

协程

class NineDemoCoroutineViewModel(private val tickerIdList: List<String>) : ViewModel(),
    LifecycleObserver {

    private var model: MarketTickerModel = MarketTickerModel(tickerIdList)
    private var chartModel: NineChartModel = NineChartModel(tickerIdList)
    private val mutableLiveData by lazy { MutableLiveData<ChartUIEvent>() }
    val liveData: LiveData<ChartUIEvent> = mutableLiveData

    private var viewModelList: MutableList<StockViewModel> =
        Collections.synchronizedList(mutableListOf())

    private var retryCount: Int = 0

    fun loadData() {
        viewModelScope.launch(Dispatchers.IO) {
            try {
                val tickerInfoDeferred = async { model.loadData() } // 行情报价
                val chartInfoDeferred = async { chartModel.loadData() } // 图表接口
                val tickerInfoData = tickerInfoDeferred.await()
                val chartInfoData = chartInfoDeferred.await()

                viewModelList.clear()
                // 行情数据和图表数据合并
                viewModelList.addAll(tickerInfoData.map {
                    val tickerInfo = it
                    val chartInfo = chartInfoData.firstOrNull { tickerInfo.tickerId == it.tickerId }
                    StockViewModel(
                        tickerInfo.tickerId,
                        tickerInfo.type,
                        tickerInfo.name ?: "--",
                        CountyResManager.getDrawableResByCounty(tickerInfo.regionCode),
                        tickerInfo.close ?: "",
                        tickerInfo.changeRatio ?: "",
                        chartInfo?.entryList,
                        chartInfo?.maxLength ?: 0
                    )
                })
                // 主线程
                mutableLiveData.postValue(ChartUIEvent.ShowTickerData(viewModelList, false))
                // 执行五秒轮询一次的逻辑
                runLoop()
            } catch (e: Throwable) {
                e.printStackTrace()
                // 错误重试
                if (retryCount < 3) {
                    loadData()
                    retryCount++
                }
            }
        }
    }

    private var loopTime: Long = 0L

    private fun runLoop() {
        if (System.currentTimeMillis() - loopTime < NineChartPresenter.DELAY_TIME) {
            return
        }
        viewModelScope.launch {
            delay(NineChartPresenter.DELAY_TIME)
            loadData()
        }
        loopTime = System.currentTimeMillis()
    }
}

RxJava2

class NineDemoRxJavaViewModel(private val tickerIdList: List<String>) : ViewModel(),
    LifecycleObserver {

    companion object {
        const val TAG = "NineDemoRxJavaViewModel"
    }

    private val mutableLiveData by lazy { MutableLiveData<ChartUIEvent>() }
    val liveData: LiveData<ChartUIEvent> = mutableLiveData
    private var model: MarketTickerModel = MarketTickerModel(tickerIdList)
    private var chartModel: NineChartModel = NineChartModel(tickerIdList)

    fun loadData(disposable: CompositeDisposable) {
        disposable.add(
            // 5秒轮训
            Observable.interval(10, TimeUnit.SECONDS).flatMap {
                Observable.zip(
                    model.loadData2(),
                    chartModel.loadData2(),
                    BiFunction<List<TickerBase>, List<ChartViewModel>, List<StockViewModel>> { t1, t2 ->
                        t1.map {
                            val tickerBase = it
                            val chartModel = t2.firstOrNull { it.tickerId == tickerBase.tickerId }
                            StockViewModel(
                                tickerBase.tickerId,
                                tickerBase.type,
                                tickerBase.name ?: "--",
                                CountyResManager.getDrawableResByCounty(tickerBase.regionCode),
                                tickerBase.close ?: "",
                                tickerBase.changeRatio ?: "",
                                chartModel?.entryList ?: arrayListOf<Entry>(),
                                chartModel?.maxLength ?: 0
                            )
                        }
                    })
            }.retry(3)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({
                    Log.i(TAG, "loadDataSuccess: ")
                    mutableLiveData.value = ChartUIEvent.ShowTickerData(it)
                }, { })
        )
    }
}

Flow

class NineDemoFlowViewModel(private val tickerIdList: List<String>) : ViewModel(),
    LifecycleObserver {

    private var tickerModel: MarketTickerModel = MarketTickerModel(tickerIdList)
    private var chartModel: NineChartModel = NineChartModel(tickerIdList)

    val liveData = liveData<ChartUIEvent> {
        try {
            emitSource(flowData())
        } catch (e: Throwable) {
            e.printStackTrace()
        }
    }

    private suspend fun flowData(): LiveData<ChartUIEvent> {
        return flow {
            while (true) {
                delay(5000)
                emit(1)
            }
        }.retry(3)
          .map {
            tickerModel.loadData().zip(chartModel.loadData()) { t1, t2 ->
                StockViewModel(
                    t1.tickerId,
                    t1.type,
                    t1.name ?: "--",
                    CountyResManager.getDrawableResByCounty(t1.regionCode),
                    t1.close ?: "",
                    t1.changeRatio ?: "",
                    t2.entryList ?: arrayListOf<Entry>(),
                    t2.maxLength ?: 0
                )
            }
        }.map { ChartUIEvent.ShowTickerData(it) }
            .catch { Log.e("NineDemoFlowViewModel", "flowData: ${it.message}") }
            .onCompletion { Log.i("NineDemoFlowViewModel", "flowData: onCompletion") }
            .flowOn(Dispatchers.IO)
            .asLiveData()
    }
}

总结

对比上面三种实现方式,可以看到RxJava2Flow的处理代码量更少,更加易读易懂,不需要额外再去try catch异常。

RxJava2对比FlowFlowLiveData通过liveData结构体的的方式结合更加简单自然,没有RxJava2那么生硬。另外Rxjava2需要通过CompositeDisposable来取消订阅事件,避免内存泄漏,而Flow不需要担心内存泄漏的问题,当Activity销毁的时候会自动cancelliveData所在的协程,从而取消Flow生产者。

img

所以,我建议正确的架构应该是在 ViewViewModel 间使用 LiveData 进行通讯,并在应用的底层和 DataRepository 层架构中使用协程挂起函数或者Flow,业务简单的话直接使用挂起函数处理即可,业务复杂的话使用Flow