RecyclerView性能优化的那些坑我帮你踩过了
RecyclerView不同方案对比,我踩过不少坑
最近项目重构,重新梳理了一下RecyclerView相关的技术方案。说实话,Android端的列表组件选择真的不少,每种都有各自的特点。今天就把我常用的几种方案做个对比,主要是为了以后自己查阅方便,顺便分享给有需要的朋友。
我主要对比的是原生RecyclerView、ConcatAdapter方案,还有Google推荐的Paging3集成方案。这几种我都在项目里用过,各有各的坑。
三种方案的基本实现
先看看最基础的原生RecyclerView实现:
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private List<String> dataList;
public MyAdapter(List<String> data) {
this.dataList = data;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_layout, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
String item = dataList.get(position);
holder.textView.setText(item);
}
@Override
public int getItemCount() {
return dataList.size();
}
static class ViewHolder extends RecyclerView.ViewHolder {
TextView textView;
ViewHolder(View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.text_view);
}
}
}
ConcatAdapter相对简洁一些,多个Adapter组合:
ConcatAdapter concatAdapter = new ConcatAdapter();
concatAdapter.addAdapter(headerAdapter);
concatAdapter.addAdapter(contentAdapter);
concatAdapter.addAdapter(footerAdapter);
recyclerView.setAdapter(concatAdapter);
Paging3的实现稍微复杂点,需要定义DataSource:
@Singleton
class Repository @Inject constructor(
private val apiService: ApiService,
private val db: AppDatabase
) {
fun getItems(): Flow<PagingData<Item>> {
return Pager(
config = PagingConfig(pageSize = 20),
pagingSourceFactory = { ItemPagingSource(apiService, db) }
).flow.cachedIn(viewModelScope)
}
}
// 在ViewModel中
val items: Flow<PagingData<Item>> = repository.getItems()
.map { pagingData ->
pagingData.map { item -> item.toUiModel() }
}.cachedIn(viewModelScope)
谁更灵活?谁更省事?
从灵活性来说,原生RecyclerView肯定是王道。你想怎么玩就怎么玩,各种动画、特殊布局、复杂的交互逻辑都能实现。不过代价就是代码量大,维护成本高。我之前做那个电商项目的商品详情页,因为需要复杂的吸顶效果和联动,最后还是回到了原生方案。
ConcatAdapter在处理多个不同类型的section时确实省事,特别是头部广告、商品列表、底部加载更多这种常见布局。但我踩过一个坑,就是数据更新时容易出现位置错乱的问题。记得有一次线上崩溃,就是因为ConcatAdapter内部的position映射出了问题。
Paging3最大的优势是处理大数据分页加载,内存管理做得很好。但是学习成本相对高一些,而且对于简单的列表展示就显得有点重了。我在做那种只有几条数据的小列表时,完全不会考虑Paging3。
性能对比:差距比我想象的大
我做过一个小测试,在模拟器里加载1000条数据,看看各方案的表现。原生RecyclerView的初次渲染速度最快,大概120ms左右,毕竟是最直接的操作。ConcatAdapter稍慢一点,因为涉及到多个adapter的协调,约140ms。
Paging3的首屏渲染时间最长,大概200ms,因为它要做预加载和缓存管理。但一旦页面稳定,滚动流畅度是最好的。特别是在快速滑动时,Paging3的表现明显优于其他两种方案。
内存占用方面,Paging3的优势很明显。加载大量数据时,它的内存占用比其他方案低30%左右。这主要是因为Paging3做了很好的数据缓存和释放机制。
我的选型逻辑
看场景吧,我一般这么选:
- 简单列表(少于100条数据):直接用原生RecyclerView,简单粗暴
- 多section列表(头部+内容+底部):优先考虑ConcatAdapter
- 大数据列表(超过1000条):毫不犹豫选择Paging3
其实还有一个重要因素是团队成员的技术栈。如果团队里大部分人都只熟悉传统的RecyclerView,那强行推Paging3可能会增加沟通成本。我们团队之前就有过这种情况,为了新技术而新技术,结果反而拖慢了开发进度。
另外需要注意的一点是兼容性。Paging3需要API 21以上,如果你的项目还要支持老版本系统,那就得三思了。我之前的项目就因为这个问题,最终放弃了Paging3。
还有一个我踩过的坑,就是在Fragment里使用ConcatAdapter时,生命周期管理很重要。如果处理不当,会出现数据错乱或者内存泄漏。建议配合Fragment的onViewCreated和onDestroyView来做相应的适配器初始化和清理。
踩坑提醒:这三点一定注意
首先是DiffUtil的使用。无论是哪种方案,数据更新时都要记得用DiffUtil来优化性能。我之前就是直接notifyDataSetChanged(),导致列表滑动卡顿严重。改成DiffUtil后,性能提升明显。
其次是异步线程问题。所有数据更新操作都必须在主线程执行,这个很容易忘。我曾经在子线程里直接调用adapter.notifyItemChanged(),结果各种奇怪问题都出现了。
最后是内存泄漏。RecyclerView的缓存机制很复杂,不当使用会导致内存居高不下。特别是结合网络请求时,记得及时清理相关的回调和引用。
以上是我个人对这些RecyclerView方案的对比总结,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多,后续会继续分享这类博客。以上是我踩坑后的总结,希望对你有帮助。

暂无评论