You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android:后台在Room中下载存储数据时应用卡顿问题

问题核心

应用启动时联网全量下载数据,删除Room中所有旧数据再插入新数据,导致UI冻结;即使数据无变化,重复的删除插入操作仍会引发卡顿,同时UI层数据处理逻辑也存在优化空间。

关键问题排查
  1. 主线程执行数据库操作:AppRepository中开启了.allowMainThreadQueries(),虽然用了CompletableFuture,但仍存在主线程执行IO操作的风险。
  2. 无意义的全量更新:无论数据是否变化,都会执行删除所有表+全量插入,造成不必要的IO开销。
  3. UI层数据处理低效:holidayDays.addAll(days)会导致数据重复,Adapter全量刷新进一步加剧卡顿。
  4. 线程池使用不规范: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 05:16:05