RecyclerView缓存及源码

相关文章RecyclerView使用

1.RecyclerView中的一些重要类型:

1.RecyclerView:
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
	 final Recycler mRecycler = new Recycler();
	 //处理adapter更新
	 AdapterHelper mAdapterHelper;
	 ChildHelper mChildHelper;
	 Adapter mAdapter;
	 LayoutManager mLayout;
}
2.Recycler:

Recycler类主要负责实现ViewHolder的缓存:

public final class Recycler {
	 //只在布局时使用,这个Scrap里面暂存了数据未被修改的viewHolder
     final ArrayList<RecyclerView.ViewHolder> mAttachedScrap = new ArrayList();
     //只在布局时使用,里面ViewHolder数据发生了变化,需要重新绑定Scrap
     ArrayList<RecyclerView.ViewHolder> mChangedScrap = null;
     //这个缓存存放已经detatch掉的viewHolder,但是里面的position和viewtype等数据还在
     //这个缓存默认大小是2,只在正常滑动屏幕时回收View(而不是重布局)时使用
     final ArrayList<RecyclerView.ViewHolder> mCachedViews = new ArrayList();
     //这个缓存里面的viewHolder也是detatch过的,而且里面的数据已经清空,
     //相当与一个刚new出来的viewHolder,它里面的viewHolder是按照viewtype分类存储的
     RecyclerView.RecycledViewPool mRecyclerPool;
	 //这个对象里面也有一个mHiddenViewList存放隐藏的View
	 ChildHelper mChildHelper;
}
3.LayoutManager:

这个layoutmanager就是我们平时调用setLayoutManager传进来的LayoutManager,RecyclerView的measure,layout,draw过程都外包给这个类了

public abstract static class LayoutManager {
	//这个ChildHelper是RecyclerView直接赋值过来的,二者同一个对象
	ChildHelper mChildHelper;
    RecyclerView mRecyclerView;
	
	public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec);
	public void onLayoutChildren(Recycler recycler, State state);
}
4.ViewHolder:
public abstract static class ViewHolder {
	public final View itemView;
	WeakReference<RecyclerView> mNestedRecyclerView;
	
	//ViewHolder有三个标记位,它们可以用来从缓存中寻找ViewHolder缓存
	int mPosition = NO_POSITION;			
	long mItemId = NO_ID;
    int mItemViewType = INVALID_TYPE;
    
	//表示这个ViewHolder已经被绑定到了某个position,这时mPosition,mItemId和mItemViewType都是正确有效的
	static final int FLAG_BOUND = 1 << 0;
	//表示这个ViewHolder对应的数据已经过期,需要重新被adapter绑定,这时mPosition和mItemId是相匹配的
	static final int FLAG_UPDATE = 1 << 1;
	//ViewHolder失效意味着mPosition和ItemId已经不可信,它们不再与mItemViewType匹配,此时需要重新绑定
	static final int FLAG_INVALID = 1 << 2;
	//表示当前ViewHolder指向的数据已经从数据集中被移除,但这个ViewHolder仍然可能正在被使用
	static final int FLAG_REMOVED = 1 << 3;
	//表示这个ViewHolder是从scrap缓存中返回的
	static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5;
	//当View被detached的时候
	static final int FLAG_TMP_DETACHED = 1 << 8;
}
5.ChildHelper
class ChildHelper {
	final Callback mCallback;
	//这个列表用来存储从屏幕上消失(称为remove)的View
	final List<View> mHiddenViews;
	interface Callback {
		 ...
		 void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams);
         void detachViewFromParent(int offset);
		 void onEnteredHiddenState(View child);
         void onLeftHiddenState(View child);
	}
}
6.RecycledViewPool
public static class RecycledViewPool {
	private static final int DEFAULT_MAX_SCRAP = 5;			//默认每个type的viewHolder最多存5个
	//每个ScrapData里有一个ViewHolder列表,这些列表中存放的ViewHolder都有同样viewType
 	static class ScrapData {
          final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
          int mMaxScrap = DEFAULT_MAX_SCRAP;
          long mCreateRunningAverageNs = 0;
          long mBindRunningAverageNs = 0;
    }
    //这里相当于用一个二位数组存储ViewHolder,第一个维度就是viewType
    SparseArray<ScrapData> mScrap = new SparseArray<>();
    
