这10张图拿去,别再说学不会RecyclerView的缓存复用机制了!

计算机软件开发 2024-10-2 16:39:10 34 0 来自 中国
ViewPager2是在RecyclerView的根本上构建而成的,意味着其可以复用RecyclerView对象的绝大部门特性,比如缓存复用机制等。
作为ViewPager2系列的第一篇,本篇的重要目的是快速遍及须要的前置知识,而内容的焦点,正是前面所提到的RecyclerView的缓存复用机制。
RecyclerView,顾名思义,它会接纳其列表项视图以供重用
详细而言,当一个列表项被移出屏幕后,RecyclerView并不会烧毁其视图,而是会缓存起来,以提供给新进入屏幕的列表项重用,这种重用可以:

  • 克制重复创建不须要的视图
  • 克制重复执行昂贵的findViewById
从而到达的改善性能、提升应用相应本事、降低功耗的结果。而要相识此中的工作原理,我们还得回到RecyclerView是怎样构建动态列表的这一步。
RecyclerView是怎样构建动态列表的?

与RecyclerView构建动态列表相干联的几个告急类中,Adapter与ViewHolder负责共同使用,共同界说RecyclerView列表项数据的展示方式,此中:

  • ViewHolder是一个包罗列表项视图(itemView)的封装容器,同时也是RecyclerView缓存复用的重要对象
  • Adapter则提供了数据<->视图 的“绑定”关系,其包罗以下几个关键方法:

    • onCreateViewHolder:负责创建并初始化ViewHolder及其关联的视图,但不会添补视图内容。
    • onBindViewHolder:负责提取适当的数据,添补ViewHolder的视图内容。

然而,这2个方法并非每一个进入屏幕的列表项都会回调,相反,由于视图创建及findViewById执行等动作都重要会合在这2个方法,每次都要回调的话反而效率不佳。因此,我们应该通过对ViewHolder对象积极地缓存复用,来只管淘汰对这2个方法的回调频次。

  • 最优环境是——取得的缓存对象恰恰是原先的ViewHolder对象,这种环境下既不必要重新创建该对象,也不必要重新绑定命据,即拿即用。
  • 次优环境是——取得的缓存对象虽然不是原先的ViewHolder对象,但由于二者的列表项范例(itemType)雷同,其关联的视图可以复用,因此只必要重新绑定命据即可。
  • 末了着实没办法了,才必要执行这2个方法的回调,即创建新的ViewHolder对象并绑定命据。
