RecyclerView缓存复用原理

RecyclerView缓存复用机制

来到RecyclerView的Adapter代码中:

    @NonNull
    @Override
    public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.item, parent, false);
        Log.i("minfo", "onCreateViewHolder");
        return new MyViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
        ((TextView)holder.textView).setText(datas.get(position));
        Log.i("minfo", "onBindViewHolder");
    }

在onCreateViewHolder方法和onBindViewHolder方法打印,不断滑动recyclerView,会发现一个现象就是刚开始都会调两个方法,但是调用一定次数之后,就只会调onBindViewHolder方法了。那么这个滑动过程中,就是在不断地回收复用。

首先要明白,RecyclerView缓存复用的是什么?缓存复用的是ViewHolder,ViewHolder是itemView的封装类。

Recycler 是 RecyclerView 的内部类,也是这套复用机制的核心,显然 Recycler 的主要成员变量也都是用来缓存和复用 ViewHolder 的:

public final class Recycler {
    final ArrayList mAttachedScrap = new ArrayList<>();
    ArrayList mChangedScrap = null;
 
    final ArrayList mCachedViews = new ArrayList<>();
    
    RecycledViewPool mRecyclerPool;
    
    private ViewCacheExtension mViewCacheExtension;
}
缓存机制
总共有四级缓存,按照优先级分:
  • 一级缓存:

一级缓存的代码

        void scrapView(View view) {
            final ViewHolder holder = getChildViewHolderInt(view);
            if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
                    || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
                if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
                    throw new IllegalArgumentException("Called scrap view with an invalid view."
                            + " Invalid views cannot be reused from scrap, they should rebound from"
                            + " recycler pool." + exceptionLabel());
                }
                holder.setScrapContainer(this, false);
                mAttachedScrap.add(holder);
            } else {
                if (mChangedScrap == null) {
                    mChangedScrap = new ArrayList();
                }
                holder.setScrapContainer(this, true);
                mChangedScrap.add(holder);
            }
        }

scrap是用来保存被rv移除掉但最近又马上要使用的缓存,比如说rv中自带item的动画效果。

稍微仔细看的话就能发现scrap缓存有两个成员mChangedScrap和mAttachedScrap,它们保存的对象有些不一样,一般调用adapter的notifyItemRangeChanged被移除的viewholder会保存到mChangedScrap,其余的notify系列方法(不包括notifyDataSetChanged)移除的viewholder会被保存到mAttachedScrap中。

二级缓存:mCachedViews ,用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存容量是 2,可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 先进先出 的规则移除旧 ViewHolder,再将viewHolder添加进RecycledViewPool中。

三级缓存:ViewCacheExtension ,开发给用户的自定义扩展缓存,需要用户自己管理 View 的创建和缓存。通过提供给开发者抽象方法来自己实现缓存。

四级缓存:RecycledViewPool ,ViewHolder 缓存池,在有限的 mCachedViews 中如果存不下新的 ViewHolder 时,会从cachedView缓存中移除,然后将移除的 ViewHolder 存入RecyclerViewPool 中。

将缓存viewHolder添加到recyclerViewPool中,根据itemtype获取对应的ScrapData数据,里面存储着该itemType的缓存集合。RecycledViewPool默认大小为5,当大于了最大容量,就不再缓存。

可以通过以下方式修改RecycledViewPool的缓存大小:

RecyclerView.getRecycledViewPool().setMaxRecycledViews(int viewType, int max);
        public void putRecycledView(ViewHolder scrap) {
            final int viewType = scrap.getItemViewType();
            final ArrayList scrapHeap = getScrapDataForType(viewType).mScrapHeap;
            if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
                return;
            }
            if (DEBUG && scrapHeap.contains(scrap)) {
                throw new IllegalArgumentException("this scrap item already exists");
            }
            scrap.resetInternal();
            scrapHeap.add(scrap);
        }
 
//根据itemtype获取对应的ScrapData数据
private ScrapData getScrapDataForType(int viewType) {
            ScrapData scrapData = mScrap.get(viewType);
            if (scrapData == null) {
                scrapData = new ScrapData();
                mScrap.put(viewType, scrapData);
            }
            return scrapData;
        }
mCachedViews二级缓存与四级缓存RecycledViewPool的区别:

cacheView缓存的viewHolder带有数据,而四级缓存已经清空了数据,只是缓存了viewHolder,从 RecyclerViewPool 中取出来的 ViewHolder 需要重新执行 bindViewHolder才能使用。所以二级缓存的复用速度会比四级缓存的复用速度更高。

绘图总结:
复用机制

在滑动过程中,recyclerView不断在复用ViewHolder来显示item,那么就从recyclerView的滑动事件入手源码,然后步步深入:

 RecyclerView.onLayout(...)
-> RecyclerView.dispatchLayout()    
-> RecyclerView.dispatchLayoutStep2() // do the actual layout of the views for the final state.
-> mLayout.onLayoutChildren(mRecycler, mState) // mLayout 类型为 LayoutManager
-> LinearLayoutManager.onLayoutChildren(...) // 以 LinearLayoutManager 为例
-> LinearLayoutManager.fill(...) // The magic functions :) 填充给定的布局,注释很自信的说这个方法很独立,稍微改动就能作为帮助类的一个公开方法,程序员的快乐就是这么朴实无华。
-> LinearLayoutManager.layoutChunk(recycler, layoutState) // 循环调用,每次调用填充一个 ItemView 到 RV
-> LinearLayoutManager.LayoutState.next(recycler) 
-> RecyclerView.Recycler.getViewForPosition(int) // 回到主角了,通过 Recycler 获取指定位置的 ItemView    
-> Recycler.getViewForPosition(int, boolean) // 调用下面方法获取 ViewHolder,并返回上面需要的 viewHolder.itemView 
-> Recycler.tryGetViewHolderForPositionByDeadline(...)

最终调用 tryGetViewHolderForPositionByDeadline方法来获取ViewHolder,这个方法里面是复用的核心,方法部分代码

ViewHolder tryGetViewHolderForPositionByDeadline(int position, ...) {
    if (mState.isPreLayout()) {
        // 从 mChangedScrap 里面去获取 ViewHolder,动画相关
        holder = getChangedScrapViewForPosition(position);
    }
    
    if (holder == null) {
        // mAttachedScrap、 mHiddenViews、mCachedViews 获取 ViewHolder
        //    这个 mHiddenViews 是用来做动画期间的复用
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
    }
    
    if (holder == null) {
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 如果 Adapter 的 hasStableIds 方法返回为 true
        //    优先通过 ViewType 和 ItemId 两个条件从 mAttachedScrap 和 mCachedViews 寻找
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
        }
      
        if (holder == null && mViewCacheExtension != null) {
            //从自定义缓存获取
            View view = mViewCacheExtension
                getViewForPositionAndType(this, position, type);
            holder = getChildViewHolder(view);
        }
    }
    
    if (holder == null) {
        //从 RecycledViewPool 获取 ViewHolder
        holder = getRecycledViewPool().getRecycledView(type);
    }
  
    if (holder == null) {
        // 如果四级缓存里都没有,那就再调用createViewHolder创建ViewHolder
        holder = mAdapter.createViewHolder(RecyclerView.this, type);
    }
}

参考:
RecyclerView 的缓存复用机制

版权声明:
作者:Zad
链接:https://www.techfm.club/p/53385.html
来源:TechFM
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>