RxJava循环异步获取本地DB数据,如何保证返回结果顺序?
解决RxJava异步请求按原始顺序返回结果的问题
嘿,你遇到的这个乱序问题在RxJava异步场景里太常见了!现在的写法是同时发起所有请求,哪个先跑完就先回调,结果顺序自然没法保证。要实现按contents的遍历顺序依次返回结果,这里有两种贴合你需求的RxJava方案:
方案一:串行执行请求(完美匹配"先返回第一个再第二个"的需求)
如果希望严格按照你遍历contents的顺序,先搞定第一个Content的请求并返回结果,再启动第二个请求,用concatMap替代循环订阅就好:
// 把contents转换成Observable序列 Observable.fromIterable(contents) .concatMap(content -> // 每个content对应的数据库请求,指定在IO线程执行 AppManagers.getContentManager() .getScoreCardsAndUpdate(content.getId()) .subscribeOn(Schedulers.io()) // 数据库操作别放主线程,避免卡顿 ) .observeOn(AndroidSchedulers.mainThread()) .subscribe(scoreCards -> { // 这里的回调会严格按contents的顺序来! // 第一个回调是第一个Content的结果,第二个是第二个,依次类推 ... }, throwable -> { // 别忘了处理错误情况 });
为啥这个能行?
concatMap的特性就是按顺序处理每个内部Observable,必须等前一个请求完全完成(发送完数据+结束),才会订阅下一个请求。这样一来,回调顺序就和你遍历contents的顺序完全一致了。
方案二:并行请求+事后排序(适合想提速但要最终有序的场景)
如果想让所有请求同时跑以节省总时间,但最终拿到的结果列表要和contents顺序一致(不过这种是等所有请求都跑完才一起返回,不是逐个返回),可以用索引绑定+排序的方式:
// 给每个Content分配原始索引 Observable.range(0, contents.size()) .flatMap(index -> { Content content = contents.get(index); return AppManagers.getContentManager() .getScoreCardsAndUpdate(content.getId()) .subscribeOn(Schedulers.io()) // 把结果和它的原始索引绑在一起 .map(scoreCards -> new Pair<>(index, scoreCards)); }) .toList() // 按原始索引排序,恢复顺序 .map(pairs -> pairs.stream() .sorted(Comparator.comparingInt(Pair::first)) .map(Pair::second) .collect(Collectors.toList())) .observeOn(AndroidSchedulers.mainThread()) .subscribe(orderedScoreCardsList -> { // 这个列表的顺序和contents完全一致 ... }, throwable -> { // 错误处理不能少 });
原理简单说:
先给每个Content标记它在原始列表里的位置,请求完成后把结果和索引存一起。等所有请求都结束,再按索引排序,就能得到和原始顺序一致的结果列表。这种方式总耗时取决于最慢的那个请求,但要等全部完成才能拿到结果。
为啥原来的写法会乱序?
你之前的循环里,每个getScoreCardsAndUpdate()都是独立的Observable,调用subscribe()后会立刻发起请求,这些请求是并行跑的,哪个先结束就先触发回调,顺序自然没法控制啦。
内容的提问来源于stack exchange,提问作者Katy Colins
相关产品推荐
相关产品推荐

