一:前言
RecyclerView是我们开发中很常用的一个控件,但是阿简没有仔细的去了解以下其实现原理,最近正好在搞一个需求,看了下源码,一起学习下~
二:关于RecyclerView
在我们使用RecyclerView的时候不免使用Adapter去处理数据,处理完了数据之后再recyclerView.setAdapter(_)去显示相关item,可以看出,UI和数据是分开的,通过对recyclerView源码的学习,recyclerView在设计的时候也遵循这个规则
三:RecyclerView.class中Recycler内部类说明
Recycler内部类:
mAttachedScrap:缓存当前屏幕可见的ViewHolder
mChangedScrap:ViewHolder更新的时候会保存在这个ArrayList里面
mCachedViews:缓存滑动时即将与RecyclerView分离的ViewHolder,按子View的position或id缓存,默认最多存放2个
mViewCacheExtension:用户自定义缓存
mRecyclerPool:缓存池(后面说明)
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private final List<ViewHolder>
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
static final int DEFAULT_CACHE_SIZE = 2;
四:什么时候会使用到缓存机制?入口在哪?
4.1:
RecyclerView是一个自定义viewGroup,所以我们从onTouchEvent(MotionEvent event),当event等于MotionEvent.ACTION_MOVE就是滑动的时候开始分析:
case MotionEvent.ACTION_MOVE: {
...省略
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
}
} break;
我们来看一下从scrollByInternal()一直点进去之后的调用链
OnTouch --> scrollByInternal --> scrollStep --> mLayout.scrollVerticallyBy --> scrollBy --> fill --> layoutChunk --> layoutState.next --> getViewForPosition --> tryGetViewHolderForPositionByDeadline
4.2:
当RecyclerView中item发生变化的时候,就是Adapter调用了notifydatasetchanged这些一系列的方法时候,会触发以下调用链:
notifyDataSetChanged-->mObservable.notifyChanged --> (RecyclerViewDataObserver)mObservers.get(i).onChanged --> requestLayout
可以看到最后调用到了requestLayout,所以会重新执行RecyclerView的onMeasure-onLayout-onDraw方法,然后就会开始调用缓存操作:
onMeasure()/onLayout() -->dispatchLayoutStep1()/dispatchLayoutStep2()--> mLayout.onLayoutChildren -->onLayoutChildren() -->fill()->layoutChunk() -->next() -->getViewForPosition()-->tryGetViewHolderForPositionByDeadline
五:缓存机制分析
由第四步可知最后都是调用到了fill()->layoutChunk() -->next() -->getViewForPosition()-->tryGetViewHolderForPositionByDeadline
那我们来看tryGetViewHolderForPositionByDeadline做了什么(这个方法也是我们常说的四级缓存的实现)
//根据传入的position获取ViewHolder
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...省略
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
//预布局 属于特殊情况 从mChangedScrap中获取ViewHolder
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
if (holder == null) {
//1、尝试从mAttachedScrap中获取ViewHolder,此时获取的是屏幕中可见范围中的ViewHolder
//2、mAttachedScrap缓存中没有的话,继续从mCachedViews尝试获取ViewHolder
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
...省略
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
...省略
final int type = mAdapter.getItemViewType(offsetPosition);
//如果Adapter中声明了Id,尝试从id中获取,这里不属于缓存
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
}
if (holder == null && mViewCacheExtension != null) {
3、从自定义缓存mViewCacheExtension中尝试获取ViewHolder,该缓存需要开发者实现
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
}
}
if (holder == null) { // fallback to pool
//4、从缓存池mRecyclerPool中尝试获取ViewHolder
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
//如果获取成功,会重置ViewHolder状态,所以需要重新执行Adapter#onBindViewHolder绑定数据
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
if (holder == null) {
...省略
//5、若以上缓存中都没有找到对应的ViewHolder,最终会调用Adapter中的onCreateViewHolder创建一个
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
}
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
//6、如果需要绑定数据,会调用Adapter#onBindViewHolder来绑定数据
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
...省略
return holder;
}
可见以下流程图:
六:RecyclerView.class中RecycledViewPool内部类说明
通过上一步的分析可知,前面三级缓存是不用重新绑定viewHolder的,只有从RecycledViewPool中获取到的RecycledViewPool需要重新绑定数。
通过下面一种场景可以帮助理解:如一个APP的主界面ViewPager有多个fragment,其中某两个fragment都会用到RecyclerView,而且其中的一些item还是相同的,那么此时我们为了节约内存,就可以使用
RecycledViewPool让这两个RecyclerView共享同一个四级缓存pool
RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool();
MyAdapter myAdapter = new MyAdapter(datas);
RecyclerView.ViewHolder viewHolder = myAdapter.createViewHolder(recyclerView, 0);
pool.putRecycledView(viewHolder);
recyclerView.setRecycledViewPool(pool);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(myAdapter);