RecyclerView 众所周知有四级缓存是目前性能最好的ListView控件,官方也是推荐使用这个控件,同时支持LayoutManager 以及 ItemDecoration 自定义元素的摆放以及分线线。接下来我们结合RecyclerView的源码来了解下它的工作机制, 只有了解了工作机制以后我们才更容易方便我们来使用它。
首先我们来一个简单使用
RecyclerView recycler_view = findViewById(R.id.recycler_view);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recycler_view.setLayoutManager(layoutManager);
List stringList = new ArrayList<>();
for (int i = 0; i < 200; i++) {
int count = (int) (Math.random() * 10);
StringBuilder builder = new StringBuilder();
for (int i1 = 0; i1 < count; i1++) {
builder.append("a");
}
stringList.add(builder.toString());
}
RecyclerAdapter testAdapter = new RecyclerAdapter(stringList);
recycler_view.setAdapter(testAdapter);
从上面的代码我可以看到最基本的设置需要上面的一些元素,必须有一个LayoutManager 因为他是控制元素的摆放的,还有需要一个适配器Adapter它是提供数据绑定和创建itemView对象的。
public void setLayoutManager(@Nullable LayoutManager layout) {
if (layout == mLayout) {
return;
}
stopScroll();
// TODO We should do this switch a dispatchLayout pass and animate children. There is a good
// chance that LayoutManagers will re-use views.
if (mLayout != null) {
// end all running animations
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
}
mLayout.removeAndRecycleAllViews(mRecycler);
mLayout.removeAndRecycleScrapInt(mRecycler);
mRecycler.clear();
if (mIsAttached) {
mLayout.dispatchDetachedFromWindow(this, mRecycler);
}
mLayout.setRecyclerView(null);
mLayout = null;
} else {
mRecycler.clear();
}
// this is just a defensive measure for faulty item animators.
mChildHelper.removeAllViewsUnfiltered();
//将LayoutManager赋值给了mLayout
mLayout = layout;
if (layout != null) {
。。。。
mLayout.setRecyclerView(this);
if (mIsAttached) {
mLayout.dispatchAttachedToWindow(this);
}
}
mRecycler.updateViewCacheSize();
requestLayout();
}
注释可以看出mLayout就是LayoutManager,同时做了一些初始化操作。
public void setAdapter(@Nullable Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
processDataSetCompletelyChanged(false);
requestLayout();
}
private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
// .... 移除和取消一些东西
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
//同样的是adapter 给了mAdapter
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver);
adapter.onAttachedToRecyclerView(this);
}
if (mLayout != null) {
mLayout.onAdapterChanged(oldAdapter, mAdapter);
}
mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
mState.mStructureChanged = true;
}
从上面的注释可以最基础的结论就是mAdapter
就是我们的Adapter
, mLayout
就是我们的LayoutManager
。他们都调用了requestLayout
方法,去执行RecyclerView
的测量 布局以及绘制方法。
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.isAutoMeasureEnabled()) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
/**
* This specific call should be considered deprecated and replaced with
* {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could
* break existing third party code but all documentation directs developers to not
* override {@link LayoutManager#onMeasure(int, int)} when
* {@link LayoutManager#isAutoMeasureEnabled()} returns true.
*/
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
final boolean measureSpecModeIsExactly =
widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
if (measureSpecModeIsExactly || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
// 摆放item前的操作
dispatchLayoutStep1();
}
// set dimensions in 2nd step. Pre-layout should happen with old dimensions for
// consistency
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
//完成 控件的摆放, 自定义LayoutManager 的核心方法在这个api中被调用
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
// 如果RecyclView 宽和高 有一个是不确定的 warp_content
if (mLayout.shouldMeasureTwice()) {
mLayout.setMeasureSpecs(
MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
}
} else {
//通常我们自定义LayoutManager 不会设置setAutoMeasureEnabled 所以测量方法会走这个else
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
// 只有adapter 调用 removenotify 这种更新api时 mAdapterUpdateDuringMeasure = true
// custom onMeasure
if (mAdapterUpdateDuringMeasure) {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
onExitLayoutOrScroll();
if (mState.mRunPredictiveAnimations) {
mState.mInPreLayout = true;
} else {
// consume remaining updates to provide a consistent state with the layout pass.
mAdapterHelper.consumeUpdatesInOnePass();
mState.mInPreLayout = false;
}
mAdapterUpdateDuringMeasure = false;
stopInterceptRequestLayout(false);
} else if (mState.mRunPredictiveAnimations) {
// mRunPredictiveAnimations 只有执行了dispatchLayoutStep1 才会赋值 true
// If mAdapterUpdateDuringMeasure is false and mRunPredictiveAnimations is true:
// this means there is already an onMeasure() call performed to handle the pending
// adapter change, two onMeasure() calls can happen if RV is a child of LinearLayout
// with layout_width=MATCH_PARENT. RV cannot call LM.onMeasure() second time
// because getViewForPosition() will crash when LM uses a child to measure.
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight());
return;
}
if (mAdapter != null) {
mState.mItemCount = mAdapter.getItemCount();
} else {
mState.mItemCount = 0;
}
startInterceptRequestLayout();
// 测量 mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
stopInterceptRequestLayout(false);
mState.mInPreLayout = false; // clear
}
}
在测量方法中我们可以看到dispatchLayoutStep1
、 dispatchLayoutStep2
dispatchLayoutStep3
意思很明显步骤
。但是通常我们不会设置setAutoMeasureEnabled(true)
所以上面三个方法并不会执行(LinearLayoutManager会)。
紧接着即使onLayout方法布局
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
//实际执行布局的方法
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
// leave the state in START
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
// leave the state in START
return;
}
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
//如果adapter有更新 或者 大小发生改变都会运行第二步。
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
我们依然可以看到 dispatchLayoutStep1
、 dispatchLayoutStep2
、 dispatchLayoutStep3
三个布局步骤。
dispatchLayoutStep1
: 更新适配器 、决定运行那个动画、保存视图相关信息,如果有必要还会进行一些预测布局。
dispatchLayoutStep2
: 实际布局的执行, 可能会运行多次 。
dispatchLayoutStep3
: 保存动画信息,触发动画并执行清理操作。
根据上面解释可以清楚知道,我们布局的核心就在第二步。在代码中的注释也可以清楚执行这一点。
private void dispatchLayoutStep2() {
startInterceptRequestLayout();
onEnterLayoutOrScroll();
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
//自定义 LayoutManager的核心方法,onLayoutChildren
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
mState.mLayoutStep = State.STEP_ANIMATIONS;
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
}
在上面我们找到了onLayoutChildren 这个自定义LayoutManager的核心方法。 代码看了这么多依然不见四级缓存, 其实RecyclerView有个专门处理类Recycler。
public final class Recycler {
//一级缓存 存放的是当前显示的View
final ArrayList mAttachedScrap = new ArrayList<>();
ArrayList mChangedScrap = null;
//二级缓存 大小是2 存放的是 经常需要使用的View 比如 来回滑动RecyclerView 一小段距离,让其频繁复用最边界的两个Item。此时的view就存放在这个缓存容器
final ArrayList mCachedViews = new ArrayList();
private final List
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;
//第四级缓存 当缓存的内容大小超过2 就会储存在mRecyclerPool 中,它是根据 ItemViewType 去分类存储的
RecycledViewPool mRecyclerPool;
//第三级缓存, 默认留给用户自己实现
private ViewCacheExtension mViewCacheExtension;
//第二级缓存的默认大小
static final int DEFAULT_CACHE_SIZE = 2;
//省略代码。。。。。
}
上面RecyclerView默认的四个缓存容器分别是mAttachedScrap
、 mCachedViews
、mViewCacheExtension
、 mRecyclerPool
。
接下来我们看看 这个四个缓存是怎样被使用的。首先我们从这个mAttachedScrap开始,它是屏幕内的View集合。看看他是什么时候被添加的东西的。
Recycler.java (RecycleView的内部类)
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();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
这里我们完成了mAttachedScrap 集合添加数据。但是我们在这里看不出什么,继续跟踪。
LayoutManager.java(RecycleView的内部类)
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 暂时不知道怎么解释
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
//但是这里调用了 scrapView 方法
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
在scrapOrRecycleView 中我们找到scrapView 方法。接着看这个方法,什么时候被调用。
LayoutManager.java (RecycleView的内部类)
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
搜索这个方法招不到这个方法 在RecycleView.java 中, 说明他可能在LayoutManager的实现类 中被调用。查看LinearLayoutManager搜索在 onLayoutChildren 中使用到了这个方法。
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
return;
}
}
// 省略。。。。。
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
detachAndScrapAttachedViews(recycler);
// 省略。。。。。
}
调用摆放的方法的时候我们就会向 一级缓存mAttachedScrap 中添加holder。
这里由回到了当初的三个步骤方法,dispatchLayoutStep1
、dispatchLayoutStep2
、dispatchLayoutStep3
接下来看看 二级缓存
recycleViewHolderInternal
void recycleViewHolderInternal(ViewHolder holder) {
//noinspection unchecked
final boolean transientStatePreventsRecycling = holder
.doesTransientStatePreventRecycling();
final boolean forceRecycle = mAdapter != null
&& transientStatePreventsRecycling
&& mAdapter.onFailedToRecycleView(holder);
boolean cached = false;
boolean recycled = false;
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) {
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;
}
// 存放在二级缓存中
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
//二级缓存中存不了,就存四级缓存中
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
}
mViewInfoStore.removeViewHolder(holder);
if (!cached && !recycled && transientStatePreventsRecycling) {
holder.mOwnerRecyclerView = null;
}
}
这个方法很明显就是如果能存 二级缓存 mCachedViews
不能存就存addViewHolderToRecycledViewPool
。
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
clearNestedRecyclerViewIfNotNested(holder);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE)) {
holder.setFlags(0, ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE);
ViewCompat.setAccessibilityDelegate(holder.itemView, null);
}
if (dispatchRecycled) {
dispatchViewRecycled(holder);
}
holder.mOwnerRecyclerView = null;
//添加到RecycledViewPool中
getRecycledViewPool().putRecycledView(holder);
}
三级缓存 mViewCacheExtension 是一个自定义缓存
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
// 分别从四个缓存中取数据
if (holder == null && mViewCacheExtension != null) {
//我们只需要实现这个getViewForPositionAndType 完成自己的获取holder 的方式就行
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
if (holder == null) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view which does not have a ViewHolder"
+ exceptionLabel());
} else if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned"
+ " a view that is ignored. You must call stopIgnoring before"
+ " returning this view." + exceptionLabel());
}
}
}
}
//获取item
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
在LinearLayoutManager 中有个next方法,我们每次需要数据就会调用这个方法
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
而我们在自定义LayoutManager时通常都是直接使用recycler.getViewForPosition(mCurrentPosition)
取条目的holder。