如何阻止Room LiveData观察者接收待处理的多次更新通知?
问题
在更新Room数据库前移除观察者,之后重新添加观察者,却仍会收到每一次更新的通知,导致RecyclerView被多次更新。
我的部分代码如下:
public class ArticlesListFragment extends Fragment { private LiveData<List<Article>> mLDgetAllArticle; private long mClistId; private ArrayList<Article> mArticles; public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { this.mLDgetAllArticle = this.articleViewModel.getAllArticle(mclistId); this.setArticlesObserver(); ... } private void setArticlesObserver() { if (this.mLDgetAllArticle != null && !this.mLDgetAllArticle.hasObservers()) this.mLDgetAllArticle.observe(getViewLifecycleOwner(), this::updateArticlesList); } private void updateArticlesList(List<Article> articles) { this.mArticles = new ArrayList<>(articles); this.mArticlesRecyclerViewAdapter.setAdapterDatas(this.mArticles); } private void removeArticlesObserver() { if (this.mLDgetAllArticle != null && this.mLDgetAllArticle.hasObservers()) this.mLDgetAllArticle.removeObservers(getViewLifecycleOwner()); } private void updateArticle(Article article) { this.articleViewModel.updateArticle(article); } ... }
目前一切正常,但在批量更新所有Article时:
for (int i = 0; i < this.mArticles.size(); i++) { this.mArticles.get(i).setOrd(i); this.updateArticle(this.mArticles.get(i)); }
我尝试先移除观察者,完成更新后再重新添加:
this.removeArticlesObserver(); for (int i = 0; i < this.mArticles.size(); i++) { this.mArticles.get(i).setOrd(i); this.updateArticle(this.mArticles.get(i)); } this.setArticlesObserver();
但重新添加观察者后,updateArticlesList被调用的次数和updateArticle的执行次数完全一致,还是会收到每一次更新的通知。
我忽略了什么?有没有办法在重新添加观察者前清空所有待处理的结果?
相关类代码:
ViewModelFactory.java
public class ViewModelFactory implements ViewModelProvider.Factory { private final ArticleDataRepository articleDataSource; private final Executor executor; private static volatile ViewModelFactory factory; public static ViewModelFactory getInstance(Context context) { if (factory == null) { synchronized (ViewModelFactory.class) { if (factory == null) { factory = new ViewModelFactory(context); } } } return factory; } private ViewModelFactory(Context context) { getDB database = getDB.getInstance(context); this.articleDataSource = new ArticleDataRepository(database.articleDao()); this.executor = Executors.newSingleThreadExecutor(); } @Override @NotNull public <T extends ViewModel> T create(Class<T> modelClass) { if (modelClass.isAssignableFrom(ArticleViewModel.class)) { return (T) new ArticleViewModel(articleDataSource, executor); } throw new IllegalArgumentException("Unknown ViewModel class"); } }
ArticleViewModel.java
public class ArticleViewModel extends ViewModel { private final ArticleDataRepository articleDataSource; private final Executor executor; public ArticleViewModel( ArticleDataRepository articleDataSource, Executor executor ) { this.articleDataSource = articleDataSource; this.executor = executor; } public LiveData<List<Article>> getAllArticle(long clistId) { return articleDataSource.getAll(clistId); } }
ArticleDataRepository.java
public class ArticleDataRepository { private final ArticleDao articleDao; public ArticleDataRepository(ArticleDao articleDao) { this.articleDao = articleDao; } public LiveData<List<Article>> getAll(long clistId) { return this.articleDao.getAll(clistId); } }
解决方案与分析
核心原因
- Room LiveData的特性:Room返回的LiveData会监听数据库变化,每一次
update操作都会触发查询结果更新。即使你移除了观察者,数据库的更新事件已经被Room处理,重新订阅时,后续完成的更新仍会推送通知。 - 循环单条更新的弊端:循环调用单个
update会触发多次数据库变更,对应多次LiveData回调,这是设计逻辑导致的,移除观察者无法规避后台线程中未完成的更新操作。
最优解决方案:使用批量更新接口
这是从根源解决问题的方式,避免多次更新触发多次回调:
- 在
ArticleDao中添加批量更新方法:
@Dao public interface ArticleDao { @Query("SELECT * FROM article WHERE clistId = :clistId") LiveData<List<Article>> getAll(long clistId); // 批量更新接口 @Update void updateArticles(List<Article> articles); }
- 在
ArticleDataRepository中实现该方法:
public void updateArticles(List<Article> articles) { executor.execute(() -> articleDao.updateArticles(articles)); }
- 在
ArticleViewModel中暴露方法:
public void updateArticles(List<Article> articles) { articleDataSource.updateArticles(articles); }
- 在Fragment中替换循环更新逻辑:
// 不需要移除观察者,批量更新只会触发一次回调 for (int i = 0; i < this.mArticles.size(); i++) { this.mArticles.get(i).setOrd(i); } this.articleViewModel.updateArticles(this.mArticles);
备选方案:用标志位控制刷新
如果暂时无法修改为批量更新,可通过标志位过滤批量更新期间的多次回调:
private boolean mIsBulkUpdating = false; private void updateArticlesList(List<Article> articles) { if (mIsBulkUpdating) { // 批量更新期间仅更新本地数据,不刷新RecyclerView this.mArticles = new ArrayList<>(articles); return; } this.mArticles = new ArrayList<>(articles); this.mArticlesRecyclerViewAdapter.setAdapterDatas(this.mArticles); } // 批量更新方法 private void bulkUpdateArticles() { mIsBulkUpdating = true; for (int i = 0; i < this.mArticles.size(); i++) { this.mArticles.get(i).setOrd(i); this.updateArticle(this.mArticles.get(i)); } // 延迟确保所有更新完成后统一刷新 new Handler(Looper.getMainLooper()).postDelayed(() -> { mIsBulkUpdating = false; this.mArticlesRecyclerViewAdapter.setAdapterDatas(this.mArticles); }, 100); }
内容的提问来源于stack exchange,提问作者Chris972
相关产品推荐
相关产品推荐

