RecycleView的缓存机制

185 阅读3分钟

RecycleView是怎么缓存的?

RecycleView中的缓存分四级缓存,每一级缓存策略都根据相应的场景设定

缓存机制

1. 存哪里?
publish final class Recycler{
    //一级缓存:用来缓存还在屏幕内的 ViewHolder
    ArrayList<ViewHolder> mAttachedScrap; // 存储的是当前还在屏幕中的 ViewHolder;按照 id 和 position 来查找ViewHolder
    ArrayList<ViewHolder> mChangedScrap; // 表示数据已经改变的 ViewHolder 列表, 存储 notifyXXX 方法时需要改变的ViewHolder

    /**
     * 二级缓存:用来缓存移除屏幕之外的ViewHolder,可通过setItemCacheSize调整,默认容量为 2
     * 被滑出屏幕的ViewHolder,将会保存到这个数组中
     * 但是在恢复的时候将会有两种情况:
     * 1.比如说一个ViewHolder滑出后被用户马上滑回来,这个时候其实ViewHolder并没有变化,这个时候将会直接复用,不重新绑定数据
     * 2.如果是新的位置需要复用Cache的时候,将会判断是否是同一个ViewType,如果是的话则复用重新绑定数据,如果不是的话,则从mRecyclerPool中获取
     */
    ArrayList<ViewHolder> mCachedViews;
    
    //三级缓存:自定义拓展View缓存,一般情况下用不上
    ViewCacheExtension mViewCacheExtension;
    
    /**
    * 四级缓存:ViewHolder 缓存池,在有限的 mCachedViews 中如果存不下新的 ViewHolder 时,就会把ViewHolder存入RecyclerViewPool中
    * 1.按照 Type 来查找 ViewHolder
    * 2.每个 Type 默认最多缓存 5 个
    * 3.可以多个 RecyclerView 共享 RecycledViewPool
    */
    RecycledViewPool mRecyclerPool;
}
2.怎么存?

举例LinearLayoutManager,在布局子控件的时候,会先暂时缓存目前所有ViewHolder,调用LayoutMananger将每个ViewHolder缓存到相应的级别

/** 调用链 
*       LinearLayoutManager.onLayoutChildren(...)               1.布局子控件
*    -> LayoutManager.detachAndScrapAttachedViews(recycler)     2.LayoutManager缓存ViewHodler
*    -> LayoutManager.scrapOrRecycleView(..., view)             3.判断每个ViewHolder显示在屏幕中
*    -> Recycler.scrapView(view)                                4.保存进一级缓存或二级缓存中
*    or
 *   -> Recycler.recycleViewHolderInternal(viewHolder)
*/

private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    if (viewHolder.isInvalid() && !viewHolder.isRemoved()
            && !mRecyclerView.mAdapter.hasStableIds()) {
        //判断如果是失效并且没有被移除,放进二、四级缓存中
        recycler.recycleViewHolderInternal(viewHolder);
    } else {
        //否则放进一级缓存中
        recycler.scrapView(view); 
    }
}


//放进二、四级缓存中
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)) {
          int cachedViewSize = mCachedViews.size();
          if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
              // 1. 如果mCacheViews 满了,先入栈的的Holder会出栈,保存到缓存池中
              recycleCachedViewAt(0); 
          }
           mCachedViews.add(targetCacheIndex, holder);
           cached = true;
        }
 
        if (!cached) { 
            // 2. 不能放进 mCacheViews 的放 RecyclerViewPool
            addViewHolderToRecycledViewPool(holder, true);
        }
    }   
}


void scrapView(View view) {
    final ViewHolder holder = getChildViewHolderInt(view);
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
        || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
        // 标记为移除或失效的 || 完全没有改变 || item 无动画或动画不复用
        mAttachedScrap.add(holder);
    } else {
        // 相反就得出放入 mChangedScrap 的条件啦
        mChangedScrap.add(holder);
    }
}



复用机制

举例LinearLayoutManager,在布局的时候,会尝试从缓存中遵循顺序去获取ViewHolder

  1. 首先会尝试从一、二级缓存中获取(mAttachedScrap,mChangeViews)
  2. 从三级缓存中获取
  3. 从回收池中根据ViewType获取
  4. 调用onCreateViewHolder创建
ViewHolder tryGetViewHolderForPositionByDeadline(int position, ...) {
    if (mState.isPreLayout()) {
        // 0) 预布局从 mChangedScrap 里面去获取 ViewHolder,动画相关
        holder = getChangedScrapViewForPosition(position);
    }
 
    if (holder == null) {
        // 1) 从mAttachedScrap、CachedViews 中分别尝试获取 ViewHolder
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        //会对ViewHolder进行校验,比如校验ViewType是否一致等,如果不一致则继续往其他级的缓存中获取
        if (!validateViewHolderForOffsetPosition(holder)) {
            ...
            holder = null
        }
    }
 
    if (holder == null) {
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 2) 如果 Adapter 的 hasStableIds 方法返回为 true
        //    优先通过 ViewType 和 ItemId 两个条件从 mAttachedScrap 和 mCachedViews 寻找
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
        }
 
        if (holder == null && mViewCacheExtension != null) {
            // We are NOT sending the offsetPosition because LayoutManager does not know it.
            // 3) 从自定义缓存获取,别问,问就是别用
            View view = mViewCacheExtension
                getViewForPositionAndType(this, position, type);
            holder = getChildViewHolder(view);
        }
    }
 
    if (holder == null) {
        // 4) 从 RecycledViewPool 获取 ViewHolder
        holder = getRecycledViewPool().getRecycledView(type);
    }
 
    if (holder == null) {
        // 缓存全取过了,没有,那只好 create 一个咯
        holder = mAdapter.createViewHolder(RecyclerView.this, type);
    }
 
}