RecyclerView缓存复用原理

手机软件开发 2024-9-12 10:57:15 16 0 来自 中国
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<ViewHolder> mAttachedScrap = new ArrayList<>();    ArrayList<ViewHolder> mChangedScrap = null;     final ArrayList<ViewHolder> mCachedViews = new ArrayList<>();        RecycledViewPool mRecyclerPool;        private ViewCacheExtension mViewCacheExtension;}复用机制

在滑动过程中,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类中的源码及表明可知,Recycler会依次从mChangedScrap/mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool中实行获取指定位置或ID的ViewHolder对象以供重用,假如全都获取不到则直接重新创建。
缓存机制

统共有四级缓存,按照优先级分:


  • 一级缓存:
一级缓存的代码:
        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<ViewHolder>();                }                holder.setScrapContainer(this, true);                mChangedScrap.add(holder);            }        }scrap是用来生存被rv移撤除但迩来又立刻要利用的缓存,好比说rv中自带item的动画效果。
mChangedScrap/mAttachedScrap

轻微仔细看的话就能发现scrap缓存有两个成员mChangedScrap和mAttachedScrap,它们生存的对象有些不一样,一样寻常调用adapter的notifyItemRangeChanged被移除的viewholder会生存到mChangedScrap,别的的notify系列方法(不包罗notifyDataSetChanged)移除的viewholder会被生存到mAttachedScrap中。用来缓存还在屏幕内的viewholder
二级缓存: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<ViewHolder> 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才气利用。以是二级缓存的复用速率会比四级缓存的复用速率更高。
mCachedViews只会缓存2个viewholder,就是屏幕滑出去的上面一个item和下面一个item。当上面一个item刚滑出去大概下面一个item刚滑出去,缓存在mCachedViews内里,内容不会被清空。此时再把这个item滑入屏幕内,不会走onBindViewHolder,由于数据还在。而随着往上或往下继承滑动更多,移除屏幕的item高出2个,mCachedViews缓存容量不足,就会将滑出屏幕的viewholder往recyclerViewPool中缓存。而把这些更多的item滑回屏幕时,这些缓存的viewholder数据已经被清掉,以是会调用onBindViewHolder方法重设数据
画图总结:

1.png 参考:
RecyclerView 的缓存复用机制
https://www.bilibili.com/video/BV1Yb4y1R7xn?p=3&spm_id_from=pageDriver&vd_source=40c24e77b23dc2e50de2b7c87c6fed59
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 20:31, Processed in 0.137746 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表