    //从RecycledViewPool取ViewHolder只能依据viewType,而不能依据position,所以这个缓存优先级比较低
	public ViewHolder getRecycledView(int viewType) {
          final ScrapData scrapData = mScrap.get(viewType);
          if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
               final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
               return scrapHeap.remove(scrapHeap.size() - 1);
          }
          return null;
    }
}

2.缓存机制:

1.自定义Adapter的时候要实现两个重要的方法:

public class RecyclerAdapter extends RecyclerView.Adapter {
	//这个方法用来创建一个viewHolder,当所有缓存都没有合适的viewHolder时,就会调用这个方法创建viewHolder;
	public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
		//最后这个view会被从viewHolder中取出来通过addView添加到RecyclerView
		//所以viewHolder和view是一对一的关系
		View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
        ItemViewHolder holder = new ItemViewHolder(view);
        return holder;
	}
	//viewHolder和数据不是一对一的关系,一个View可能会显示多种数据,这个方法就是用来更换viewHolder中绑定的数据的
	//如果获取的ViewHolder是从RecycledViewPool得到,或是由onCreateViewHolder创建,该方法都会执行
	public void onBindViewHolder(ItemViewHolder holder, int position) {
		String text = dataList.get(position).getName();
        holder.textView.setText(text);
	}
}

缓存机制的最主要的目的,就是让RecyclerView内部尽可能少的调用onCreateViewHolder和onBindViewHolder这两个方法

2.onCreateViewHolder和onBindViewHolder方法在哪里使用:

ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {
 	 ViewHolder holder = null;
 	 // 1.如果在预布局阶段,先试图从mChangedScrap中获取一个缓存
 	 if (mState.isPreLayout()) {
            holder = getChangedScrapViewForPosition(position);
     }
     // 2.这个方法会分别尝试从mAttachedScrap,mChildHelper,mCachedViews中获取ViewHolder
     // 注意这个等级的缓存只需要依据position获取,获取到的type一定是正确的
     if (holder == null) {
     		holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
     }
     if (holder == null) {
     		// 3.如果mAdapter被设置为有固定的id,则会遍历mAttachedScrap和mCachedViews找到id匹配的viewHolder返回
			if (mAdapter.hasStableIds()) {
				  holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);
			}
			// 4.试图从用户自定义的缓存中获取缓存,默认获取不到
			if (holder == null && mViewCacheExtension != null) {
				  final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
				  holder = getChildViewHolder(view);
			}
	 }
	 //5.试图从RecycledViewPool中根据type获取一个恢复出厂设置的缓存对象
     //注意从RecycledViewPool中只能依据type获取了,因为position信息已经被丢弃了
	 if (holder == null) {
			holder = getRecycledViewPool().getRecycledView(type);
	 }
	 //6.获取不到任何缓存,只能利用Adapter重新创建一个viewHolder
     //这个createViewHolder方法就用到我们在自定义Adapter时必须重写的方法
	 if (holder == null) {
			holder = mAdapter.createViewHolder(RecyclerView.this, type);						//1
	 }
	 //7.现在ViewHolder创建好了,该给这个ViewHolder绑定数据和位置信息了
	 boolean bound = false;
	 //如果从RecycledViewPool获取,或新创建了ViewHolder,就会符合条件进入这个分支
	 if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
	 	 //这个方法间接地调用了onBindViewHolder这个方法,也就是我们在Adapter重写的方法
		 bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);		//2
	 }
	 return holder;
}

注释1和注释2处就是我们在Adapter中重写的两个重要方法使用的位置

3.获取缓存的方式:

1.通过position获取:

