含大量长BigInteger的RecyclerView拖动滚动条卡顿崩溃如何解决
卡顿原因分析与优化方案
相关代码
RecyclerAdapter代码
public class CollatzAdapter extends RecyclerView.Adapter<CollatzAdapter.ViewHolder> { ArrayList<BigInteger> list = new ArrayList<BigInteger>(); List<Long> primeList; public CollatzAdapter() { } public CollatzAdapter(ArrayList<BigInteger> list, ArrayList<Long> primeList) { this.list = list; this.primeList = primeList; } public CollatzAdapter(ArrayList<BigInteger> list) { this.list = list; } @NonNull @Override public CollatzAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.row,parent,false); return new ViewHolder(view); } @Override public void onBindViewHolder(@NonNull CollatzAdapter.ViewHolder holder, int position) { holder.mIterationPosition.setText((position + ":")); holder.mNumber.setText(list.get(position).toString()); } @Override public int getItemCount() { return list.size(); } public class ViewHolder extends RecyclerView.ViewHolder { public TextView mNumber; public TextView mIterationPosition; public ViewHolder(@NonNull View itemView) { super(itemView); mNumber = itemView.findViewById(R.id.iteration_num); mIterationPosition = itemView.findViewById(R.id.iteration_position); //mPrime = itemView.findViewById(R.id.iteration_prime); } } public void setList(ArrayList<BigInteger> list) { this.list = list; } }
实现逻辑代码
iterationFragment = inflater.inflate(R.layout.fragment_iteration, container, false); collatzViewModel = new ViewModelProvider(getActivity()).get(CollatzViewModel.class); mRecyclerView = (RecyclerView) iterationFragment.findViewById(R.id.recycler_view); mRecyclerView.setHasFixedSize(false); mLayoutManager = new LinearLayoutManager(getActivity()); mAdapter = new CollatzAdapter(); mRecyclerView.setLayoutManager(mLayoutManager); mRecyclerView.setAdapter(mAdapter); collatzViewModel.getCollatz().observe(getViewLifecycleOwner(),(list)->{ mAdapter.setList(list); mAdapter.notifyDataSetChanged(); });
问题现象
当RecyclerView加载包含大量BigInteger的列表时,比如单条BigInteger长度达300字符、总条目数达10万条的场景,拖动滚动条时会出现严重卡顿,甚至会导致应用崩溃。按照RecyclerView的设计逻辑本应仅加载可视区域的少量BigInteger,出现该问题的原因和优化方案如下。
卡顿原因
- 全量刷新触发冗余重绘:代码中更新数据时使用
notifyDataSetChanged(),该方法会强制重绘所有item,完全没有利用RecyclerView的增量更新能力,10万条数据下重绘开销极高。 - 实时转换字符串阻塞主线程:
onBindViewHolder中每次绑定都调用BigInteger.toString(),300位大整数转字符串是重度CPU计算操作,滚动时连续触发会直接阻塞主线程。 - 布局测量冗余:
setHasFixedSize(false)配置会导致每次数据变更时,RecyclerView都要重新测量所有item计算自身大小,10万条数据的测量成本极高。 - 长文本渲染开销大:300字符的长文本每次绑定都要触发TextView的排版、换行计算,若item布局没有做优化,每个item的渲染成本会进一步升高。
- 内存抖动和OOM风险:10万条BigInteger本身占用内存较高,滚动时产生的大量临时字符串对象会触发频繁GC,严重时直接导致OOM崩溃。
优化方案
数据层优化
- 提前缓存字符串结果:ViewModel计算Collatz序列时,直接把所有BigInteger转换成String后再存储,不要在
onBindViewHolder中做实时转换,避免滚动时重复计算。 - 分页加载:不要一次性把10万条数据全部加载进内存,根据滚动位置分批加载,每次仅加载可视区域前后几十条数据即可。
RecyclerView配置优化
- 修正
setHasFixedSize配置:如果RecyclerView的宽高为match_parent或固定值,把setHasFixedSize(false)改成setHasFixedSize(true),避免不必要的全量测量。 - 替换全量刷新:用
DiffUtil做增量更新,仅刷新发生变更的item;首次加载全量数据时,用notifyItemRangeInserted(0, list.size())替代notifyDataSetChanged(),大幅降低更新开销。 - 增大缓存数量:调用
mRecyclerView.setItemViewCacheSize(20),根据单屏显示的item数调整缓存大小,多缓存一倍的item,减少滚动时的创建和绑定次数。
布局与绑定逻辑优化
- 简化
onBindViewHolder逻辑:仅做字符串赋值操作,不要在该方法中做任何计算、格式转换操作。 - 优化item布局:给显示数字的TextView设置固定宽高,关闭不必要的自动适配、省略号、输入法联想等属性,降低TextView的排版渲染开销。
内容的提问来源于stack exchange,提问作者Erick Sorto
相关产品推荐
相关产品推荐

