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

RecyclerView批量更新报错,Handler优化后应用卡顿求助

解决RecyclerView更新时的IllegalStateException与卡顿问题

我来帮你分析并解决这两个困扰你的问题:

问题根源解析

  1. IllegalStateException:你在EditText的afterTextChanged回调里直接调用notifyDataSetChanged(),而此时RecyclerView可能正处于布局计算或者滚动的过程中。RecyclerView内部有严格的断言机制,会阻止这种在布局/滚动期间修改数据集并通知更新的操作,因为这会导致布局状态不一致,所以抛出了Cannot call this method while RecyclerView is computing a layout or scrolling的错误。你用全局G.handler.post避开了断言,但带来了新的性能问题。
  2. 卡顿问题:每次输入字符就触发全量的notifyDataSetChanged(),这会让RecyclerView重新绑定所有列表项、重新计算布局,当列表项较多时,这种频繁的全量刷新会带来极大的性能开销,直接导致应用卡顿无响应。

分步解决方案

1. 安全触发RecyclerView更新,规避断言错误

不要使用全局Handler,而是利用RecyclerView自身的post()方法——它会确保在RecyclerView完成当前布局/滚动操作后再执行更新逻辑,既贴合RecyclerView的生命周期,又能彻底避开断言限制。

2. 优化刷新逻辑,拒绝全量刷新

不要每次都调用notifyDataSetChanged(),而是只更新实际发生数据变化的列表项。比如你在循环里修改了mList.get(i)的值,那么只需要对每个i调用notifyItemChanged(i)即可,这样RecyclerView只会重新绑定变化的item,大幅减少性能消耗。

3. 修复ViewHolder复用导致的TextWatcher重复绑定问题

RecyclerView的ViewHolder会被复用,每次onBindViewHolder时如果直接给EditText添加TextWatcher,会导致同一个EditText绑定多个Watcher,触发多次转换逻辑。所以需要先移除之前的Watcher,再添加新的。

修改后的完整适配器代码

public class CurrencyAdapter extends RecyclerView.Adapter<CurrencyAdapter.ViewHolder> {
    private Context mContext;
    private List<CurrencyModel> mList;
    private MainActivity activity;

    public CurrencyAdapter(Context mContext, List<CurrencyModel> mList) {
        this.mContext = mContext;
        this.mList = mList;
        this.activity = (MainActivity) mContext;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        TextView txtName;
        ImageView imgIcon;
        EditText edtInput;
        // 保存当前item的TextWatcher,用于复用ViewHolder时移除
        TextWatcher textWatcher;

        public ViewHolder(View v) {
            super(v);
            txtName = v.findViewById(R.id.txt_name);
            imgIcon = v.findViewById(R.id.img_icon);
            edtInput = v.findViewById(R.id.edt_input);
        }
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(mContext).inflate(R.layout.list, parent, false);
        return new ViewHolder(v);
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, final int position) {
        CurrencyModel model = mList.get(position);
        holder.txtName.setText(model.getName());

        // 设置图标
        switch (model.getName()) {
            case "dollar":
                holder.imgIcon.setImageResource(R.drawable.dollar);
                break;
            case "pound":
                holder.imgIcon.setImageResource(R.drawable.pound);
                break;
            case "euro":
                holder.imgIcon.setImageResource(R.drawable.euro);
                break;
        }

        // 先移除之前的TextWatcher,避免复用导致多次触发
        if (holder.textWatcher != null) {
            holder.edtInput.removeTextChangedListener(holder.textWatcher);
        }

        // 设置输入框值,保留小数精度
        holder.edtInput.setText(String.valueOf(model.getValue()));

        // 创建新的TextWatcher并保存到ViewHolder
        holder.textWatcher = new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}

            @Override
            public void afterTextChanged(Editable editable) {
                String inputStr = editable.toString().trim();
                if (!inputStr.isEmpty()) {
                    try {
                        // 用Double.parseDouble支持小数输入,更符合货币场景
                        double inputValue = Double.parseDouble(inputStr);
                        convertor(position, inputValue);
                    } catch (NumberFormatException e) {
                        // 处理非法输入,比如非数字字符
                        e.printStackTrace();
                    }
                }
            }
        };
        holder.edtInput.addTextChangedListener(holder.textWatcher);
    }

    private void convertor(int position, double inputValue) {
        CurrencyModel currentModel = mList.get(position);
        double baseValue = 0;

        // 计算基准转换值
        switch (currentModel.getName()) {
            case "dollar":
                baseValue = inputValue * 3200;
                break;
            case "pound":
                baseValue = inputValue * 4100;
                break;
            case "euro":
                baseValue = inputValue * 3400;
                break;
        }

        // 更新其他货币的值,并记录需要刷新的位置
        List<Integer> changedPositions = new ArrayList<>();
        for (int i = 0; i < mList.size(); i++) {
            if (position != i) {
                CurrencyModel model = mList.get(i);
                switch (currentModel.getName()) {
                    case "dollar":
                        model.setValue(baseValue / 3200);
                        break;
                    case "pound":
                        model.setValue(baseValue / 4100);
                        break;
                    case "euro":
                        model.setValue(baseValue / 3400);
                        break;
                }
                changedPositions.add(i);
            }
        }

        // 使用RecyclerView的post方法,确保在安全时机更新
        activity.recyc.post(() -> {
            // 只刷新变化的item,而非全量刷新
            for (int pos : changedPositions) {
                notifyItemChanged(pos);
            }
        });
    }

    @Override
    public int getItemCount() {
        return mList.size();
    }
}

额外优化建议

  • 输入防抖:如果用户快速输入字符,会频繁触发转换逻辑,可以添加防抖机制(比如用Handler延迟500ms执行转换,每次输入时取消之前的延迟任务),进一步减少不必要的计算和刷新。
  • 精度处理:货币值建议用BigDecimal代替double,避免浮点数精度丢失的问题。
  • 解耦Context强转:可以通过接口回调的方式传递数据更新逻辑,而不是直接将Context强转为MainActivity,让适配器更灵活、解耦。

内容的提问来源于stack exchange,提问作者Ehsan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:37:16