RecyclerView 优化

一、了解 RecyclerView

五虎上将

RecyclerView.LayoutManager

类型 作用
RecyclerView.LayoutManager 负责Item视图的布局的显示管理
RecyclerView.ItemDecoration 给每一项Item添加子View,例如可以进行画分割线之类
RecyclerView.ItemAnimator 负责处理数据添加或删除时的动画效果
RecyclerView.Adapter 为每一项Item创建视图
RecyclerView.ViewHolder 承载Item视图的子布局
RecyclerView.Recycler 负责处理View的缓存

调用关系

调用关系

二、缓存 ViewHolder

缓存机制

RecyclerView缓存机制

缓存流程

  • scrap --> cache --> ViewCacheExtension --> RecycledViewPool --> 创建新的ViewHolder
  • 滑出屏幕表项对应的ViewHolder会被回收到mCachedViews+mRecyclerPool结构中,mCachedViews是ArrayList,默认存储最多2个ViewHolder,当它放不下的时候,按照先进先出原则将最先进入的ViewHolder存入回收池的方式来腾出空间。mRecyclerPool是SparseArray,它会按viewType分类存储ViewHolder,默认每种类型最多存5个。
缓存流程1
缓存流程2

总结

Scrap

说明 结果
优先级 一级
重新创建ViewHolder false
重新绑定数据 false
容量 无大小限制,但最多包含屏幕可见表项
用途 用于布局过程中屏幕可见表项的回收和复用
类型 ArrayList
  • mAttachedScrap : 缓存显示在屏幕上的itemViewHolder
    • 按照idposition来查找ViewHolder
  • mChangedScrap : 缓存发生变化的itemViewHolder(notifXXX)
    • 按照idposition来查找ViewHolder

Cached

说明 结果
优先级 二级
重新创建ViewHolder false
重新绑定数据 false
容量 默认大小限制为2
用途 用于移出屏幕表项的回收和复用,且只能用于指定位置的表项,有点像“回收池预备队列”
类型 ArrayList
  • mCachedViews : 滑动过程中的回收和复用都是先处理的这个List
    • 只能复用于指定位置
    • 最大储存mViewCacheMax = mRequestedCacheMax + extraCacheextraCache是由prefetch的时候计算出来的)
    • 用于解决RecyclerView滑动抖动时的情况,还有用于保存Prefetch

ViewCacheExtension

说明 结果
优先级 二级
重新创建ViewHolder /
重新绑定数据 /
容量 /
用途 开发者可自定义的一层缓存
类型 /
  • mViewCacheExtension : 开发者可自定义的一层缓存,是虚拟类ViewCacheExtension的一个实例,开发者可实现方法getViewForPositionAndType(Recycler recycler, int position, int type)来实现自己的缓存
    • 适用场景(自备梯子)
    • 位置固定
    • 内容不变
    • 数量有限

Pool : 回收池

说明 结果
优先级 三级
重新创建ViewHolder false
重新绑定数据 true
容量 默认每种类型最多存5个
用途 用于移出屏幕表项的回收和复用,且只能用于指定viewType的表项
类型 ViewHolder按类型分类存储在SparseArray中,同类ViewHolder存储在ScrapData中的ArrayList
  • mRecyclerPool : 在有限的mCachedViews放不下的时候,按先进先出原则将最先进入的ViewHolder存入mRecyclerPool

三、优化

Ⅰ、基础优化

1.onBindViewHolder

  • 1.不要在onBind设置监听。因为mRecyclerPoolViewHolder时要重新调用onBindonClickListener应设置在ViewHolder
/*=========== Adapter ===========*/     
...
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int i) {
    ...
    // 把item对应的实体类传给itemView,方便点击事件发生时得到对应的资源。
    viewHolder.itemView.setTag(i);
}  

...
class ViewHolder extends RecyclerView.ViewHolder {
    ...
    private LinearLayout mItem;

    ViewHolder(@NonNull final View itemView) {
        ...
        mItem = itemView.findViewById(R.id.ll_item);

        // onClickListener 不要在 onBind 的时候设置,因为 mRecyclerPool 取 ViewHolder 时要重新调用 onBind
        mItem.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (null != mItemClick) {
                    // 设置点击监听
                    mItemClick.onItemClick(v, (int) itemView.getTag());
                }
            }
        });
    }
    ...
}

// Item监听
private OnRvItemClick mItemClick;

// 设置监听
public void setItemClick(OnRvItemClick mItemClick) {
    this.mItemClick = mItemClick;
}

public interface OnRvItemClick {
    // 点击项事件
    void onItemClick(View v, int position);
}

/*=========== Other ===========*/ 
// 设置点击项监听事件
Adapter.setItemClick(new OnRvItemClick() {
    @Override
    public void onItemClick(View v, int data) {
        mAdapter.getItem(data);
    }
});
  • 2.不要在onBindViewHolder做逻辑判断和计算
    bindViewHolder方法是在UI线程进行的,如果在该方法进行耗时操作,将会影响滑动的流畅性。

2.设置高度固定

  • 如果RecyclerView固定宽高,只是用于展示固定大小的组件,可设置recyclerView.setHasFixedSize(true)这样可避免每次绘制Item时,不再重新计算高度。
RecyclerView.setHasFixedSize(true);

3.重写 onScroll

  • 对于大量图片、复杂布局的RecyclerView考虑重写onScroll事件,滑动暂停后再加载。
