RecyclerView (二) -- 缓存复用机制(下)

作者:opLW
参考:
1.启舰大神的文章
2.RecyclerView 源码分析(三) - RecyclerView的缓存机制
3.基于滑动场景解析RecyclerView的回收复用机制原理
最近RecyclerView用的很多,打算写一系列相关的文章,做一个总结,加深理解。? 有什么不对还望指出。(RV代表RecyclerViewLM代表LayoutManagerVH代表ViewHolderRR代表Recycler)

  1. RecyclerView (一) – 绘制流程
  2. RecyclerView (二) – 缓存复用机制(上)

目录

3.RR的回收
4.一些场景复用的情况

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中有诸如:removeViewAtdetachViewAtaddView等方法,这些方法最终都是调用RV一个叫ChildHelper的内部类。这里简单介绍下这个类,具体就不深究了。我们知道RV其实也是一个ViewGroup,那么就会涉及到对所要展示子View的管理工作,包括添加,移除并且刷新界面,这一些操作最终会真正影响手机界面上显示的内容。那么这些工作就统一交给ChildHelper去做。
    • 4 当VH无效并且还没有被移除时调用Recycler的recycleViewHolderInternal方法,将VH回收到第二,第四级缓存。这里先跳过,后面详细介绍。
    • 5 先看看scrapView的源码。
      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);
             }
         }
      
      可以看出5会将一些还有效的VH放到一级缓存中。
  • 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 如果前面的没能成功放到第二级缓存中,则直接放到第四级中。
  • 4)总结 detachAndScrap系列,removeAndRecycle系列。第一个系列主要将一些需要临时从屏幕上移开的VH放到一级缓存(当然也有少数例外的)。第二个系列则真正的将VH回收到第二级和第四级中。两个系列的方法最终都是要经过Recycler真正的去管理缓存。

4.一些场景复用的情况
  • 1)首次加载RV时
    在这里插入图片描述
    VH的创建情况
    在这里插入图片描述
    可以看见刚开始只创建了一个屏幕所能展示的VH,原因: 通过前面的分析我们知道了LM会通过Recycler.getViewForPosition去得到一个VH的itemView。而此时RV刚刚创建,并没有可以复用的,所以会直接创建0-5共6个新的VH,用于布局屏幕。

  • 2)向下滑动一段距离(中间的小插曲)
    在这里插入图片描述
    VH的创建情况
    在这里插入图片描述

    • 让我们分析一波,01通过removeAndScrap系列方法被回收并且放到了第二级缓存mCachedViews中(因为刚开始mCachedViews还没有内容,所以可以暂存两个,而且2还有部分可见座所以没被回收),78出现了但是因为没有可以复用的VH(此时mCachedViews没有对应位置的VH可复用,mRecyclerPool还为空),所以调用了createViewHolder创建新的VH。嗯嗯嗯这一切都合情合理,和我想得一毛一样?
    • 咦不对啊?Q1为什么滑动到8时,出现了9 Q2为什么9调用的是createViewHolder方法,瞬间对自己之前的想法产生了疑问?。后面经过查找找到了答案。
    • 原来是自RecyclerView25版本之后,默认会有预取功能。大概介绍一下,就是RecyclerView会根据当前的滑动情况,预获取下一次可能会用到的VH,所以直接调用createViewHolder方法。关于预取的后面再深入研究。下面我们暂且关闭预取功能。(预取功能是Google工程师给我们的拎一个福利,不推荐关闭,这里仅做实验?)
  • 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原创七言律诗,转载请注明出处

发布了21 篇原创文章 · 获赞 28 · 访问量 7323

猜你喜欢

转载自blog.csdn.net/qq_36518248/article/details/90180609
今日推荐