RecyclerView性能优化的那些坑我帮你踩过了

兰兰 ☘︎ 交互 阅读 1,126
赞 6 收藏
二维码
手机扫码查看
反馈

RecyclerView不同方案对比,我踩过不少坑

最近项目重构,重新梳理了一下RecyclerView相关的技术方案。说实话,Android端的列表组件选择真的不少,每种都有各自的特点。今天就把我常用的几种方案做个对比,主要是为了以后自己查阅方便,顺便分享给有需要的朋友。

RecyclerView性能优化的那些坑我帮你踩过了

我主要对比的是原生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方案的对比总结,有更优的实现方式欢迎评论区交流。这个技巧的拓展用法还有很多,后续会继续分享这类博客。以上是我踩坑后的总结,希望对你有帮助。

本文章不代表JZTHEME立场,仅为作者个人观点 / 研究心得 / 经验分享,旨在交流探讨,供读者参考。
发表评论

暂无评论