现实上,这也是RecyclerView从缓存中查找最佳匹配ViewHolder对象时所依照的优先级序次。而真正负责执行这项查找工作的,则是RecyclerView类中一个被称为接纳者的内部类——Recycler。
Recycler是怎样查找ViewHolder对象的?

    /**     * ...     * When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and     * first level cache to find a matching View. If it cannot find a suitable View, Recycler will     * call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking     * {@link RecycledViewPool}.     *      * 当调用getViewForPosition(int)方法时,Recycler会查抄attached scrap和一级缓存(指的是mCachedViews)以找到匹配的View。      * 如果找不到符合的View,Recycler会先调用ViewCacheExtension的getViewForPositionAndType(RecyclerView.Recycler, int, int)方法,再查抄RecycledViewPool对象。     * ...     */    public abstract static class ViewCacheExtension {        ...    }    public final class Recycler {        ...        /**         * Attempts to get the ViewHolder for the given position, either from the Recycler scrap,         * cache, the RecycledViewPool, or creating it directly.         *          * 实行通过从Recycler scrap缓存、RecycledViewPool查找或直接创建的情势来获取指定位置的ViewHolder。         * ...         */        @Nullable        ViewHolder tryGetViewHolderForPositionByDeadline(int position,                boolean dryRun, long deadlineNs) {            if (mState.isPreLayout()) {                // 0 实行从mChangedScrap中获取ViewHolder对象                holder = getChangedScrapViewForPosition(position);                ...            }            if (holder == null) {                // 1.1 实行根据position从mAttachedScrap或mCachedViews中获取ViewHolder对象                holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);                ...            }            if (holder == null) {                ...                final int type = mAdapter.getItemViewType(offsetPosition);                if (mAdapter.hasStableIds()) {                    // 1.2 实行根据id从mAttachedScrap或mCachedViews中获取ViewHolder对象                    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),                            type, dryRun);                    ...                }                if (holder == null && mViewCacheExtension != null) {                    // 2 实行从mViewCacheExtension中获取ViewHolder对象                    final View view = mViewCacheExtension                            .getViewForPositionAndType(this, position, type);                    if (view != null) {                        holder = getChildViewHolder(view);                        ...                    }                }                if (holder == null) { // fallback to pool                    // 3 实行从mRecycledViewPool中获取ViewHolder对象                    holder = getRecycledViewPool().getRecycledView(type);                    ...                }                if (holder == null) {                    // 4.1 回调createViewHolder方法创建ViewHolder对象及其关联的视图                    holder = mAdapter.createViewHolder(RecyclerView.this, type);                    ...                }            }                if (mState.isPreLayout() && holder.isBound()) {                ...            } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {                ...                // 4.1 回调bindViewHolder方法提取数据添补ViewHolder的视图内容                bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);            }                ...                return holder;        }        ...    }    团结RecyclerView类中的源码及注释可知,Recycler会依次从mChangedScrap/mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool中实行获取指定位置或ID的ViewHolder对象以供重用,如果全都获取不到则直接重新创建。这此中涉及的几层缓存布局分别是:
mChangedScrap/mAttachedScrap

mChangedScrap/mAttachedScrap重要用于临时存放仍在当前屏幕可见、但被标记为「移除」或「重用」的列表项,其均以ArrayList的情势持有着每个列表项的ViewHolder对象,巨细无明确限定,但一样平常来讲,其最大数就是屏幕内总的可见列表项数。
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();    ArrayList<ViewHolder> mChangedScrap = null;但问题来了,既然是当前屏幕可见的列表项,为什么还必要缓存呢?又是什么时间列表项会被标记为「移除」或「重用」的呢?
这2个缓存布局现实上更多是为了克制出现像局部革新这一类的利用,导致全部的列表项都必要重绘的环境。
区别在于,mChangedScrap重要的使用场景是:

  • 开启了列表项动画(itemAnimator),而且列表项动画的canReuseUpdatedViewHolder(ViewHolder viewHolder)方法返回false的条件下;
  • 调用了notifyItemChanged、notifyItemRangeChanged这一类方法,关照列表项数据发生变革;
    boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {        return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,                viewHolder.getUnmodifiedPayloads());    }        public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,            @NonNull List<Object> payloads) {        return canReuseUpdatedViewHolder(viewHolder);    }            public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder) {        return true;    }canReuseUpdatedViewHolder方法的返回值表示的差别寄义如下:

  • true,表示可以重用原先的ViewHolder对象
  • false,表示应该创建该ViewHolder的副本,以便itemAnimator使用两者来实现动画结果(比方交错淡入淡出结果)。
简朴讲就是,mChangedScrap重要是为列表项数据发生变革时的动画结果服务的
mAttachedScrap应对的则是剩下的绝大部门场景,比如:

  • 像notifyItemMoved、notifyItemRemoved这种列表项发生移动,但列表项数据自己没有发生变革的场景。
  • 关闭了列表项动画,大概列表项动画的canReuseUpdatedViewHolder方法返回true,即允许重用原先的ViewHolder对象的场景。
