美文网首页Android篇
Recyclerview缓存复用机制

Recyclerview缓存复用机制

作者: w达不溜w | 来源:发表于2021-03-14 10:42 被阅读0次

    先了解一下Recyclerview主要有哪些类

    类名 作用
    LayoutManager 负责ItemView的布局和显示管理
    ItemDecoration 给ItemView添加子View,如分割线
    ItemAnimator 添加或删除的动画效果
    Adapter 为ItemView创建视图
    ViewHolder 承载包装ItemView
    Recycler 四级缓存
    Recycler

    我们先要搞清楚缓存复用的对象是谁?ViewHolder(包装View Recyclerview的一个itemView)
    Recycler是Recyclerview的内部类,它的主要成员变量是用来缓存和复用ViewHolder的。

    public final class Recycler {
       
        final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
      
        ArrayList<ViewHolder> mChangedScrap = null;
    
        final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
    
        RecycledViewPool mRecyclerPool;
    
        private ViewCacheExtension mViewCacheExtension;
      
            //...
    }
    

    一级缓存:mAttachedScrap和mChangedScrap (用来缓存还在屏幕内的ViewHolder)

    • mAttachedScrap存储当前屏幕中的ViewHolder,按id和position来查找ViewHolder
    • mChangedScrap表示数据已经改变的ViewHolder,存储notifyXXX方法时需要改变的ViewHolder

    二级缓存:mCachedViews(用来缓存移出到屏幕之外的ViewHolder)

    默认情况下缓存容量为2,如果mCachedViews容量已满,会根据FIFO的规则移除旧ViewHolder

    三级缓存:mViewCacheExtension(开放给用户的自定义缓存)

    四级缓存:mRecyclerPool(ViewHolder缓存池)

    用SparseArray存ViewType和ArrayList<ViewHolder>,每个ViewType最多缓存5个ViewHolder。RecyclerPool只保存ViewType类型,不保存数据。

    复用
    我们从RecyclerView的滑动事件onTouchEvent和布局onLayout入手 RecyclerView.png

    核心代码tryGetViewHolderForPositionByDeadline分析

    ViewHolder tryGetViewHolderForPositionByDeadline(int position,
            boolean dryRun, long deadlineNs) {
        
        //...
        if (mState.isPreLayout()) {
            // 0)从 mChangedScrap 里面去获取 ViewHolder,动画相关
            holder = getChangedScrapViewForPosition(position);
        }
       
        if (holder == null) {
          // 1) mAttachedScrap、mCachedViews
            holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        }
        if (holder == null) {
    
            if (mAdapter.hasStableIds()) {
              // 2) mAttachedScrap、mCachedViews (通过viewType,itemId获取)
                holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                        type, dryRun);
               
            }
            if (holder == null && mViewCacheExtension != null) {
               // 3)mViewCacheExtension 自定义缓存 
                final View view = mViewCacheExtension
                        .getViewForPositionAndType(this, position, type);
                if (view != null) {
                    holder = getChildViewHolder(view);
                }
            }
            if (holder == null) { 
              // 4) 缓冲池里获取
                holder = getRecycledViewPool().getRecycledView(type);
               
            }
            if (holder == null) {
             
                holder = mAdapter.createViewHolder(RecyclerView.this, type);
       
            }
        }
    
            bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
      
        return holder;
    }
    

    可看到:

    tryGetViewHolderForPositionByDeadline(从ViewHolder拿itemView)分情况获取

    1. getChangedScrapViewForPosition ----- mChangedScrap 动画相关

    2. getScrapOrHiddenOrCachedHolderForPosition ----- mAttachedScrap、mCachedViews

    3. getScrapOrCachedViewForId ----- mAttachedScrap、mCachedViews (通过viewType,itemId获取)

    4. mViewCacheExtension.getViewForPositionAndType ----- mViewCacheExtension 自定义缓存

    5. getRecycledViewPool().getRecycledView 从缓冲池里获取

    当没有缓存的时候?—>mAdapter.createViewHolder—>onCreateViewHolder就是给我们重写的。

    创建ViewHolder后绑定 —> tryBindViewHolderByDeadline—>mAdapter.bindViewHolder—>onBindViewHolder

    缓存

    什么时候缓存呢?

    所谓缓存,就是RecyclerView怎么往四级缓存里添加数据的。

    当我们调用notifyXXX的时候,会触发requestLayout方法,就会重新布局:

    //调用链(源码太长,不一一贴出来,只贴核心代码,对照着源码看)
          RecyclerView.onLayout()
    —>dispatchLayout()
    —>dispatchLayoutStep2()
    —>mLayout.onLayoutChildren
    —>LinearLayoutManager.onLayoutChildren(recycler)
    —>detachAndScrapAttachedViews(recycler)
    —>RecyclerView.detachAndScrapAttachedViews
    —>scrapOrRecycleView(recycler, i, v);  
    

    跟踪到scrapOrRecycleView

    private void scrapOrRecycleView(Recycler recycler, int index, View view) {
        final ViewHolder viewHolder = getChildViewHolderInt(view);
        if (viewHolder.shouldIgnore()) {
            if (DEBUG) {
                Log.d(TAG, "ignoring view " + viewHolder);
            }
            return;
        }
        if (viewHolder.isInvalid() && !viewHolder.isRemoved()
                && !mRecyclerView.mAdapter.hasStableIds()) {
            removeViewAt(index);
            //——>分析1 (缓存到 mCacheViews 和 RecyclerViewPool)
            recycler.recycleViewHolderInternal(viewHolder);
        } else {
            detachViewAt(index);
            //——>分析2 缓存到mAttachedScrap和mChangedScrap
            recycler.scrapView(view);
            mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
        }
    }
    

    ——>分析1 缓存到 mCacheViewsRecyclerViewPool

    void recycleViewHolderInternal(ViewHolder holder) {
      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) {
                //如果mCachedViews容量已满,会根据FIFO的规则取出旧ViewHolder,放到RecycledViewPool,然后再移除。
               recycleCachedViewAt(0);
               cachedViewSize--;
           }
            mCachedViews.add(targetCacheIndex, holder);
            cached = true;
        }
        
         if (!cached) {
             //ViewHolder有变化的放RecycledViewPool (RecyclerPool只保存ViewType类型,不保存数据)
             addViewHolderToRecycledViewPool(holder, true);
             recycled = true;
         }
      }
    }
    
    void recycleCachedViewAt(int cachedViewIndex) {
        ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
        addViewHolderToRecycledViewPool(viewHolder, true);
        mCachedViews.remove(cachedViewIndex);
    }
    

    ——>分析2 缓存到mAttachedScrapmChangedScrap

    void scrapView(View view) {
      final ViewHolder holder = getChildViewHolderInt(view);
      if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
          || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
    
        holder.setScrapContainer(this, false);
        //标记为移除或失效||没有改变||item无动画或动画不复用
        //放mAttachedScrap
        mAttachedScrap.add(holder);
      } else {
        if (mChangedScrap == null) {
          mChangedScrap = new ArrayList<ViewHolder>();
        }
        holder.setScrapContainer(this, true);
        //其它情况放也就是ViewHolder有改变
        //放mChangedScrap
        mChangedScrap.add(holder);
      }
    }
    

    相关文章

      网友评论

        本文标题:Recyclerview缓存复用机制

        本文链接:https://www.haomeiwen.com/subject/zohqcltx.html