RecycleView的复用回收机制1
以前的我看源码,属于那种沉浸在代码细节里面不可自拔的人,一旦在细节里迷失了,就放弃了,经过大半年的工作和学习,逐渐找到了自己看源码的一些思路。
一、看代码的一些思路?
- 带着问题,自顶向下,先从最简单的流程分析。比如看属性动画源码的时候,我先从属性动画类的构造函数开始,找到主要的初始化代码,虽然一开始不知道哪个是主要的,不过可以回来看,然后在查看他的setFloatValues()方法,之后在查看他的start()方法这就是最简单的思路了。
- 如果找到某个方法断了,可以尝试搜索这个方法调用的地方,如果调用的地方特别多,就不要用了,比如我看了start的方法,发现属性动画start方法只绘制动画的一帧,就很奇怪,然后我想到了这个方法里面的注册的回调方法,最终找到了doFrame方法,顺腾摸瓜,学习了android的Choreographer类的运作过程,收获颇多。
- 实在不行就百度,但是我尽量不百度,因为要培养自己的能力,百度了很不甘心,但是自己是个新手,就先百度吧,然后根据博主讲的内容,自己按照源码分析一下,这样更快,也不一定非要看完博客文章的所有内容。
- 看源码切记有的放矢,先了解主干,顺着主干去分析,期间的话可以通过画图的方式去理解,虽然我很少画图,以后慢慢培养自己的能力吧。
- 也可以使用调试,demo运行的方式去猜测里面怎么实现的,实在不行就放一放,如果不是很紧急。
二、提出问题,探索问题
1.问题
- RecycleView 怎么回收view的,也就是什么情况下View会被回收。
- RecycleView怎么复用View的(这里我的第二篇文章再分析吧)。
- RecycleView如何实现滑动的效果的。
现在我们从源头开始探索,如果让我设计一个显示List列表的一个控件,我会怎么实现呢?
- 首先是继承Linerlayout,然后有多少数据,往里面加入多少控件呗。
- 目前这个控件不能滑动,为了解决滑动问题,我根据分发机制,让这个View进行消费手势,根据她滑动的距离,我用Scroller实现就好了,之后抬手的时候加上惯性滑动。
- 但是如果数据很多,会造成内存的大量使用而且是有可能会出现oom的情况,再深入思考一下,我们可以只让这个控件显示用户需要看到的部分,其他部分就不需要显示了,对资源合理的应用。
- 那控件是在滑动的,不可能只把控件的位置写死,会出现一些控件只显示一部分的情况,那应该怎么办呢,就不能把控件位置写死,内部控件位置,根据手势滑动情况,进行变化,但是显示只显示需要的部分。
- 我们可以复用之前已经创建的View,也就是我们不一定要创建显示只用来显示的View,也可以创建一个缓存池,缓存一定量的View,到时候直接从可以优先从里面拿,这样就避免了View的频繁创建。
2.从具体的模型入手
上图是我用RecycleView显示的列表,带颜色区域是item,item的属性为margin_top :10dp,在这种情况下,假设向上滑动了一段距离,RecycleView该如何显示列表呢。
先简单分析一下:
1假设第一个item的底部最终会离开屏幕,那么就需要对这个item进行回收。
2.假设需要显示第七个item,那就需要调用addView(-1)这个方法,在底部添加View,这个添加的View优先从缓存中拿,如果缓存中不满足,那么就新建ViewHoler,然后添加之后,再检测View需要不需要回收。
3.对滑动的距离进行消费,也就是让item滑动到相应的位置。
三、源码分析
这里我们就分析主要的代码就好了,至于之前的onMeasure方法和onLayout的方法,先不进行分析。
假设我们使用的是LinearLayoutManager,做竖直方向滑动。
3.1 scrollBy方法
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
//RecyclerView.State保存了一些RecycleView的一些状态信息,在这个流程里需要用到这些信息
//RecyclerView.Recycler是回收逻辑类,回收的具体逻辑在这类里面实现
//delta是滑动距离,往上滑动delta为正值,反之负值。
if (getChildCount() == 0 || delta == 0) {
return 0;
}
//如果mLayoutState为空,就新建,说明mLayoutState是单例的。
ensureLayoutState();
//设置回收状态为true
mLayoutState.mRecycle = true;
//向上滑动为LAYOUT_END,因为是底部View不断上滑,向下滑动为LAYOUT_START,因为是顶部View不断上滑。
final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
//absDelta为滑动长度
final int absDelta = Math.abs(delta);
//初始化当前mLayoutState的内容,前往3.2查看
updateLayoutState(layoutDirection, absDelta, true, state);
//消费的距离,前往3.3查看
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
if (consumed < 0) {
if (DEBUG) {
Log.d(TAG, "Don't have any more elements to scroll");
}
return 0;
}
//给滑动距离赋值,absDelta和consumed取绝对值小的
final int scrolled = absDelta > consumed ? layoutDirection * consumed : delta;
//这里最终会遍历每个item,调用offsetTopAndBottom进行View的位置设置
mOrientationHelper.offsetChildren(-scrolled);
if (DEBUG) {
Log.d(TAG, "scroll req: " + delta + " scrolled: " + scrolled);
}
mLayoutState.mLastScrollDelta = scrolled;
return scrolled;
}
3.2 updateLayoutState 方法
// A code block
private void updateLayoutState(int layoutDirection, int requiredSpace,
boolean canUseExistingSpace, RecyclerView.State state) {
// If parent provides a hint, don't measure unlimited.
mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mLayoutDirection = layoutDirection;
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
calculateExtraLayoutSpace(state, mReusableIntPair);
//一般情况mReusableIntPair数组中的两个元素都为0
int extraForStart = Math.max(0, mReusableIntPair[0]);
int extraForEnd = Math.max(0, mReusableIntPair[1]);
boolean layoutToEnd = layoutDirection == LayoutState.LAYOUT_END;
mLayoutState.mExtraFillSpace = layoutToEnd ? extraForEnd : extraForStart;
mLayoutState.mNoRecycleSpace = layoutToEnd ? extraForStart : extraForEnd;
int scrollingOffset;
if (layoutToEnd) {
//主要查看这里的流程
mLayoutState.mExtraFillSpace += mOrientationHelper.getEndPadding();
// get the first child in the direction we are going
//获取列表中,屏幕底部的View
final View child = getChildClosestToEnd();
// the direction in which we are traversing children
mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
: LayoutState.ITEM_DIRECTION_TAIL;
mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
//这里注意mOffset为该View的bottom+marginBottom
mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
// calculate how much we can scroll without adding new children (independent of layout)
//这里注意scrollingOffset 为的mOffset -(RecycleView的高度-RecycleView的paddingBottom)
//也就是该View距离超出屏幕显示范围的距离
scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
- mOrientationHelper.getEndAfterPadding();
} else {
final View child = getChildClosestToStart();
mLayoutState.mExtraFillSpace += mOrientationHelper.getStartAfterPadding();
mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
: LayoutState.ITEM_DIRECTION_HEAD;
mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child);
scrollingOffset = -mOrientationHelper.getDecoratedStart(child)
+ mOrientationHelper.getStartAfterPadding();
}
mLayoutState.mAvailable = requiredSpace;
if (canUseExistingSpace) {
//这里注意mAvailable 为滑动距离-scrollingOffset
mLayoutState.mAvailable -= scrollingOffset;
}
mLayoutState.mScrollingOffset = scrollingOffset;
}
上面分析了三个距离,需要注意scrollingOffset 为底部View的超出屏幕的距离,mAvailable为滑动距离-超出距离,也就是需要填充的View的距离,mOffset为底部View的bottom+marginBottom的距离。
我们用图表示一下:
3.3 updateLayoutState 方法
//fill 方法是主要方法,会发起回收的逻辑和添加item view的逻辑
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;
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
//如果滑动距离小于mScrollingOffset,那么这里会先进行一次回收的判断
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
//判断回收View的方法,请前往3.4查看
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
//用于添加View方法,结果反馈的类。
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
//如果需要填充的距离大于0并且没有到达列表的极限数量
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
//初始化该类状态
layoutChunkResult.resetInternal();
if (RecyclerView.VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
//添加View方法,前往3.5查看
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (RecyclerView.VERBOSE_TRACING) {
TraceCompat.endSection();
}
if (layoutChunkResult.mFinished) {
break;
}
//mConsumed 一般是添加进入的View的高度
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
/**
* Consume the available space if:
* * layoutChunk did not request to be ignored
* * OR we are laying out scrap children
* * OR we are not doing pre-layout
*/
if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
|| !state.isPreLayout()) {
//可填充距离-添加View的高度
layoutState.mAvailable -= layoutChunkResult.mConsumed;
// we keep a separate remaining space because mAvailable is important for recycling
remainingSpace -= layoutChunkResult.mConsumed;
}
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
//mScrollingOffset+添加View高度
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
//这里一般mScrollingOffset赋值为滑动距离
layoutState.mScrollingOffset += layoutState.mAvailable;
}
//这里进行回收操作
recycleByLayoutState(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
if (DEBUG) {
validateChildOrder();
}
//开始的mAvailable-最终的mAvailable
return start - layoutState.mAvailable;
}
这里主要做了三个操作,第一个操作时,如果滑动距离小于底部View的mScrollingOffset,那么就做回收操作,如果大于且item的数量没有到达极限,就进入while循环,不停的填充View,并且进行回收操作。
3.4 recycleByLayoutState方法
recycleByLayoutState:
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
if (!layoutState.mRecycle || layoutState.mInfinite) {
return;
}
int scrollingOffset = layoutState.mScrollingOffset;
int noRecycleSpace = layoutState.mNoRecycleSpace;
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
} else {
//向上滑动调用这里,从顶部开始回收
recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
}
}
recycleViewsFromStart:
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,
int noRecycleSpace) {
if (scrollingOffset < 0) {
if (DEBUG) {
Log.d(TAG, "Called recycle from start with a negative value. This might happen"
+ " during layout changes but may be sign of a bug");
}
return;
}
// ignore padding, ViewGroup may not clip children.
//limit 一般赋值为scrollingOffset
//noRecycleSpace一般是0
final int limit = scrollingOffset - noRecycleSpace;
final int childCount = getChildCount();
if (mShouldReverseLayout) {
for (int i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedEnd(child) > limit
|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
// stop here
recycleChildren(recycler, childCount - 1, i);
return;
}
}
} else {
//一般会执行这里的方法,如果顶部View的Bottom小于limit(滑动距离),需要回收了。
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedEnd(child) > limit
|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
// stop here
//回收,如果i==0则不回收,其他情况进行回收。
recycleChildren(recycler, 0, i);
return;
}
}
}
}
recycleChildren方法会遍历0到i下标的view,调用removeAndRecycleViewAt(i)
removeAndRecycleViewAt:
public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
final View view = getChildAt(index);
//将view从RecycleView中移除
removeViewAt(index);
//调用回收方法
recycler.recycleView(view);
}
Recycler ::recycleView
public void recycleView(@NonNull View view) {
//获取对应ViewHolder
ViewHolder holder = getChildViewHolderInt(view);
if (holder.isTmpDetached()) {
removeDetachedView(view, false);
}
if (holder.isScrap()) {
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
//这里是主要的回收流程
recycleViewHolderInternal(holder);
if (mItemAnimator != null && !holder.isRecyclable()) {
mItemAnimator.endAnimation(holder);
}
}
recycleViewHolderInternal:
//这个方法针对于mCachedViews和mRecyclerPool进行回收
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)) {
// Retire oldest cached view
int cachedViewSize = mCachedViews.size();
//如果cachedViewSize大于2,那么就移除首部的元素
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}
int targetCacheIndex = cachedViewSize;
if (ALLOW_THREAD_GAP_WORK
&& cachedViewSize > 0
//这里根据lastPrefetchIncludedPosition方法进行判断
//mPrefetchRegistry中存在一个保存position的缓存数组
//根据position的缓存次数,来选择再mCachedViews中的插入位置
//类似于LRU
&& !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;
}
//如果没有加入mCachedViews,就加入到缓存池中
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
if (DEBUG) {
Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
+ "re-visit here. We are still removing it from animation lists"
+ exceptionLabel());
}
}
mViewInfoStore.removeViewHolder(holder);
if (!cached && !recycled && transientStatePreventsRecycling) {
holder.mOwnerRecyclerView = null;
}
}
mRecyclerPool中有一个map,map中的key为ViewHolder的type,value为ScrapData,ScrapData中有一个List列表,保存对应的缓存,最大长度为5。
3.4 layoutChunk方法
layoutChunk:
//这个方法是填充View的方法
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//获取View,从缓存中获取或者新建,下一节重点讲这个方法。
View view = layoutState.next(recycler);
if (view == null) {
if (DEBUG && layoutState.mScrapList == null) {
throw new RuntimeException("received null view when unexpected");
}
// if we are laying out views in scrap, this may return null which means there is
// no more items to layout.
result.mFinished = true;
return;
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
//如果为上滑
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
//底部会调用RecycleView的addView的方法,在底部添加一个View
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
//根据View的layoutParams和RecycleView的一些属性,调用View的measure方法
measureChildWithMargins(view, 0, 0);
//mConsumed 为View的测量高度加上 上下margin的高度,加上view的layoutparams中的 mDecorInsets的bottom和top的值
//mDecorInsets和RecycleView的中的ItemDecoration的getItemOffsets方法相关。
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
int left, top, right, bottom;
if (mOrientation == VERTICAL) {
if (isLayoutRTL()) {
right = getWidth() - getPaddingRight();
left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
} else {
left = getPaddingLeft();
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = layoutState.mOffset - result.mConsumed;
} else {
top = layoutState.mOffset;
bottom = layoutState.mOffset + result.mConsumed;
}
} else {
top = getPaddingTop();
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
right = layoutState.mOffset;
left = layoutState.mOffset - result.mConsumed;
} else {
left = layoutState.mOffset;
right = layoutState.mOffset + result.mConsumed;
}
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
//调用View的layout方法
layoutDecoratedWithMargins(view, left, top, right, bottom);
if (DEBUG) {
Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
+ (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
+ (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin));
}
// Consume the available space if the view is not removed OR changed
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
result.mFocusable = view.hasFocusable();
}
这个方法主要工作就是填充一个View,然后调用View的measure方法,之后给mConsumed赋值,再调用View的layout方法。
3.4 mAttachedScrap缓存
上面讲到回收的方法,只是针对于mCachedViews和mRecyclerPool的缓存,而mAttachedScrap没有涉及到。下面来讲一下这个缓存
在recycleView的绘制方法中,会调用到LayoutManager的onLayoutChildren方法。
onLayoutChildren:
...
//遍历item,加入到mAttachedScrap中
detachAndScrapAttachedViews(recycler);
...//之后fill填充会用到mAttachedScrap中的缓存
detachAndScrapAttachedViews:
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);
}
}
scrapOrRecycleView:
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 (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
//分析该方法
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
Recycler :: scrapView:
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);
//将ViewHolder加入
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
总结
经过了上面的分析之后,大概对RecycleView的滑动和回收过程有了一个了解,如果要是让我来做一个RecycleView的缓存,我可能只有一个缓存池,就没了,RecycleView缓存的高明之处就是对缓存的粒度进行一个切分,针对于不同的情境,将回收的内容放入对应的缓存当中,关于缓存的分析,下篇文章会进行深入探索。