RecyclerView批量更新报错,Handler优化后应用卡顿求助
解决RecyclerView更新时的IllegalStateException与卡顿问题
我来帮你分析并解决这两个困扰你的问题:
问题根源解析
- IllegalStateException:你在
EditText的afterTextChanged回调里直接调用notifyDataSetChanged(),而此时RecyclerView可能正处于布局计算或者滚动的过程中。RecyclerView内部有严格的断言机制,会阻止这种在布局/滚动期间修改数据集并通知更新的操作,因为这会导致布局状态不一致,所以抛出了Cannot call this method while RecyclerView is computing a layout or scrolling的错误。你用全局G.handler.post避开了断言,但带来了新的性能问题。 - 卡顿问题:每次输入字符就触发全量的
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
相关产品推荐
相关产品推荐

