作者:opLW
参考:
1.启舰大神的文章
2.RecyclerView 源码分析(三) - RecyclerView的缓存机制
3.基于滑动场景解析RecyclerView的回收复用机制原理
最近RecyclerView用的很多,打算写一系列相关的文章,做一个总结,加深理解。? 有什么不对还望指出。(RV代表RecyclerView, LM代表LayoutManager,VH代表ViewHolder,RR代表Recycler)
目录
3.RR的回收
- 1)回收和复用对称存在构成了RV的缓存机制,前面我们对RV的复用有了一个大致的了解,下面看看RV的回收。我们知道在RV中,LM主要负责布局管理,那么我们就从LM最常用的几个回收View的方法入手,看看RV的回收,后面会有文章介绍自定义LM以及相关方法的使用。?
- 2)detachAndScrap系列 注意 对VH的一些标志(比如isRemoved等)有疑惑的可以看看RecyclerView 源码分析(三) - RecyclerView的缓存机制
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) { ==1== final int childCount = getChildCount(); for (int i = childCount - 1; i >= 0; i--) { final View v = getChildAt(i); scrapOrRecycleView(recycler, i, v); ==1== } } public void detachAndScrapView(@NonNull View child, @NonNull Recycler recycler) { ==1== int index = mChildHelper.indexOfChild(child); scrapOrRecycleView(recycler, index, child); ==1== } public void detachAndScrapViewAt(int index, @NonNull Recycler recycler) { ==1== final View child = getChildAt(index); scrapOrRecycleView(recycler, index, child); ==1== } private void scrapOrRecycleView(Recycler recycler, int index, View view) { ==2== final ViewHolder viewHolder = getChildViewHolderInt(view); // 省略部分代码 if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !mRecyclerView.mAdapter.hasStableIds()) { removeViewAt(index); ==3== recycler.recycleViewHolderInternal(viewHolder); ==4== } else { detachViewAt(index); ==3== recycler.scrapView(view); ==5== mRecyclerView.mViewInfoStore.onViewDetached(viewHolder); } }
- 1 可以看出LM中几个
detachAndScrap
开头的函数,最终都会调用2scrapOrRecycleView
进行回收。 - 3 注意 LM中有诸如:
removeViewAt
,detachViewAt
,addView
等方法,这些方法最终都是调用RV一个叫ChildHelper的内部类。这里简单介绍下这个类,具体就不深究了。我们知道RV其实也是一个ViewGroup
,那么就会涉及到对所要展示子View
的管理工作,包括添加,移除并且刷新界面,这一些操作最终会真正影响手机界面上显示的内容。那么这些工作就统一交给ChildHelper
去做。 - 4 当VH无效并且还没有被移除时调用Recycler的
recycleViewHolderInternal
方法,将VH回收到第二,第四级缓存。这里先跳过,后面详细介绍。 - 5 先看看
scrapView
的源码。
可以看出5会将一些还有效的VH放到一级缓存中。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); } }
- 1 可以看出LM中几个
- 3)removeAndRecycle系列
- 简介 removeAndRecycle系列有许多方法,但是最终都会调用RR的一个方法
recycleViewHolderInternal
,这个方法会涉及到其他几级的缓存。 - 下面看看源码:
//省略一些不合法回收的判断 //noinspection unchecked final boolean transientStatePreventsRecycling = holder .doesTransientStatePreventRecycling(); final boolean forceRecycle = mAdapter != null && transientStatePreventsRecycling && mAdapter.onFailedToRecycleView(holder); boolean cached = false; boolean recycled = false; if (DEBUG && mCachedViews.contains(holder)) { throw new IllegalArgumentException("cached view received recycle internal? " + holder + exceptionLabel()); } if (forceRecycle || holder.isRecyclable()) { if (mViewCacheMax > 0 && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) { // Retire oldest cached view int cachedViewSize = mCachedViews.size(); if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) { ===6=== recycleCachedViewAt(0); cachedViewSize--; } int targetCacheIndex = cachedViewSize; if (ALLOW_THREAD_GAP_WORK && cachedViewSize > 0 && !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) { // when adding the view, skip past most recently prefetched views int cacheIndex = cachedViewSize - 1; while (cacheIndex >= 0) { int cachedPos = mCachedViews.get(cacheIndex).mPosition; if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) { break; } cacheIndex--; } targetCacheIndex = cacheIndex + 1; } ===7=== mCachedViews.add(targetCacheIndex, holder); cached = true; } if (!cached) { ===8=== addViewHolderToRecycledViewPool(holder, true); recycled = true; } } else { // 省略部分代码,大致是与动画相关的 } // even if the holder is not removed, we still call this method so that it is removed // from view holder lists. mViewInfoStore.removeViewHolder(holder); if (!cached && !recycled && transientStatePreventsRecycling) { holder.mOwnerRecyclerView = null; }
- 6 对于合法可以回收的,在这里会先判断第二级缓存是否满了。满了则将第二级缓存的VH中的第一个先放到第四级mRecyclerPool中。(如果对四级缓存不是很了解可以先看看前面的RecyclerView (二) – 缓存复用机制(上))
- 7 将VH添加到第二级缓存中的末尾。
- 8 如果前面的没能成功放到第二级缓存中,则直接放到第四级中。
- 简介 removeAndRecycle系列有许多方法,但是最终都会调用RR的一个方法
- 4)总结 detachAndScrap系列,removeAndRecycle系列。第一个系列主要将一些需要临时从屏幕上移开的VH放到一级缓存(当然也有少数例外的)。第二个系列则真正的将VH回收到第二级和第四级中。两个系列的方法最终都是要经过Recycler真正的去管理缓存。
4.一些场景复用的情况
-
1)首次加载RV时
VH的创建情况
可以看见刚开始只创建了一个屏幕所能展示的VH,原因: 通过前面的分析我们知道了LM会通过Recycler.getViewForPosition
去得到一个VH的itemView。而此时RV刚刚创建,并没有可以复用的,所以会直接创建0-5共6个新的VH,用于布局屏幕。 -
2)向下滑动一段距离(中间的小插曲)
VH的创建情况
- 让我们分析一波,0和1通过
removeAndScrap
系列方法被回收并且放到了第二级缓存mCachedViews
中(因为刚开始mCachedViews
还没有内容,所以可以暂存两个,而且2还有部分可见座所以没被回收),7和8出现了但是因为没有可以复用的VH(此时mCachedViews
没有对应位置的VH可复用,mRecyclerPool还为空),所以调用了createViewHolder
创建新的VH。嗯嗯嗯这一切都合情合理,和我想得一毛一样? - 咦不对啊?Q1为什么滑动到8时,出现了9? Q2为什么9调用的是
createViewHolder
方法,瞬间对自己之前的想法产生了疑问?。后面经过查找找到了答案。 - 原来是自
RecyclerView
25版本之后,默认会有预取功能。大概介绍一下,就是RecyclerView会根据当前的滑动情况,预获取下一次可能会用到的VH,所以直接调用createViewHolder
方法。关于预取的后面再深入研究。下面我们暂且关闭预取功能。(预取功能是Google工程师给我们的拎一个福利,不推荐关闭,这里仅做实验?)
- 让我们分析一波,0和1通过
-
3)关闭之后的情况。
VH的创建情况
可以看出关闭之后,当我们滚动到9时才创建9,而且是直接onBindViewHolder
来复用mRecyclerPool中的内容 -
4)继续向下滑
VH的创建情况
显然现在mRrcyclerPool中已经有VH可以复用了,不用再创建。 -
5)往回滑动一段距离
VH的创建情况
相比上一张手机截图,我们多了10,9,8,7四个,而我们需要重新绑定的有9,8,7。咦??不是说mCachedView
默认缓存两个吗?按理说10和9在上一张截图所处的状态时应该都添加到了mCachedView
中呀。为什么9也要重新绑定呢,那是因为你回滚的时候下方被移除的也要添加到mCachedView
中呀,所以会挤掉最先添加的9。
5.总结
-其实上面的场景都是特殊的场景,仅供参考,具体的LayoutManager
对于ViewHolder的管理各不相同,什么时候添加View啊,先添加还是先移除啊等等各不相同。那我们学习这些有什么用呢?其实我们只要熟悉几个主要的回收和复用方法就好,了解四级缓存。因为他们是固定的。 ?
万水千山总是情,麻烦手下别留情。
如若讲得有不妥,文末留言告知我,
如若觉得还可以,收藏点赞要一起。
opLW原创七言律诗,转载请注明出处