下面以一个简朴的notifyItemRemoved(int position)利用为例来演示:
notifyItemRemoved(int position)方法用于关照观察者,先前位于position的列表项已被移除, 其今后的列表项position都将往前移动1位。
为了简化问题、方便演示,我们的范例将会居于以下限定:

  • 列表项总个数没有铺满整个屏幕——意味着不会触发mCachedViews、mRecyclerPool等布局的缓存利用
  • 去除列表项动画——意味着调用notifyItemRemoved后RecyclerView只会重新布局子视图一次
  recyclerView.itemAnimator = null抱负环境下,调用notifyItemRemoved(int position)方法后,应只有位于position的列表项会被移除,其他的列表项,无论是位于position之前或之后,都最多只会调解position值,而不应发生视图的重新创建或数据的重新绑定,即不应该回调onCreateViewHolder与onBindViewHolder这2个方法。
为此,我们就必要将当前屏幕内的可见列表项临时从当前屏幕剥离,临时缓存到mAttachedScrap这个布局中去。
比及RecyclerView重新开始布局体现其子视图后,再遍历mAttachedScrap找到对应position的ViewHolder对象举行复用。
2.png mCachedViews

mCachedViews重要用于存放已被移出屏幕、但有大概很快重新进入屏幕的列表项。其同样是以ArrayList的情势持有着每个列表项的ViewHolder对象,默认巨细限定为2。
    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();    int mViewCacheMax = DEFAULT_CACHE_SIZE;    static final int DEFAULT_CACHE_SIZE = 2;比如像朋侪圈这种按更新时间的先后序次展示的Feed流,我们常常会在快速滑动中确定是否有自己感爱好的内容,当意识到刚才滑走的内容大概比较风趣时,我们通常就会将上一条内容重新滑返来检察。
这种场景下我们寻求的自然是上一条内容展示的及时性与完备性,而不应让用户产生“才滑走那么一会儿又要重新加载”的抱怨,也即同样不应发生视图的重新创建或数据的重新绑定。
我们用几张流程表示图来演示这种环境:
同样为了简化问题、方便形貌,我们的范例将会居于以下限定:

  • 关闭预拉取——意味着之后向上滑动时,都不会再预拉取「待进入屏幕地域」的一个列表项放入mCachedView了
recyclerView.layoutManager?.isItemPrefetchEnabled = false

  • 只存在一种范例的列表项,即全部列表项的itemType雷同,默认都为0。
我们将图中的列表项分成了3块地域,分别是被滑出屏幕之外的地域、屏幕内的可看法域、随着滑动手势待进入屏幕的地域。
3.png

  • 当position=0的列表项随着向上滑动的手势被移出屏幕后,由于mCachedViews初始容量为0,因此可直接放入;

  • 当position=1的列表项同样被移出屏幕后,由于未到达mCachedViews的默认容量巨细限定,因此也可继续放入;

  • 此时改为向下滑动,position=1的列表项重新进入屏幕,Recycler就会依次从mAttachedScrap、mCachedViews查找可重用于此位置的ViewHolder对象;
  • mAttachedScrap不是应对这种环境的,自然找不到。而mCachedViews会遍历自身持有的ViewHolder对象,对比ViewHolder对象的position值与待复用位置的position值是否同等,是的话就会将ViewHolder对象从mCachedViews中移除并返回;
  • 此处拿到的ViewHolder对象即可直接复用,即符合前面所述的最优环境

  • 别的,随着position=1的列表项重新进入屏幕,position=7的列表项也会被移出屏幕,该位置的列表项同样会进入mCachedViews,即RecyclerView是双向缓存的。
mViewCacheExtension

mViewCacheExtension重要用于提供额外的、可由开发职员自由控制的缓存层级,属于非通例使用的环境,因此这里暂不睁开讲。
mRecyclerPool

mRecyclerPool重要用于按差别的itemType分别存放超出mCachedViews限定的、被移出屏幕的列表项,其会先以SparseArray区分差别的itemType,然后每种itemType对应的值又以ArrayList的情势持有着每个列表项的ViewHolder对象,每种itemType的ArrayList巨细限定默以为5。
    public static class RecycledViewPool {        private static final int DEFAULT_MAX_SCRAP = 5;        static class ScrapData {            final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();            int mMaxScrap = DEFAULT_MAX_SCRAP;            long mCreateRunningAverageNs = 0;            long mBindRunningAverageNs = 0;        }        SparseArray<ScrapData> mScrap = new SparseArray<>();        ...    }由于mCachedViews默认的巨细限定仅为2,因此,当滑出屏幕的列表项高出2个后,就会按照先辈先出的序次,依次将ViewHolder对象从mCachedViews移出,并按itemType放入RecycledViewPool中的差别ArrayList<ViewHolder>。