为什么可以用position去获取一个View?
试想如果一个刚划出屏幕的一个View的位置信息还没有丢弃,现在又要滑进屏幕,那么可以根据这个position的值从缓存直接拿到这个View,这个View无需做任何修改,所以用position获取View的效率是最高的,例如:

	  //从mChangedScrap中获取ViewHolder缓存
      holder = getChangedScrapViewForPosition(position);
      //从mAttachedScrap,mChildHelper,mCachedViews中获取ViewHolder缓存
      holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);

从这些缓存中取ViewHolder的方式就是遍历并对比position,例如:

	//从mAttachedScrap取缓存
	final int scrapCount = mAttachedScrap.size();
	for (int i = 0; i < scrapCount; i++) {
		final ViewHolder holder = mAttachedScrap.get(i);
		holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
		return holder;
	}
	//从mCachedViews取缓存
	final int cacheSize = mCachedViews.size();
	for (int i = 0; i < cacheSize; i++) {
		final ViewHolder holder = mCachedViews.get(i);
		return holder;
	}
2.通过id获取:
if (mAdapter.hasStableIds()) {
	 //这里是从mAttachedScrap和mCachedViews中根据id和type获取ViewHolder
	 holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);
}

先来看一下这个ID是从哪来的:

public abstract static class Adapter<VH extends ViewHolder> {
	 private boolean mHasStableIds = false;			//默认没有固定的ID
	 
	 public void setHasStableIds(boolean hasStableIds) {
          mHasStableIds = hasStableIds;
     }

	 public final void bindViewHolder(@NonNull VH holder, int position) {
	 	  //在ViewHolder绑定之前会给这个ViewHolder赋上一个与position相关的值
		  if (hasStableIds()) {
                holder.mItemId = getItemId(position);
          }
          onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
	 }
	 
	 //想要设置ViewHolder的ID需要重写这个方法
	 public long getItemId(int position) {
		 //根据positon计算得到一个id,可以直接把position作为id
		 return position;
	 }
}

public abstract static class ViewHolder {
	 long mItemId = NO_ID;						  	//Adapter不支持固定ID,ViewHolder的默认id就是NO_ID
	 
	 public final long getItemId() {
            return mItemId;
     }
}

通过id获取ViewHolder的方式同样是遍历并对比id,例如:

ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
	//从mAttachedScrap中获取ViewHolder缓存
	final int count = mAttachedScrap.size();
	for (int i = count - 1; i >= 0; i--) {
		final ViewHolder holder = mAttachedScrap.get(i);
		if (holder.getItemId() == id && type == holder.getItemViewType()){
			holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
			return holder;
		}
	}
	//从mCachedViews中获取ViewHolder缓存
	final int cacheSize = mCachedViews.size();
	for (int i = cacheSize - 1; i >= 0; i--) {
		 final ViewHolder holder = mCachedViews.get(i);
		 if (holder.getItemId() == id && type == holder.getItemViewType()) {
				return holder;
		 }
	}
}

有固定Id的好处是,这些View会优先考虑放在效率较高的缓存中,通过id寻找ViewHolder时也会尽可能从高效的缓存中获取,adapter设置固定id可以提高缓存效率

3.通过viewType获取:
	//从RecycledViewPool中获取
	holder = getRecycledViewPool().getRecycledView(type);
	//创建ViewHolder也需要viewType
	holder = mAdapter.createViewHolder(RecyclerView.this, type);

在RecycledViewPool中会把每种不同type的viewHolder分别放在不同列表中,参考前面RecycledViewPool的结构

4.数据更新:

1.notifyDataSetChanged
public abstract static class Adapter<VH extends ViewHolder> {
	private final AdapterDataObservable mObservable = new AdapterDataObservable();

	public final void notifyDataSetChanged() {
      	mObservable.notifyChanged();
	}
}

这里的mObservable是一个RecyclerViewDataObserver对象

private class RecyclerViewDataObserver extends AdapterDataObserver {
	 public void onChanged() {
           processDataSetCompletelyChanged(true);				//执行数据彻底修改
           if (!mAdapterHelper.hasPendingUpdates()) {
           		 //这里调用了View的requestLayout,这个方法会触发Measure,Layout,draw过程;
                 requestLayout();							
           }
     }
}

