RxJava+Room+Retrofit链式调用异常:收藏项逐次减少至消失
问题分析与解决方案
看起来你遇到的核心问题是RxJava流的异步操作不同步,加上共享变量的线程安全风险,导致收藏状态被API返回的未收藏数据覆盖,最终出现收藏项逐渐消失的情况。咱们一步步拆解问题,再给出修正方案:
核心问题点
1. 异步插入导致流程脱节
你的insertKafaniInDb方法内部是创建Observable后直接subscribe,这是一个完全独立的异步流,和上游的API请求流没有关联。也就是说:
- API数据一返回,
getKafaniFromApi()就会立刻发射数据 - 后续的「更新收藏」「查询全量数据」操作会直接执行,但此时DB插入操作可能还没完成
- 插入完成后,API返回的未收藏数据会覆盖掉你刚更新的收藏状态,导致最终结果里收藏项消失
2. 共享变量kafaniList存在线程安全隐患
ViewModel里用kafaniList存储之前的收藏列表,在RxJava的多线程流中,这种可变变量可能因为线程调度顺序问题,出现「还没赋值就被使用」或者「值被意外覆盖」的情况。
3. 更新收藏的逻辑效率低且易出错
循环调用updateFavoriteKafana逐个更新,不仅DB操作次数多,还容易因为和插入操作的时序问题导致无效更新。
修正后的代码
第一步:重构Repository,让插入操作与流同步
先修改insertKafaniInDb,让它返回Single,这样就能把插入操作串联到RxJava流中,确保插入完成后再执行后续步骤:
fun insertKafaniInDb(kafani: List<Kafana>): Single<LongArray> { return Single.fromCallable { kafanaDao.insertAll(kafani) } .subscribeOn(Schedulers.io()) .doOnSuccess { Timber.d("Inserted ${kafani.size} kafani from API in DB...") } } // 同步API请求与DB插入 fun getKafaniFromApi(): Observable<List<Kafana>> { return apiService.getKafani() .toObservable() .flatMap { apiKafani -> // 等待插入完成后,再把API数据传递到下游 insertKafaniInDb(apiKafani).map { apiKafani } } }
第二步:优化ViewModel,去掉共享变量,串联完整流程
用RxJava的操作符传递收藏列表,避免线程安全问题,同时确保每一步操作都在前一步完成后执行:
fun get(): Observable<List<Kafana>> { // 1. 先获取当前收藏列表 return kafanaRepository.getFavoriteKafani() .flatMapObservable { favoriteList -> // 2. 拉取API数据并插入DB(等待插入完成) kafanaRepository.getKafaniFromApi() // 3. 批量更新收藏状态(用批量DB操作更高效) .flatMap { _ -> Single.fromCallable { // 提取收藏项的名称,批量更新DB val favoriteNames = favoriteList.map { it.name } kafanaDao.setFavoritesByNames(favoriteNames) }.subscribeOn(Schedulers.io()) } // 4. 更新完成后,获取最终的全量数据 .flatMapObservable { kafanaRepository.getKafaniFromDb() } } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) }
第三步:优化Room DAO,添加批量更新方法
为了提高效率,在DAO中新增批量更新收藏状态的方法,避免循环调用单条更新:
@Dao interface KafanaDao { // 确保插入时替换旧数据,这样API数据会覆盖原有记录 @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertAll(kafani: List<Kafana>): LongArray // 批量更新收藏状态 @Query("UPDATE kafana SET is_favorite = 1 WHERE name IN (:names)") fun setFavoritesByNames(names: List<String>) // ... 其他原有方法 }
为什么这样修改能解决问题?
- 全流程同步:现在每一步操作(查询收藏→API请求→插入DB→更新收藏→查询全量)都是按顺序执行的,前一步完成后才会触发后一步,彻底避免了异步操作的时序冲突。
- 无共享变量:通过RxJava的操作符把收藏列表直接传递到后续操作中,消除了线程安全隐患。
- 高效批量操作:用单条SQL批量更新收藏状态,减少了DB操作次数,同时避免了循环更新带来的错误。
内容的提问来源于stack exchange,提问作者Esteban
相关产品推荐
相关产品推荐