这种缓存布局重要思量的是随着被滑出屏幕列表项的增多,以及被滑出距离的越来越远,重新进入屏幕内的大概性也随之降低。于是Recycler就在时间与空间上做了一个权衡,允许雷同itemType的ViewHolder被提取复用,只必要重新绑定命据即可。
如许一来,既可以克制无穷增长的ViewHolder对象缓存挤占了本来就告急的内存空间,又可以淘汰回调相比较之下执行代价更加昂贵的onCreateViewHolder方法。
同样我们用几张流程表示图来演示这种环境,这些表示图将在前面的mCachedViews表示图根本上继续利用:

  • 假设现在存在于mCachedViews中的还是position=0及position=1这两个列表项。
  • 当我们继续向上滑动时,position=2的列表项会实行进入mCachedViews,由于超出了mCachedViews的容量限定,position=0的列表项会从mCachedViews中被移出,并放入RecycledViewPool中itemType为0的ArrayList<ViewHolder>,即图中的环境①;

  • 同时,底部的一个新的列表项也将随着滑动手势进入到屏幕内,但由于此时mAttachedScrap、mCachedViews、mRecyclerPool均没有符合的ViewHolder对象可以提供给其复用,因此该列表项只能执行onCreateViewHolder与onBindViewHolder这2个方法的回调,即图中的环境②;
  • 比及position=2的列表项被完全移出了屏幕后,也就顺遂进入了mCachedViews中。

  • 我们继续保持向上滑动的手势,此时,由于下一个待进入屏幕的列表项与position=0的列表项的itemType雷同,因此我们可以在走到从mRecyclerPool查找符合的ViewHolder对象这一步时,根据itemType找到对应的ArrayList<ViewHolder>,再取出此中的1个ViewHolder对象举行复用,即图中的环境①。
  • 由于itemType范例同等,其关联的视图可以复用,因此只必要重新绑定命据即可,即符合前面所述的次优环境
10.png

  • ②③ 环境与前面的同等,此处不再赘余。
末了总结一下,
RecyclerView缓存复用机制对象ViewHolder(包罗列表项视图(itemView)的封装容器)目的淘汰对onCreateViewHolder、onBindViewHolder这2个方法的回调长处1.克制重复创建不须要的视图 2.克制重复执行昂贵的findViewById结果改善性能、提升应用相应本事、降低功耗焦点类Recycler、RecyclerViewPool缓存布局mChangedScrap/mAttachedScrap、mCachedViews、mViewCacheExtension、mRecyclerPool缓存布局容器范例容量限定缓存用途优先级序次(数值越小,优先级越高)mChangedScrap/mAttachedScrapArrayList<ViewHolder>无,一样平常为屏幕内总的可见列表项数临时存放仍在当前屏幕可见、但被标记为「移除」或「重用」的列表项0mCachedViewsArrayList<ViewHolder>默以为2存放已被移出屏幕、但有大概很快重新进入屏幕的列表项1mViewCacheExtension开发者自己界说无提供额外的可由开发职员自由控制的缓存层级2mRecyclerPoolSparseArray<ArrayList<ViewHolder>>每种itemType默以为5按差别的itemType分别存放超出mCachedViews限定的、被移出屏幕的列表项3以上的就是RecyclerView缓存复用机制的焦点内容了。
您需要登录后才可以回帖 登录 | 立即注册

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

GMT+8, 2024-11-21 20:56, Processed in 0.155950 second(s), 35 queries.© 2003-2025 cbk Team.

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