RecyclerView的源码分析
RecyclerView 源码分析(转载)
欢迎关注我的 简书 ,关注我的专题 Android Class 我会长期坚持为大家收录简书上高质量的 Android 相关博文。
RecyclerView 其功能的高度解耦化,规范 ViewHolder 的写法,以及对动画友好支持,都是它与传统控件 ListView 的区别。
而无论 ListView 还是 RecyclerView,本质上都是在有限的屏幕之上,展示大量的内容。所以复用的逻辑,就成了它们最最重要的核心原理,
RecyclerView 的几大模块
- ItemDecoration--为 RecyclerView 添加分割线
- ItemAnimator--控制 RecyclerView 中 item 的动画
- LayoutManager--负责 RecyclerView 中,控制 item 的布局方向
-
RecyclerView.Adapter--为 RecyclerView 承载数据
- Recycler--控制 RecyclerView 缓存的核心类
刚刚我们提到 RecyclerView 的高度解耦,就是通过以上对象各司其职,来实现 RecyclerView 的基本功能。 本文的重点就是缓存原理分析,不过在此之前,我们还是简单地分别介绍下以上中各个模块的源码。
-->ItemDecoration
void markItemDecorInsetsDirty() {
final int childCount = mChildHelper.getUnfilteredChildCount();
for (int i = 0; i < childCount; i++) {
final View child = mChildHelper.getUnfilteredChildAt(i);
((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
}
mRecycler.markItemDecorInsetsDirty();
}
这个方法首先遍历了 RecyclerView 和 LayoutManager 的所有子 View,将其子 View 的 LayoutParams 中的 mInsetsDirty
属性置为 true。接着调用了 mRecycler.markItemDecorInsetsDirty()
,Recycler 是 RecyclerView 的一个内部类,就是它管理着 RecyclerView 的复用逻辑。这个我们一会再细谈。
@Override
public void onDraw(Canvas c) {
super.onDraw(c);
final int count = mItemDecorations.size();
for (int i = 0; i < count; i++) {
mItemDecorations.get(i).onDraw(c, this, mState);
}
}
在 draw
方法中:
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
// 当 mInsetsDirty 为 false,说明 mDecorInsets 缓存可用
return lp.mDecorInsets;
}
if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
// changed/invalid items should not be updated until they are rebound.
return lp.mDecorInsets;
}
final Rect insets = lp.mDecorInsets;
insets.set(0, 0, 0, 0);
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
}
来解释一下这段代码,首先 getItemDecorInsetsForChild
方法是在 RecyclerView 进行 measureChild 时调用的。目的就是为了取出 RecyclerView 的 ChildView 中的分割线属性 --- 在 LayoutParams 中缓存的 mDecorInsets 。而 mDecorInsets 就是 Rect 对象, 其保存记录的是所有添加分割线需要的空间累加的总和,由分割线的 getItemOffsets
方法影响。
最后在 measureChild 方法里,将分割线 ItemDecoration 的尺寸加入到 itemView 的 padding 中。
但是大家都知道缓存并不是总是可用的,mInsetsDirty 这个 boolean 字段来记录它的时效性,当 mInsetsDirty 为 false 时,说明缓存可用,直接取出可以,当 mInsetsDirty 为 true 时,说明缓存的分割线属性就需要重新计算了。
到此,关于 RecyclerView 添加分割线 ItemDecoration 的源码分析,也就基本结束了。
-->ItemAnimator
如果有人问我,在什么情况下你绝对会选择 RecyclerView,而不是 ListView?如果需求对 Item 的动画有一定要求,这绝对是我选择 RecyclerView 的重要原因之一。ListView 如果要做 Item 的增删动画,那可要费很大劲儿,而 RecyclerView 自身对动画就有很好的支持。
public @NonNull ItemHolderInfo recordPreLayoutInformation(@NonNull State state,
@NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags,
@NonNull List
这两个函数看命名也能猜一个大概,其目的就是为了记录在 RecyclerView 布局之前/之后,必要的一些 layout 信息保存在 ItemHolderInfo 中,ItemHolderInfo 这个类就是用来记录当前 ItemView 的位置信息
recordPreLayoutInformation
来记录 layout 之前的状态信息,这个方法在 dispatchLayoutStep1
之中调用。
dispatchLayoutStep1
这个方法做了什么呢,来看看注释就一目了然:
/**
* The final step of the layout where we save the information about views for animations,
* trigger animations and do any necessary cleanup.
*/
layout 的最后一个步骤,保存 view 动画的信息,执行动画,状态清理。当然也有 dispatchLayoutStep2
方法,他们三个方法依次在 onLayout
方法的 dispatchLayout
方法调用。
dispatchLayout
方法目的是 layout RecyclerView 的 childview,并且记录动画执行的过程、变更。
现在知道了这两个 API 的作用就是在 layout 前后记录 itemview 动画的状态,保存在 ItemHolderInfo
中,我们继续寻找执行动画的 API。
- animateDisappearance
当 ViewHolder 从 RecyclerView 的 layout 中移除时,调用 - animateAppearance
当 ViewHolder 添加进 RecyclerView 时,调用 - animatePersistence
当 ViewHolder 已经添加进 layout 还未移除时,调用 - animateChange
当 ViewHolder 已经添加进 layout 还未移除,并且调用了 notifyDataSetChanged 时,调用。
以上四个 api 就是为 RecyclerView 执行动画的。
调用的时机和方式是如何的呢?
// Step 4: Process view info lists and trigger animations
mViewInfoStore.process(mViewInfoProcessCallback);
执行触发方法的位置就在我们刚才提到的 dispatchLayoutStep3
方法中,去根据一些保存的 flag 状态去触发动画。
-->LayoutManager
与其他绑定 adapter 展示数据的控件,比如 ListView、GrideView 相比,RecyclerView 允许自定义规则去放置子 view,这个规则的控制者就是 LayoutManager。一个 RecyclerView 如果想展示内容,就必须设置一个 LayoutManager。
我们按照惯例来看设置 LayoutManager 的入口:
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
&& heightMode == MeasureSpec.EXACTLY;
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
// set dimensions in 2nd step. Pre-layout should happen with old dimensions for
// consistency
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2();
// now we can get the width and height from the children.
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
// if RecyclerView has non-exact width and height and if there is at least one child
// which also has non-exact width & height, we have to re-measure.
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 {
// 省略非自动测量的情况
}
}
来分析一下 onMeasure
方法,mAutoMeasure
字段用来标记是否使用 RecyclerView 的默认规则进行自动测量,否则就必须在 LayoutManager 中自己实现 onMeasure 来进行测量。LinearLayoutManager 的 mAutoMeasure
字段属性就被设置成为了 true。
所以我们重点来看mAutoMeasure
为 true 时,测量的规则。
当 RecyclerView 的 MeasureSpec
为 MeasureSpec.EXACTLY
时,这个时候可以直接确定 RecyclerView 的宽高,所以 return 退出测量。当 RecyclerView 的宽高为不为 EXACTLY 时,首先进行的测量步骤就是 dispatchLayoutStep1
,这个我们在分析动画源码的时候提到过。dispatchLayoutStep1
的作用总结起来就是记录 layout 之前,view 的信息。
接着继续调用了 dispatchLayoutStep2
方法:
@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;
}
跟进 dispatchLayout
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {
// max offset we should set is mFastScroll + available
final int start = layoutState.mAvailable;
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (layoutChunkResult.mFinished) {
break;
}
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
}
return start - layoutState.mAvailable;
}
这就是省略了部分代码的 fill
方法,其中的 while 循环中,通过判断 LaytouState 中保存的状态来不断的通过 LayoutChunk
方法填充 view。所以接着再看 LayoutChunk
方法。
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
一路看到这里,我们的 Recycler 终于出现了...跟进 getViewForPosition
方法吧。
在此之前可以先看看 Recycler 几个重要的成员变量,便于我们更好的认识 RecyclerView 的缓存结构:
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
跟进 tryGetViewHolderForPositionByDeadline
方法,一段一段的阅读这个方法代码。
在文章之初我们就说过,RecyclerView 的缓存单元是 ViewHolder,这个tryGetViewHolderForPositionByDeadline
方法就完整的展示了如何在每个层级的缓存中,取出来 ViewHolder,下面我们一步步的分析一下:
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used
if (!dryRun) {
// we would like to recycle this but need to make sure it is not used by
// animation logic etc.
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {
removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
如果在第一步发现没有缓存的 ViewHolder,则去 mAttachedScrap 中取,当然 mAttachedScrap 中没有怎么办呢,接着去 mHiddenViews 里面去找,如果还没有,继续从 mCachedViews 中取缓存的 ViewHolder ,这一系列操作是缓存层级的第二步。
if (holder == null) { // fallback to pool
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ position + ") fetching from shared pool");
}
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
这段代码是从 RecycledViewPool 中取根据 type 取 ViewHolder,对于 RecycledViewPool 下面多说几句:
if (holder == null) {
long start = getNanoTime();
if (deadlineNs != FOREVER_NS
&& !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
// abort - we have a deadline we can't meet
return null;
}
holder = mAdapter.createViewHolder(RecyclerView.this, type);
if (ALLOW_THREAD_GAP_WORK) {
// only bother finding nested RV if prefetching
RecyclerView innerView = findNestedRecyclerView(holder.itemView);
if (innerView != null) {
holder.mNestedRecyclerView = new WeakReference<>(innerView);
}
}
long end = getNanoTime();
mRecyclerPool.factorInCreateTime(type, end - start);
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
}
}
这个时候没有办法,就只能调用 mAdapter.createViewHolder(RecyclerView.this, type)
,来创建一个 ViewHolder 了。
到此我们就知道一个 ViewHolder 是如何层层从缓存中取出的了。
写在后面:
本文从源码的角度研究了 RecyclerView 的主要模块和功能,但是 RecyclerView 本身是很复杂的,要考虑到非常多的情况,光是各种状态的记录就让人看得很迷,这种复杂度的控件真不是一般程序员可以搞定的。不过我们不必深究每一块细节,将大致的流程梳理在心,也肯定会有收获和启发。未来打算总结下 RecyclerView 可能发生的一些复用问题。有疑问可以在下方交流。
-->RecyclerView 缓存原理
通常认为RecyclerView有四级缓存,RecyclerView的缓存是通过Recycler类来完成的,方法的入口:
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<ViewHolder>();
private ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private final List<ViewHolder> mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mViewCacheMax = DEFAULT_CACHE_SIZE;
private RecycledViewPool mRecyclerPool;//四级缓存
private ViewCacheExtension mViewCacheExtension;
private static final int DEFAULT_CACHE_SIZE = 2;
第一级缓存:
mAttachedScrap: 用于缓存显示在屏幕上的 item 的 ViewHolder
mChangedScrap: 还是屏幕内的ViewHolder,没看懂啥意思,可能是用于缓存局部改变时候的holder
第二级缓存
mCachedViews:划出屏幕外的item,这个list的默认大小是2,
第三级缓存
mViewCacheExtension:自定义缓存,RecyclerView默认是没有实现的,留给开发者自己实现
第四级缓存
mRecyclerPool:mCachedViews的数量达到上限之后,会把ViewHolder存入mRecyclerPool。mRecyclerPool用SparseArray来缓存进入这一级的ViewHolder:
public abstract static class ViewCacheExtension {
@Nullable
public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,int type);
}
注意getViewForPositionAndType返回的是view而不是ViewHolder,然后会通过view的layoutParams拿到ViewHolder。