看下修改数据做了什么:

	//把mChildHelper中所有的ViewHolder加上无效标签
	final int childCount = mChildHelper.getUnfilteredChildCount();
	for (int i = 0; i < childCount; i++) {
           final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
           if (holder != null && !holder.shouldIgnore()) {
                holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
           }
    }
	//把mCachedViews中所有的ViewHolder加上无效标签
	final int cachedCount = mCachedViews.size();
    for (int i = 0; i < cachedCount; i++) {
    final ViewHolder holder = mCachedViews.get(i);
          if (holder != null) {
               holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
               holder.addChangePayload(null);
          }
    }
	//把mCachedViews中所有的ViewHolder转移到RecycledViewPool,并清除mCachedViews
	final int count = mCachedViews.size();
    for (int i = count - 1; i >= 0; i--) {
          ViewHolder viewHolder = mCachedViews.get(i);
          addViewHolderToRecycledViewPool(viewHolder, true);
    }
    mCachedViews.clear();

可以看到notifyDataSetChanged方法会使所有View失效,并把它们都转移到RecycledViewPool这个效率较低的缓存中,而RecycledViewPool容量有限,可能会导致ViewHolder重新创建,之后每个View都需要重新执行onBindViewHolder,所以notifyDataSetChanged效率较低;

2.notifyItemChanged
   public final void notifyItemChanged(int position) {
         mObservable.notifyItemRangeChanged(position, 1);
   }

onItemRangeChanged:

private class RecyclerViewDataObserver extends AdapterDataObserver {
	  public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
	  		//mAdapterHelper是一个专门用来处理adapter数据更新的工具类
            if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
                triggerUpdateProcessor();		//执行了requestLayout();
            }
      }
}
5.ViewHolder加入缓存的过程:

当view移出屏幕时,会间接触发如下方法:

	//className = LayoutManager
    public void removeAndRecycleViewAt(int index, Recycler recycler) {
        final View view = getChildAt(index);
        recycler.recycleView(view);
    }

然后RecyclerView方法又间接调用者如下方法:

void recycleViewHolderInternal(ViewHolder holder) {
	if (forceRecycle || holder.isRecyclable()) {
         if (mViewCacheMax > 0
                 && !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID	//invalidViewHolder不放进mCachedViews
                 | ViewHolder.FLAG_REMOVED
                 | ViewHolder.FLAG_UPDATE
                 | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
                 
              int cachedViewSize = mCachedViews.size();
              if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                  //这里发现mCachedViews已经装满了
                  //把最老的viewHolder转移到RecycledViewPool
                  //进入RecycledViewPool的viewHolder的position等信息被丢弃
                  //所以从这个缓存拿到的viewHoler需要重新执行onBindViewHolder绑定数据
                   recycleCachedViewAt(0);		
                   cachedViewSize--;
              }
              //如果mCachedViews没满,把移出屏幕的viewHolder放到mCachedViews中去
              //在mCachedViews中的viewHolder保留了position等信息,无需重新绑定数据
              mCachedViews.add(targetCacheIndex, holder);
              cached = true;
          }
          if (!cached) {
               addViewHolderToRecycledViewPool(holder, true);
               recycled = true;
          }
     }
}

把一个缓存添加到RecycledViewPool:

 void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
 	//...
 	getRecycledViewPool().putRecycledView(holder);
 }
 
 public void putRecycledView(ViewHolder scrap) {
       final int viewType = scrap.getItemViewType();
       final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
       if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
           return;
       }
       scrap.resetInternal();
       scrapHeap.add(scrap);
 }
6.scrap的作用:

从ViewHolder的回收过程可以看到,移出屏幕的viewHolder都被直接放进了mCachedViews中,并没有mAttachedScrap和mChangedScrap参与,下面通过recyclerView的重布局研究Scrap缓存的作用。

从上文可以看出,notifyDataSetChanged和notifyItemChanged方法都执行了requestLayout方法,这个方法可以触发View重新Measure,Layout,Draw过程,重布局从RecyclerView的onMeasure看起:

onMeasure