/*
// 空闲状态
RecyclerView.SCROLL_STATE_IDLE
// 滚动状态
RecyclerView.SCROLL_STATE_FLING
// 触摸后状态
RecyclerView.SCROLL_STATE_TOUCH_SCROLL
 */
 
// 添加滑动监听
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
        // 空闲状态时加载图片(数据)
        if (newState == RecyclerView.SCROLL_STATE_IDLE) {
            Glide.with(mContext).resumeRequests();
        }else {
            Glide.with(mContext).pauseRequests();
        }
    }
});

4.最大程度不使用 notifyDataSetChanged

  • 对于新增或删除数据通过DiffUtil,来进行局部数据刷新,而不是一味的全局刷新数据。
    DiffUtil是support包下新增的一个工具类,用来判断新数据和旧数据的差别,从而进行局部刷新。
    DiffUtil的使用,在原来调用mAdapter.notifyDataSetChanged()的地方:
// 设置数据
public void setData(final List newData) {
    //notifyDataSetChanged();
    // 数据优化
    // 对于新增或删除数据通过DiffUtil,来进行局部数据刷新,而不是一味的全局刷新数据。
    DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() {
        @Override
        public int getOldListSize() {
            return getItemCount();
        }

        @Override
        public int getNewListSize() {
            return newData == null ? 0 : newData.size();
        }

        @Override
        public boolean areItemsTheSame(int i, int i1) {
            return true;
        }

        @Override
        public boolean areContentsTheSame(int i, int i1) {
            return false;
        }
    });
    diffResult.dispatchUpdatesTo(this);
    this.mData = newData;
}

5.减少每个 ItemView 的层级嵌套,减少过度绘制

6.刷新闪烁

  • 设置稳定Id
    eg:当调用notifyDataSetChanged的时候,recyclerView不知道到底发生了什么,所以它只能认为所有的东西都发生了变化,即,将所有的viewHolder都放入到pool中。
    但是,如果我们设置了stable ids,那么就会不一样了:viewHolder被放入了scrap中,而不是pool中。注意,这里,它的性能提升了很多!
    • 不用重新绑定,重新创建新的viewHolder,不用重新addViewaddView会导致重新测量…
    • 原本我们需要调用notifyItemMoved(4, 6),但是现在直接调用notifyDataSetChanged()就好了,但是测试结果不会有动画效果。
Adapter.setHasStableIds(true);

7.使用 RecyclerView Prefetch 功能

  • Prefetch 功能
  • 横向嵌套,利用RecyclerView数据预取功能。
// 在嵌套内部的LayoutManager中调用LinearLayoutManger的设置方法
// num的取值:如果列表刚刚展示4个半item,则设置为5
innerLLM.setInitialItemsPrefetchCount(num);

Ⅱ、缓存优化

1.mCachedViews

  • 设置mCachedViews大小
    eg:我们有一个壁纸库的列表,用户经常会上下(左右)滑动,那么我们增加cache的容量,就会获得更好得性能。
    然而对于feed流之类的列表,用户很少返回,所以增加cache容量意义不大。
RecyclerView.setItemViewCacheSize();

2.RecycledViewPool

  • 设置每个类型储存的容量
    eg1:在我们滑动的过程中,一个类型的viewHolderpool中应该一直只会存在一个(除非你使用了GridLayoutManager),所以,如果你的pool中存在多个viewHolder的话,他们在滚动过程中基本上是无用的。
    eg2:当我们调用notifyDataSetChanged()或者notifyItemRangeChanged(i, c)c这个范围非常大的时候),那么很多viewHolder都会最终被放入到pool中,因为pool只能放置5个,那么多余的就会被丢弃,等待回收。最重要的是会重新createbind对性能影响比较大。如果你的列表能够容纳很多行,而且使用notifyDataSetChanged方法比较频繁,那么你应该考虑设置一下容量大小。
RecyclerView.getRecycledViewPool().setMaxRecycledViews(0, 20);
  • 多个列表公用一个RecycledViewPool
    eg1:RecyclerView嵌套RecyclerView考虑设置RecyclerPool缓存
RecyclerView.setRecycledViewPool();
// RecyclerView + RecyclerView,每个item内部RecyclerView设同一个pool
private RecyclerView.RecycledViewPool childPool;

public XXAdapter(){
    childPool = new RecyclerView.RecycledViewPool();
}

private class RcyViewHolder extends RecyclerView.ViewHolder {
    private SRecyclerView sRcy;

    public RcyViewHolder(View itemView) {
        super(itemView);
        sRcy = itemView.findViewById(R.id.rcy_child);
        LinearLayoutManager manager = new LinearLayoutManager(mContext);
        // 1.设置回收
        manager.setRecycleChildrenOnDetach(true);
        manager.setOrientation(LinearLayoutManager.HORIZONTAL);
        sRcy.setLayoutManager(manager);
        // 2.设置缓存Pool
        sRcy.setRecycledViewPool(childPool);
    }
}

Ⅲ、其他优化

数据优化

  • 分页加载远端数据,对拉取的远端数据进行缓存,提高二次加载速度。

四、常见解决

1.局部刷新

@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position, @NonNull List payloads) {
  if (payloads.isEmpty()) {
    onBindViewHolder(holder, position);
  } else {
    // 局部刷新
    holder.setTitle("局部更新");
    }
}

...
// 局部刷新测试
mAdapter.notifyItemChanged(2, "payload");

 
 

相关资料
资料
资料
资料


2019/07/19 12:28:12

你可能感兴趣的:(RecyclerView 优化)