Android:后台在Room中下载存储数据时应用卡顿问题
问题核心
应用启动时联网全量下载数据,删除Room中所有旧数据再插入新数据,导致UI冻结;即使数据无变化,重复的删除插入操作仍会引发卡顿,同时UI层数据处理逻辑也存在优化空间。
关键问题排查
- 主线程执行数据库操作:
AppRepository中开启了.allowMainThreadQueries(),虽然用了CompletableFuture,但仍存在主线程执行IO操作的风险。 - 无意义的全量更新:无论数据是否变化,都会执行删除所有表+全量插入,造成不必要的IO开销。
- UI层数据处理低效:
holidayDays.addAll(days)会导致数据重复,Adapter全量刷新进一步加剧卡顿。 - 线程池使用不规范:
CompletableFuture默认线程池可能和主线程产生资源竞争,未明确指定后台线程。
解决方案
1. 禁止主线程数据库操作
移除Room数据库构建时的主线程查询权限,确保所有数据库操作强制在后台线程执行:
public AppRepository(final Application application) { final AppDatabase database = Room.databaseBuilder(application, AppDatabase.class, "uhc") .enableMultiInstanceInvalidation() // 移除allowMainThreadQueries,强制所有操作走后台线程 .build(); holidayDao = database.appDao(); loadHolidays(); }
2. 优化数据库更新逻辑
先对比新旧数据,仅在数据变化时执行全量更新;同时确保事务包裹所有操作,减少IO次数:
@Transaction public void updateCalendarData(@NonNull final UnusualCalendar calendar) { // 获取本地当前数据(LiveData.getValue()为同步操作,需确保在后台线程执行) List<HolidayDay> currentFixed = holidayDao.getAllHolidayDays().getValue(); List<FloatingHoliday> currentFloating = holidayDao.getAllFloatingHolidays().getValue(); // 对比数据是否一致,一致则跳过更新 if (isDataEqual(currentFixed, calendar.getFixed()) && isDataEqual(currentFloating, calendar.getFloating())) { return; } // 全量更新操作 holidayDao.deleteAllHolidays(); holidayDao.deleteAllHolidayDays(); holidayDao.deleteAllFloatingHolidays(); calendar.getFixed() .stream() .map(HolidayDay::getHolidays) .flatMap(Collection::stream) .forEach(holidayDao::insertHoliday); calendar.getFixed().forEach(holidayDao::insertHolidayDay); calendar.getFloating().forEach(holidayDao::insertFloatingHoliday); } // 通用数据对比方法,需实体类实现getId()方法 private boolean isDataEqual(List<?> oldList, List<?> newList) { if (oldList == null && newList == null) return true; if (oldList == null || newList == null || oldList.size() != newList.size()) return false; Set<Object> oldIds = oldList.stream() .map(item -> ((Identifiable) item).getId()) .collect(Collectors.toSet()); Set<Object> newIds = newList.stream() .map(item -> ((Identifiable) item).getId()) .collect(Collectors.toSet()); return oldIds.equals(newIds); } // 定义Identifiable接口,让实体类实现 interface Identifiable { String getId(); }
3. 优化UI层数据刷新
用DiffUtil实现局部刷新,避免全量刷新带来的卡顿;同时修正数据集合的更新逻辑:
// HolidayAdapter中实现DiffUtil public class HolidayAdapter extends RecyclerView.Adapter<HolidayAdapter.ViewHolder> { private List<HolidayDay> mData = new ArrayList<>(); public void updateData(List<HolidayDay> newData) { DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new HolidayDiffCallback(mData, newData)); mData.clear(); mData.addAll(newData); diffResult.dispatchUpdatesTo(this); } static class HolidayDiffCallback extends DiffUtil.Callback { private final List<HolidayDay> oldData; private final List<HolidayDay> newData; public HolidayDiffCallback(List<HolidayDay> oldData, List<HolidayDay> newData) { this.oldData = oldData; this.newData = newData; } @Override public int getOldListSize() { return oldData.size(); } @Override public int getNewListSize() { return newData.size(); } @Override public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { return oldData.get(oldItemPosition).getId().equals(newData.get(newItemPosition).getId()); } @Override public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { HolidayDay oldItem = oldData.get(oldItemPosition); HolidayDay newItem = newData.get(newItemPosition); // 对比所有需要刷新的字段 return oldItem.getDate().equals(newItem.getDate()) && oldItem.getHolidays().equals(newItem.getHolidays()); } } } // MainActivity中的Observer修改为 sharedViewModel.getAllHolidayDays().observe(this, days -> { if (!days.isEmpty()) { adapter.updateData(days); // 使用DiffUtil局部刷新 } else if (Boolean.FALSE.equals(internet.getValue())) { showNoInternetAlert(); } });
4. 规范线程池使用
明确指定后台线程池执行下载和数据库操作,避免主线程阻塞:
// SharedViewModel的updateData方法 public void updateData(final UnusualCalendar calendar) { // 用单线程池执行数据库操作,保证事务顺序 CompletableFuture.runAsync(() -> repository.updateCalendarData(calendar), Executors.newSingleThreadExecutor()); } // MainActivity的下载逻辑 private void initiateDownload() { downloading = true; showLoadingDialog(); CompletableFuture.supplyAsync(new Downloader(), Executors.newSingleThreadExecutor()) .thenAcceptAsync(data -> { sharedViewModel.updateData(data); downloading = false; dismissLoadingDialog(); }, runOnUiThread(executor -> executor.run())) // 主线程处理UI更新 .exceptionally(throwable -> { downloading = false; dismissLoadingDialog(); // 处理下载错误,比如显示Toast return null; }); }
5. 添加加载状态提示
给用户明确的加载反馈,避免误以为应用冻结:
private ProgressDialog loadingDialog; private void showLoadingDialog() { if (loadingDialog == null) { loadingDialog = new ProgressDialog(this); loadingDialog.setMessage(getString(R.string.loading)); loadingDialog.setCancelable(false); } loadingDialog.show(); } private void dismissLoadingDialog() { if (loadingDialog != null && loadingDialog.isShowing()) { loadingDialog.dismiss(); } }
内容的提问来源于stack exchange,提问作者Andret2344
相关产品推荐
相关产品推荐