//这里的mLauout就是LayoutManager
 @Override
 protected void onMeasure(int widthSpec, int heightSpec) {
      if (mLayout == null) {
          defaultOnMeasure(widthSpec, heightSpec);
          return;
      }
      if (mLayout.isAutoMeasureEnabled()) {
		  ...
          mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
          if (mState.mLayoutStep == State.STEP_START) {
             dispatchLayoutStep1();						//Step1是预布局阶段
          }
          dispatchLayoutStep2();						//Step2是正式布局阶段
	 }
 }

 private void dispatchLayoutStep2() {
     ...
     mLayout.onLayoutChildren(mRecycler, mState);		//LayoutManager去布局子控件
 }

mLayout是个抽象类,这里以LinerLayoutManager为例:

 @Override
 public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
      detachAndScrapAttachedViews(recycler);			//先把Recycler中的ViewHolder全部detach并放进缓存
      ...		
      fill(recycler, mLayoutState, state, false);		//填充recyclerView
 }

移除ViewHolder的逻辑:

 //className = LinerLayoutManager
 public void detachAndScrapAttachedViews(Recycler recycler) {
       final int childCount = getChildCount();
       //遍历回收RecyclerView中的每个View
       for (int i = childCount - 1; i >= 0; i--) {
           final View v = getChildAt(i);
           scrapOrRecycleView(recycler, i, v);
       }
 }

 private void scrapOrRecycleView(Recycler recycler, int index, View view) {
       final ViewHolder viewHolder = getChildViewHolderInt(view);
       if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !mRecyclerView.mAdapter.hasStableIds()) {
       		//如果View已经失效了,就放入RecycledViewPool中(参考上文回收View的过程)
            removeViewAt(index);
            recycler.recycleViewHolderInternal(viewHolder);
       } else {
       		//如果这个View还能继续用,就简单暂存在mAttachedScrap
       		//后面就可以无需重新绑定直接从mAttachedScrap取出并填充RecyclerView
            detachViewAt(index);
            recycler.scrapView(view);
            mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
       }
 }
    
 //className = Recycler
 void scrapView(View view) {
      final ViewHolder holder = getChildViewHolderInt(view);
      ...
      mAttachedScrap.add(holder);					//把从RecyclerView上detach下来的ViewHolder放进缓存
 }

填充RecyclerView:

	//className = LinerLayoutManager
	int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
            RecyclerView.State state, boolean stopOnFocusable) {
        ...
		layoutChunk(recycler, state, layoutState, layoutChunkResult);
		...
	}
	
	void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
            LayoutState layoutState, LayoutChunkResult result) {
        ...
		View view = layoutState.next(recycler);				//这里会从缓存中取一个View出来
        addView(view);
	}
	//className = LayoutState
	View next(RecyclerView.Recycler recycler) {
		 if (mScrapList != null) {
            return nextViewFromScrapList();					//优先从mAttachedScrap取缓存
         }
         //这个方法会调用上文介绍的tryGetViewHolderForPositionByDeadline方法
         final View view = recycler.getViewForPosition(mCurrentPosition);	
         return view;
    }
总结:

缓存主要包括Scrap,CachedView,RecyclerPool;
Scrap中的缓存主要用于Recycler重新布局时用于临时存放屏幕上所有的ViewHolder;
CachedView中ViewHolder缓存的特点是里面的position信息没有失效,从CachedView中获取的缓存无需重新绑定数据就可以直接显示在屏幕上;
RecyclerPool的是一个可以存放多种ViewType的缓存,每个ViewType对应一个数组,这些数组又组成RecyclerPool,这个缓存中ViewHolder的所有信息都已被擦除,需要重新绑定数据才能显示在屏幕上,所以效率较低;
如果上面的缓存都获取不到ViewHolder,也没有实现自定义缓存的话,就需要通过Adapter的onCreateViewHolder来创建ViewHolder,然后在执行onBindViewHolder后绘制对应的View

获取缓存的方式可以通过position,id,type三者效率依次降低

notifyDataSetChanged方法会触发重布局,ViewHolder会全部擦出数据放进RecycledViewPool中,导致所有数据需要重新绑定,所以效率较低

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值