一、RecyclerView的四级缓存
(1)mChangedScrap、mAttachedScrap:
用于屏幕内ItemView的快速重用。
Scrap缓存,其实就是用来保存被RecyclerView移除但是最近又马上要使用的缓存,比如RecyclerView自带的item的动画等,Scrap有两个,一个是mChangedScrap,一个是mAttachedScrap,这两个的区别就是保存的对象有些不一样。一般调用adapter的notifyItemRangeChanged被移除的holder保存在mChangedScrap中,其他的notify系列方法(不包括notifyDataSetChanged)移除的holder保存在mAttachedScrap中。这两个缓存是在布局阶段使用的,其他时候是空的。布局完成之后,这两个缓存中的holder就会移动到mCacheView或者RecyclerViewPool中。
mChangedScrap一般是在预布局阶段使用的(dispatchLayoutStep1()方法),而mAttachedScrap是在整个布局阶段使用的。
mAttachedScrap 保存依附于 RecyclerView 的 ViewHolder。包含移出屏幕但未从 RecyclerView 移除的 ViewHolder。就是未与RecyclerView分离的ViewHolder。这里的是ViewHolder的数据没有发生变化的
mChangedScrap 保存数据发生改变的 ViewHolder,即调用 notifyDataSetChanged() 等系列方法后需要更新的 ViewHolder。就是与RecyclerView分离的ViewHolder。这里的是ViewHolder的数据发生变化的,需要重新走Adapter的bind的
- 在onLayout布局阶段,在LayoutManager.onLayoutChildren方法中会调用detachAndScrapAttachedViews方法,在这里又会调用scrapOrRecycleView方法,接着判断条件,如果调用了Recycler.scrapView方法,就会将ViewHolder回到到scrap中。
- 在复用阶段,在tryGetViewHolderForPositionByDeadline方法中调用getScrapOrHiddenOrCachedHolderForPosition方法时,会将隐藏的View回收到一级缓存中,即隐藏但是没有被删除的View。即在dryRun为false的时候,获取隐藏的但是没有被remove的View,调用scrapView方法保存在mAttachedScrap或者mChangedScrap(mChangedScrap还有就是在预布局阶段加入)
(2)mCachedViews
默认上限为2个,缓存屏幕外2个ItemView。mCachedViews中保存的是有数据的ViewHolder。
- 在重新布局时,会将ViewHolder添加到mCachedViews中,也是在onLayoutChildren方法中调用detachAndScrapAttachedViews,但是在scrapOrRecycleView方法中判断不同,则会调用Recycler.recycleViewHolderInternal方法回收
- 在复用阶段,如果从一级缓存中取出的ViewHolder是不可用的,则会调用recycleViewHolderInternal方法,将ViewHolder放入到二级缓存中
在这里存放的是detachView之后的视图,它里面存放的是已经remove掉的视图,已经和RV分离的关系的视图,但是它里面的ViewHolder依然保存着之前的信息,比如position、和绑定的数据等等。这一级缓存是有容量限制的,默认是2
(3)mViewCacheExtension
默认不实现的,这个是需要开发者自定义实现的缓存机制
(4)mRecyclerViewPool
缓存池,同一种ViewType的ViewHolder缓存默认上限为5个。保存的只是ViewHolder,但是这个ViewHolder没有数据,当RecyclerViewPool缓存的ViewHolder已经满了,则不会再加入了。
- 在调用recycleViewHolderInternal回收加入到mRecyclerViewPool的时候,会先判断是否可以加入到mCachedViews,如果不满足加入到mCachedViews的条件,则会加入到mRecyclerViewPool中。而添加四级缓存,可能是在回收流程中,也可能是在复用流程中,如果是在回收流程中,则是在调用Recycler.recycleViewHolderInternal方法的时候,调用了addViewHolderToRecycledViewPool方法;如果是在复用流程,则是在tryGetViewHolderForPositionByDeadline方法中有两次,一次就是通过position寻找ViewHolder的时候,如果找到的是不可用的,则会调用Recycler.recycleViewHolderInternal可能又会进入第四级缓存,一次就是通过ID找到ViewHolder,如果还是不可用则会调用recycleCachedViewAt方法,进而调用addViewHolderToRecycledViewPool加入四级缓存
缓存 | 涉及对象 | 作用 | 重新创建视图View(onCreateViewHolder) | 重新绑定数据(onBindViewHolder) |
---|---|---|---|---|
一级缓存 | mAttachedScrap | 缓存屏幕中可见范围的ViewHolder | false | false |
一级缓存 | mChangedScrap | 缓存屏幕中可见范围的但是数据发生改变ViewHolder | false | true |
二级缓存 | mCachedViews | 缓存滑动时即将与RecyclerView分离的ViewHolder,按子View的position或id缓存,默认最多存放2个 | false | false |
三级缓存 | mViewCacheExtension | 开发者自行实现的缓存 | - | - |
四级缓存 | mRecyclerPool | ViewHolder缓存池,本质上是一个SparseArray,其中key是ViewType(int类型),value存放的是 ArrayList< ViewHolder>,默认每个ArrayList中最多存放5个ViewHolder | false | true |
二、RecyclerView的预取功能(预加载功能)
Android系统是通过每16ms刷新一次页面来保证UI的流畅性,现在Android系统中刷新UI会通过CPU产生数据,然后交给GPU渲染的形式来完成,这样当CPU完成数据交给GPU之后,此时还没到16ms,那么这段时间CPU就会处于空闲状态,需要等待下一帧才会进行数据处理,这样就白白浪费了空闲时间,所以在API25开始,RecyclerView内部就实现了预取功能。
RecyclerView的预取功能是依赖于GapWorker,通过每次的MOVE事件中,来判断是否预取下一个可能要显示的Item数据,判断的依据主要就是通过传入的dx和dy得到手指接下来可能要滑动的方向,如果dx或者dy的偏移量会导致下一个item要被显示出来则预取出来,但是并不是说预取下一个可能要显示的item一定都是成功的。
其实每次rv取出要显示的一个item本质上就是取出一个viewholder,根据viewholder上关联的itemview来展示这个item。而预取出viewholder最核心的方法就是
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs)
deadlineNs的取值有两种,一种是为了兼容api25之前的没有预取功能的情况,一种就是实际的deadlineNs数值,超过这个deadline则表示预取失败,预取机制主要目的就是提高RecyclerView整体滑动的流畅性,所以有限制主要是为了保证整体流畅性。
RecyclerView.onTouch
case MotionEvent.ACTION_MOVE: {
final int index = e.findPointerIndex(mScrollPointerId);
if (index < 0) {
Log.e(TAG, "Error processing scroll; pointer index for id "
+ mScrollPointerId + " not found. Did any MotionEvents get skipped?");
return false;
}
final int x = (int) (e.getX(index) + 0.5f);
final int y = (int) (e.getY(index) + 0.5f);
int dx = mLastTouchX - x;
int dy = mLastTouchY - y;
if (dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset, TYPE_TOUCH)) {
dx -= mScrollConsumed[0];
dy -= mScrollConsumed[1];
vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]);
// Updated the nested offsets
mNestedOffsets[0] += mScrollOffset[0];
mNestedOffsets[1] += mScrollOffset[1];
}
if (mScrollState != SCROLL_STATE_DRAGGING) {
boolean startScroll = false;
if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) {
if (dx > 0) {
dx -= mTouchSlop;
} else {
dx += mTouchSlop;
}
startScroll = true;
}
if (canScrollVertically && Math.abs(dy) > mTouchSlop) {
if (dy > 0) {
dy -= mTouchSlop;
} else {
dy += mTouchSlop;
}
startScroll = true;
}
if (startScroll) {
setScrollState(SCROLL_STATE_DRAGGING);
}
}
// 如果是正在滑动
if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
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;
GapWorker.postFromTraversal
void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) {
if (recyclerView.isAttachedToWindow()) {
if (RecyclerView.DEBUG && !mRecyclerViews.contains(recyclerView)) {
throw new IllegalStateException("attempting to post unregistered view!");
}
// 判断第一次拖动时,是否将runnable交给Mainhandler里面,等待UI thread执行完成再执行prefetch
// GapWorker其实就是一个Runnable的实现类
if (mPostTimeNs == 0) {
mPostTimeNs = recyclerView.getNanoTime();
recyclerView.post(this);
}
}
// 后续动作触发去更新最新的dx和dy,prefetch会按照最新的dx和dy计算prefetch的item的position
recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy);
}
GapWorker.run
@Override
public void run() {
try {
TraceCompat.beginSection(RecyclerView.TRACE_PREFETCH_TAG);
// 这里是因为存在嵌套RecyclerView的情况
if (mRecyclerViews.isEmpty()) {
// abort - no work to do
return;
}
// Query most recent vsync so we can predict next one. Note that drawing time not yet
// valid in animation/input callbacks, so query it here to be safe.
final int size = mRecyclerViews.size();
long latestFrameVsyncMs = 0;
// 获取RecyclerView最近一次开始RenderThread的时间
for (int i = 0; i < size; i++) {
RecyclerView view = mRecyclerViews.get(i);
if (view.getWindowVisibility() == View.VISIBLE) {
latestFrameVsyncMs = Math.max(view.getDrawingTime(), latestFrameVsyncMs);
}
}
if (latestFrameVsyncMs == 0) {
// abort - either no views visible, or couldn't get last vsync for estimating next
return;
}
// 计算预加载的最后时间,如果能在截止时间之前完成预加载,那么就可以成功完成ViewHolder的预加载
// 否则就是预加载失败
long nextFrameNs = TimeUnit.MILLISECONDS.toNanos(latestFrameVsyncMs) + mFrameIntervalNs;
prefetch(nextFrameNs);
// TODO: consider rescheduling self, if there's more work to do
} finally {
mPostTimeNs = 0;
TraceCompat.endSection();
}
}
GapWorker.prefetch
void prefetch(long deadlineNs) {
// 计算预加载任务列表
buildTaskList();
// 开始预加载
flushTasksWithDeadline(deadlineNs);
}
GapWorker.buildTaskList
private void buildTaskList() {
// Update PrefetchRegistry in each view
final int viewCount = mRecyclerViews.size();
int totalTaskCount = 0;
for (int i = 0; i < viewCount; i++) {
RecyclerView view = mRecyclerViews.get(i);
if (view.getWindowVisibility() == View.VISIBLE) {
view.mPrefetchRegistry.collectPrefetchPositionsFromView(view, false);
totalTaskCount += view.mPrefetchRegistry.mCount;
}
}
// Populate task list from prefetch data...
mTasks.ensureCapacity(totalTaskCount);
int totalTaskIndex = 0;
for (int i = 0; i < viewCount; i++) {
RecyclerView view = mRecyclerViews.get(i);
if (view.getWindowVisibility() != View.VISIBLE) {
// Invisible view, don't bother prefetching
continue;
}
LayoutPrefetchRegistryImpl prefetchRegistry = view.mPrefetchRegistry;
final int viewVelocity = Math.abs(prefetchRegistry.mPrefetchDx)
+ Math.abs(prefetchRegistry.mPrefetchDy);
for (int j = 0; j < prefetchRegistry.mCount * 2; j += 2) {
final Task task;
if (totalTaskIndex >= mTasks.size()) {
// 针对每个预加载的ViewHolder创建一个Task
task = new Task();
mTasks.add(task);
} else {
task = mTasks.get(totalTaskIndex);
}
final int distanceToItem = prefetchRegistry.mPrefetchArray[j + 1];
task.immediate = distanceToItem <= viewVelocity;
task.viewVelocity = viewVelocity;
task.distanceToItem = distanceToItem;
task.view = view;
task.position = prefetchRegistry.mPrefetchArray[j];
totalTaskIndex++;
}
}
// ... and priority sort
Collections.sort(mTasks, sTaskComparator);
}
GapWorker.flushTasksWithDeadline
private void flushTasksWithDeadline(long deadlineNs) {
// 遍历所有的Task开始预加载
for (int i = 0; i < mTasks.size(); i++) {
final Task task = mTasks.get(i);
// 当task中的view==null的时候完成预加载任务
if (task.view == null) {
break; // done with populated tasks
}
flushTaskWithDeadline(task, deadlineNs);
task.clear();
}
}
GapWorker.flushTaskWithDeadline
private void flushTaskWithDeadline(Task task, long deadlineNs) {
long taskDeadlineNs = task.immediate ? RecyclerView.FOREVER_NS : deadlineNs;
// 如果没能在deadlineNs之前构造好ViewHolder,则预加载失败
// 在prefetchPositionWithDeadline中会调用RecyclerView.tryGetViewHolderForPositionByDeadline进行预加载
// 这里是预取position位置的ViewHolder,在截止日期之前
RecyclerView.ViewHolder holder = prefetchPositionWithDeadline(task.view,
task.position, taskDeadlineNs);
if (holder != null
&& holder.mNestedRecyclerView != null
&& holder.isBound()
&& !holder.isInvalid()) {
// 预取RecyclerView内部的ViewHolder,这是处理嵌套RecyclerView的情况
// 当外部预取的ViewHolder不是无效的,且是被绑定的
prefetchInnerRecyclerViewWithDeadline(holder.mNestedRecyclerView.get(), deadlineNs);
}
}
在RecyclerView.tryGetViewHolderForPositionByDeadline方法中会根据dx和dy以及当前滑动的方向计算预加载的position,dx和dy是在RecyclerView.onTouchEvent中滑动动态更新的,
三、RecyclerView的ViewHolder的复用
RecyclerView的ViewHolder的复用,其实就是从缓存中取出相应的ViewHolder来重新使用。
复用流程包括手指滑动的时候和requestLayout()的
而具体的复用流程其实都是依赖于RecyclerView.Recycler来实现的。
1.手指滑动在onTouchEvent中触发
(1)RecyclerView.onTouchEvent
还是与预加载部分累死你,其实就是在预加载之前,通过调用scroollByInternal,开始判断缓存中是否有可以复用的ViewHolder
if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
if (mGapWorker != null && (dx != 0 || dy != 0)) {
mGapWorker.postFromTraversal(this, dx, dy);
}
}
(2)RecyclerView.scrollByInternal
boolean scrollByInternal(int x, int y, MotionEvent ev) {
...
if (mAdapter != null) {
// 执行滑动过程,在scrollStep中会调用mLayout.scrollVerticallyBy或者mLayout.scrollHorizontallyBy
// mLayout其实就是LayoutManager的实现类对象,比如以LinearLayoutManager举例
scrollStep(x, y, mScrollStepConsumed);
consumedX = mScrollStepConsumed[0];
consumedY = mScrollStepConsumed[1];
unconsumedX = x - consumedX;
unconsumedY = y - consumedY;
}
...
return consumedX != 0 || consumedY != 0;
}
(3)LinearLayoutManager.scrollVerticallyBy
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
(4)LinearLayoutManager.scrollBy
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() == 0 || dy == 0) {
return 0;
}
mLayoutState.mRecycle = true;
ensureLayoutState();
final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDy = Math.abs(dy);
updateLayoutState(layoutDirection, absDy, true, state);
// 调用fill填充LayoutState定义的给定布局,
// 在fill中主要就是做了两件事:回收ViewHolder到缓存中;从缓存中取出ViewHolder进行复用
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;
}
final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
mOrientationHelper.offsetChildren(-scrolled);
if (DEBUG) {
Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
}
mLayoutState.mLastScrollDelta = scrolled;
return scrolled;
}
在fill中主要会调用两个方法:recycleByLayoutState和layoutChunk,recycleByLayoutState主要的目的是用来回收ViewHolder的,而layoutChunk主要的目的就是从缓存中取出ViewHolder进行复用
(5)LinearLayoutManager.fill()
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
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
// 首先调用recycleByLayoutState回收ViewHolder
// 这个回收是从二级缓存开始回收,即回收的时候,最少都是将回收的ViewHolder加入到mCacheViews中
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
if (RecyclerView.VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
// 执行复用,layoutChunk的过程其实就是从缓存中取出View、或者创建View,最后调用addView
// 遍历调用layoutChunk
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (RecyclerView.VERBOSE_TRACING) {
TraceCompat.endSection();
}
if (layoutChunkResult.mFinished) {
break;
}
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 || mLayoutState.mScrapList != null
|| !state.isPreLayout()) {
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) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
if (DEBUG) {
validateChildOrder();
}
return start - layoutState.mAvailable;
}
(6)LinearLayoutManager.layoutChunk
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)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
// 测量子View(即每个item的预留空间)
// 测量预留空间主要就是通过mItemDecorations遍历取出每个ItemDecoration,然后调用getItemOffsets
// 在getItemOffsets中调用outRect给每个item预留空间,用于绘制
measureChildWithMargins(view, 0, 0);
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.
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();
}
(7)LinearLayoutManager.LayoutState.next
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
// 调用RecyclerView.Recycler的复用逻辑,这里就是进行复用的流程
// 这个View其实取出的就是ViewHolder.itemView
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
(8)RecyclerView.Recycler.getViewForPosition
这个方法内部很简单,主要是做了一件事,其实就是调用RecyclerView.Recycler.tryGetViewHolderForPositionByDeadline方法,这个方法在预取的时候,其实就是用来复用或者创建ViewHolder的
(9)RecyclerView.Recycler.tryGetViewHolderForPositionByDeadline
tryGetViewHolderForPositionByDeadline方法是RecyclerView的整个预取复用流程的关键,因为RecyclerView的缓存其实是基于ViewHolder的,需要的View其实也是从ViewHolder中取出
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
if (position < 0 || position >= mState.getItemCount()) {
throw new IndexOutOfBoundsException("Invalid item position " + position
+ "(" + position + "). Item count:" + mState.getItemCount()
+ exceptionLabel());
}
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
// 判断mChangedScrap是否有缓存,如果有则取出
// 不过这个有一个前提,就是在预布局的时候,我们这个流程是onTouchEvent的时候所以并不会满足
if (mState.isPreLayout()) {
// 分别根据position或者id取出对应的ViewHolder
// ChangedScrap缓存主要是与动画相关的
// 这里有一个条件mState.isPreLayout()要为true
// 一般在我们调用adapter的notifyItemChanged等方法时为true
// 因为数据发生了变化,viewholder被detach掉后缓存在mChangedScrap之中,在这里拿到的viewHolder后续需要重新绑定
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
// 从mAttachedScrap或者mCachedViews中查找是否有ViewHolder,这里是通过position寻找
// 即判断ViewHolder.LayoutPosition==position
if (holder == null) {
// 主要是根据position获取
// 这里可以做三件事:1.从mAttachedScrap一级缓存中找到ViewHolder
// 2.回收隐藏的但是未删除的View到一级缓存中(调用scrapView,可能是进入mAttachedScrap也可能是mChangedScrap)
// 3.从mCachedViews二级缓存中找到ViewHolder
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't be used
// 回收该holder,因为该holder不可用,这时是将这个不可用的ViewHolder加入到二级缓存中
// 这个ViewHolder可能是从一级缓存中取出,也可能是从二级缓存中取出
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();
}
// 如果取出的ViewHolder是不可用的,则加入到mCachedViews中,
// 但是如果不满足加入到mCachedViews的条件,则会加入到RecyclerViewPool中
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ "position " + position + "(offset:" + offsetPosition + ")."
+ "state:" + mState.getItemCount() + exceptionLabel());
}
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
// 找到ViewHolder对应的itemId,从mAttachedScrap或者mCachedViews中找到对应的ViewHolder
// 即通过判断holder.getItemId() == id,id是传入的目标id
if (mAdapter.hasStableIds()) {
// 首先会从mAttachedScrap获取ViewHolder
// 如果是不可用的,则会调用quickRecycleScrapView,其内部会调用recycleViewHolderInternal
// 将ViewHolder回收加入到mCachedViews中
// 如果mAttachedScrap找不到,则会从mCachedViews中查询ViewHolder
// 如果找到但是不可用的,则会调用recycleCachedViewAt,其内部会调用addViewHolderToRecycledViewPool
// 将ViewHolder加入到RecyclerViewPool中
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
// 从mViewCacheExtension中查询缓存的ViewHolder
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
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());
}
}
}
// 如果前面四种情况都没找到holder,则查询RecyclerViewPool
// RecyclerViewPool缓存默认上限是5个,是每个ViewType的上限为5个
// 根据ViewHolder的ViewType先查找对应的缓存ViewHolder的List
// 然后从List的末尾出队一个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);
}
}
}
// 如果前面四步查询缓存都没找到对应的ViewHolder,则调用adapter.createViewHolder创建ViewHolder
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");
}
}
}
// This is very ugly but the only place we can grab this information
// before the View is rebound and returned to the LayoutManager for post layout ops.
// We don't need this in pre-layout since the VH is not updated by the LM.
if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
if (mState.mRunSimpleAnimations) {
int changeFlags = ItemAnimator
.buildAdapterChangeFlagsForAnimations(holder);
changeFlags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
final ItemHolderInfo info = mItemAnimator.recordPreLayoutInformation(mState,
holder, changeFlags, holder.getUnmodifiedPayloads());
recordAnimationInfoIfBouncedHiddenView(holder, info);
}
}
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
if (DEBUG && holder.isRemoved()) {
throw new IllegalStateException("Removed holder should be bound and it should"
+ " come here only in pre-layout. Holder: " + holder
+ exceptionLabel());
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
// 获取到holder之后,调用Adapter.bindViewHolder方法绑定ViewHolder
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
if (lp == null) {
rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
holder.itemView.setLayoutParams(rvLayoutParams);
} else if (!checkLayoutParams(lp)) {
rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
holder.itemView.setLayoutParams(rvLayoutParams);
} else {
rvLayoutParams = (LayoutParams) lp;
}
rvLayoutParams.mViewHolder = holder;
rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;
return holder;
}
tryGetViewHolderForPositionByDeadline方法总结:
在复用阶段:
- 首先会从getScrapOrHiddenOrCachedHolderForPosition查询,如果找到的ViewHolder是不可用的,如果是从一级缓存中找到的,则加入到mCachedViews中,如果是从二级缓存中找到的,则加入到mRecyclerViewPool中。在这里,如果dryRun为false的时候,还会去获取隐藏的但是没有被remove的View,调用scrapView方法保存在mAttachedScrap或者mChangedScrap
- 如果Adapter有稳定的id,则从getScrapOrCachedViewForId中获取ViewHolder,首先也是从一级缓存mAttachedScrap中获取,如果该holder是不可用的,则调用quickRecycleScrapView,其内部调用recycleViewHolderInternal,将ViewHolder加入到二级缓存中,但是如果mCachedViews已经无法加入,则会将mCachedViews的第0个出队加入到RecyclerViewPool中,然后将新进入的ViewHolder加入到mCachedViews中,如果一级缓存中无法获取到对应的ViewHolder,则从二级缓存mCachedViews中获取,如果获取到的ViewHolder是不可用的,则通过调用recycleCachedViewAt方法,在其内部调用addViewHolderToRecycledViewPool方法,将ViewHolder加入到RecyclerViewPool中。
tryGetViewHolderForPositionByDeadline
->getScrapOrHiddenOrCachedHolderForPosition
->scrapView(回收隐藏但没有remove的View加入一级缓存)
->recycleViewHolderInternal(回收取自一级缓存的ViewHolder加入mCachedViews,回收取自mCachedViews中的ViewHolder加入RecyclerViewPool)
tryGetViewHolderForPositionByDeadline
->getScrapOrCachedViewForId
->quickRecycleScrapView(回收取自mAttachedScrap一级缓存的View)
->recycleViewHolderInternal(具体回收,将上一步取出的View加入mCachedViews)
->recycleCachedViewAt(回收取自mCachedViews二级缓存的View)
->addViewHolderToRecycledViewPool(将上一步取自mCachedViews的View,加入到RecyclerViewPool中)
整个复用流程,其实最终是通过调用RecyclerView.Recycler.tryGetViewHolderForPositionByDeadline方法进行,而复用流程中,还会判断从缓存中或者隐藏但是没有remove的ViewHolder是否可以使用,如果不可使用,就从低级缓存加入到高级缓存中。
在这里使用的方法,其实都是属于RecyclerView.Recycler这个内部类,Recycler是真正对ViewHolder进行回收复用的类
关于RecyclerViewPool
Pool默认大小为5个。即每个ViewType对应的ArrayList的大小最大为5个。
RecyclerViewPool是通过ViewType进行缓存的,缓存的是一个ArrayList<VIewHolder>
RecyclerViewPool.ScrapData就是RecyclerViewPool的缓存单元
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
RecyclerViewPool,是根据ViewType取出对应的缓存数据。根据ViewType缓存ScrapData到SparseArray中,而ScrapData这个类中有一个ArrayList,这个ArrayList就是用来缓存这个ViewType类型对应的ViewHolder的。这里是一个先进后出的结构,这是跟回收机制有关,才采用先进后出的结构。看下面的源码,可以看出,每次对RecyclerViewPool的出队都是优先最后一个,入队都是添加到末尾。
@Nullable
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;
}
(10)RecyclerView的预布局
判断RecyclerView是否处于预布局,则需要通过RecyclerView.State.mInPreLayout属性来判断,即当mInPreLayout为true的时候是处于预布局的时候。
而mInPreLayout赋值为true的地方有两部分,一部分是在onMeasure中,一部分是在onLayout中。不过这两部分都与RecyclerView.State.mRunPredictiveAnimations挂钩,在onMeasure中的mInPreLayout = true,需要满足RecyclerView.State.mRunPredictiveAnimations = true,而在onLayout中,直接就是将mInPreLayout = mRunPredictiveAnimations,所以就需要找到mRunPredictiveAnimations赋值为true的地方,即在RecyclerView.processAdapterUpdatesAndSetAnimationFlags()方法中,将mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations,所以有需要找到mRunSimpleAnimations赋值为true的地方,而mRunSimpleAnimations的赋值又与另一个变量有关,即RecyclerView.mFirstLayoutComplete,而mFirstLayoutComplete赋值为true的时候,才能让mInPreLayout为true。
mFirstLayoutComplete赋值为true,是在onLayout中,但是是在onLayout的末尾才赋值为true。
@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;
}
所以在第一次执行onLayout的时候,肯定不会执行预布局。其实onLayout调用过程中,调用dispatchLayout中dispatchLayoutStep1()方法其实主要就是用来完成预布局功能。
dispatchLayoutStep1()
1.处理Adapter更新;2.决定是否执行ItemAnimator;3.保存ItemView的动画信息。本方法也被称为preLayout(预布局),当Adapter更新了,这个方法会保存每个ItemView的旧信息(oldViewHolderInfo)
2.RecyclerView.onLayout触发回收复用
(1)RecyclerView.onLayout
(2)RecyclerView.dispatchLayout()
(3)RecyclerView.dispatchLayoutStep1/2/3
不过dispatchLayoutStep1()内部的执行,都想需要依赖于mRunSimpleAnimations为true,而mRunSimpleAnimations为true,需要mFirstLayoutComplete为true,所以在第一次执行onLayout的时候,并不会执行dispatchLayoutStep1()中的主要的内容。
// 这里如果mRunSimpleAnimations不为true,主要就是做一些初始化
private void dispatchLayoutStep1() {
mState.assertLayoutStep(State.STEP_START);
// 保存剩余的滚动值,包括x和y方向还可以滚动多少距离
fillRemainingScrollValues(mState);
mState.mIsMeasuring = false;
// 开始拦截布局请求,应该是拦截requestLayout过程
startInterceptRequestLayout();
mViewInfoStore.clear();
// 布局或者滚动计数+1,即onTouchEvent的move事件或者onLayout过程的计数+1
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
saveFocusInfo();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
// 运行简单动画
if (mState.mRunSimpleAnimations) {
// Step 0: Find out where all non-removed items are, pre-layout
// 找到没有被remove的itemView,并且将这个itemView的ViewHolder保存在mViewInfoStore,
// 同时还将预布局的位置也保存在mViewInfoStore中
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
continue;
}
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
mViewInfoStore.addToPreLayout(holder, animationInfo);
if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
&& !holder.shouldIgnore() && !holder.isInvalid()) {
long key = getChangedHolderKey(holder);
// This is NOT the only place where a ViewHolder is added to old change holders
// list. There is another case where:
// * A VH is currently hidden but not deleted
// * The hidden item is changed in the adapter
// * Layout manager decides to layout the item in the pre-Layout pass (step1)
// When this case is detected, RV will un-hide that view and add to the old
// change holders list.
mViewInfoStore.addToOldChangeHolders(key, holder);
}
}
}
// 运行预动画
if (mState.mRunPredictiveAnimations) {
// 在这里会使用旧的position的item进行预布局。而且在这里会调用onLayoutChildren进行布局
// 不过这里只是进行预布局,只是先确定每个itemView的位置,预布局之后,
// 此时取到的每个ItemView的ViewHolder和ItemHolderInfo,便是每个ItemView的最终信息。
// Step 1: run prelayout: This will use the old positions of items. The layout manager
// is expected to layout everything, even removed items (though not to add removed
// items back to the container). This gives the pre-layout position of APPEARING views
// which come into existence as part of the real layout.
// Save old positions so that LayoutManager can run its mapping logic.
saveOldPositions();
final boolean didStructureChange = mState.mStructureChanged;
mState.mStructureChanged = false;
// temporarily disable flag because we are asking for previous layout
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = didStructureChange;
for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
final View child = mChildHelper.getChildAt(i);
final ViewHolder viewHolder = getChildViewHolderInt(child);
if (viewHolder.shouldIgnore()) {
continue;
}
if (!mViewInfoStore.isInPreLayout(viewHolder)) {
int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
boolean wasHidden = viewHolder
.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
if (!wasHidden) {
flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
}
final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
if (wasHidden) {
recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
} else {
mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
}
}
}
// we don't process disappearing list because they may re-appear in post layout pass.
clearOldPositions();
} else {
clearOldPositions();
}
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
mState.mLayoutStep = State.STEP_LAYOUT;
}
(4)LinearLayoutManager.onLayoutChildren()
从这个方法开始,其实流程就与MOVE事件滑动触发onTouchEvent,然后复用或者创建ViewHolder的流程一致。
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm:
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position.
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
// 4) scroll to fulfill requirements like stack from bottom.
// create layout state
if (DEBUG) {
Log.d(TAG, "is pre layout:" + state.isPreLayout());
}
if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
// 如果列表的item数目为0,清空所有的View
if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
return;
}
}
if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
}
ensureLayoutState();
mLayoutState.mRecycle = false;
// resolve layout direction
resolveShouldLayoutReverse();
// 第一步:确定锚点信息
final View focused = getFocusedChild();
if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
|| mPendingSavedState != null) {
mAnchorInfo.reset();
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// 计算锚点的位置和坐标
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
} else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
>= mOrientationHelper.getEndAfterPadding()
|| mOrientationHelper.getDecoratedEnd(focused)
<= mOrientationHelper.getStartAfterPadding())) {
mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
}
if (DEBUG) {
Log.d(TAG, "Anchor info:" + mAnchorInfo);
}
// LLM may decide to layout items for "extra" pixels to account for scrolling target,
// caching or predictive animations.
int extraForStart;
int extraForEnd;
final int extra = getExtraLayoutSpace(state);
// If the previous scroll delta was less than zero, the extra space should be laid out
// at the start. Otherwise, it should be at the end.
if (mLayoutState.mLastScrollDelta >= 0) {
extraForEnd = extra;
extraForStart = 0;
} else {
extraForStart = extra;
extraForEnd = 0;
}
extraForStart += mOrientationHelper.getStartAfterPadding();
extraForEnd += mOrientationHelper.getEndPadding();
if (state.isPreLayout() && mPendingScrollPosition != RecyclerView.NO_POSITION
&& mPendingScrollPositionOffset != INVALID_OFFSET) {
// if the child is visible and we are going to move it around, we should layout
// extra items in the opposite direction to make sure new items animate nicely
// instead of just fading in
final View existing = findViewByPosition(mPendingScrollPosition);
if (existing != null) {
final int current;
final int upcomingOffset;
if (mShouldReverseLayout) {
current = mOrientationHelper.getEndAfterPadding()
- mOrientationHelper.getDecoratedEnd(existing);
upcomingOffset = current - mPendingScrollPositionOffset;
} else {
current = mOrientationHelper.getDecoratedStart(existing)
- mOrientationHelper.getStartAfterPadding();
upcomingOffset = mPendingScrollPositionOffset - current;
}
if (upcomingOffset > 0) {
extraForStart += upcomingOffset;
} else {
extraForEnd -= upcomingOffset;
}
}
}
int startOffset;
int endOffset;
final int firstLayoutDirection;
if (mAnchorInfo.mLayoutFromEnd) {
firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
: LayoutState.ITEM_DIRECTION_HEAD;
} else {
firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
: LayoutState.ITEM_DIRECTION_TAIL;
}
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
// 对所有的ItemView进行回收,这里是将ItemView回收到了mCachedViews中
detachAndScrapAttachedViews(recycler);
mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mIsPreLayout = state.isPreLayout();
if (mAnchorInfo.mLayoutFromEnd) {
// 开始填充
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
final int firstElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
// end could not consume all. add more items towards start
extraForStart = mLayoutState.mAvailable;
updateLayoutStateToFillStart(firstElement, startOffset);
mLayoutState.mExtra = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
} else {
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtra = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
final int lastElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForStart += mLayoutState.mAvailable;
}
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtra = extraForStart;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
if (mLayoutState.mAvailable > 0) {
extraForEnd = mLayoutState.mAvailable;
// start could not consume all it should. add more items towards end
updateLayoutStateToFillEnd(lastElement, endOffset);
mLayoutState.mExtra = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
}
}
// changes may cause gaps on the UI, try to fix them.
// TODO we can probably avoid this if neither stackFromEnd/reverseLayout/RTL values have
// changed
if (getChildCount() > 0) {
// because layout from end may be changed by scroll to position
// we re-calculate it.
// find which side we should check for gaps.
if (mShouldReverseLayout ^ mStackFromEnd) {
int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
startOffset += fixOffset;
endOffset += fixOffset;
fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
startOffset += fixOffset;
endOffset += fixOffset;
} else {
int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
startOffset += fixOffset;
endOffset += fixOffset;
fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
startOffset += fixOffset;
endOffset += fixOffset;
}
}
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
if (!state.isPreLayout()) {
mOrientationHelper.onLayoutComplete();
} else {
mAnchorInfo.reset();
}
mLastStackFromEnd = mStackFromEnd;
if (DEBUG) {
validateChildOrder();
}
}
3.RecyclerView缓存复用机制总结
其实复用机制,最终都是进入fill方法进行复用,将ViewHolder从缓存中取出进行复用。而fill获取View,然后将View通过addView添加到布局中。而fill()方法中获取的View,其实就是通过调用LayoutState.next(),内部会优先从4级缓存中取出holder,如果都没有,则调用createViewHolder创建holder,然后会返回holder中的itemView用来添加到RecyclerView中。
四、RecyclerView的ViewHolder的回收
在onLayout流程的回收中,其实会有两部分缓存的回收,其一就是在onLayoutChildren中调用detachAndScrapAttachedViews调用scrapOrRecycleView()回收holder到一级缓存,调用RecyclerView.Recycler.recycleViewHolderInternal回收holder到2-4级缓存中;其二就是在onLayoutChildren中调用fill回收holder到2-4级缓存中。
RecyclerView的ViewHolder的回收,也从onTouchEvent和onLayout两个流程进行分析
1.onTouchEvent分析ViewHolder的回收
前面一部分与ViewHolder复用过程其实是一样的。
RecyclerView.onTouchEvent的move事件->
RecyclerView.scrollByInternal->
mLayout.scrollVerticallyBy()(mLayout其实就是LayoutManager)->
LinearLayoutManager.scrollVerticallyBy()->
LinearLayoutManager.scrollBy()->
LinearLayoutManager.fill(recycler, mLayoutState, state, false)->
即最终都会执行LinearLayoutManager.fill()方法,而在LinearLayoutManager.fill()方法中,首先就会调用recycleByLayoutState进行回收。
(1)LinearLayoutManager.recycleByLayoutState
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
if (!layoutState.mRecycle || layoutState.mInfinite) {
return;
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
recycleViewsFromEnd(recycler, layoutState.mScrollingOffset);
} else {
recycleViewsFromStart(recycler, layoutState.mScrollingOffset);
}
}
这里的两个方法其实类似,只不过一个是开始位置,一个是从结束位置。
(2)LinearLayoutManager.recycleViewsFromEnd
private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int dt) {
final int childCount = getChildCount();
if (dt < 0) {
if (DEBUG) {
Log.d(TAG, "Called recycle from end with a negative value. This might happen"
+ " during layout changes but may be sign of a bug");
}
return;
}
final int limit = mOrientationHelper.getEnd() - dt;
if (mShouldReverseLayout) {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedStart(child) < limit
|| mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
// stop here
recycleChildren(recycler, 0, i);
return;
}
}
} else {
for (int i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
if (mOrientationHelper.getDecoratedStart(child) < limit
|| mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
// stop here
recycleChildren(recycler, childCount - 1, i);
return;
}
}
}
}
在这里其实就是调用recycleChildren进行回收
(3)LinearLayoutManager.recycleChildren
private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
if (startIndex == endIndex) {
return;
}
if (DEBUG) {
Log.d(TAG, "Recycling " + Math.abs(startIndex - endIndex) + " items");
}
if (endIndex > startIndex) {
for (int i = endIndex - 1; i >= startIndex; i--) {
removeAndRecycleViewAt(i, recycler);
}
} else {
for (int i = startIndex; i > endIndex; i--) {
removeAndRecycleViewAt(i, recycler);
}
}
}
(4)LinearLayoutManager.removeAndRecycleViewAt
public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
final View view = getChildAt(index);
// 在这里,只是先remove,但是并没有detach
// 绑定的数据等信息都还在,这意味着从mCachedViews取出的视图如果符合需要的目标视图是可以直接展示的,而不需要重新绑定
removeViewAt(index);
recycler.recycleView(view);
}
其实从这四步可以看出,最终就是调用RecyclerView.Recycler.recycleView()方法,而该方法内部就是通过调用RecyclerView.Recycler.recycleViewHolderInternal()进行回收,将ViewHolder保存在mCachedViews、RecyclerViewPool等二到四级缓存中。
在RecyclerView.Recycler.recycleViewHolderInternal()方法进行回收的过程中,首先会判断mCachedViews是否已经满了,mCachedViews的默认大小为2,如果mCachedViews已经满了,则会将先进入mCachedViews中的ViewHolder移除,并且将这个移除的ViewHolder加入到RecyclerViewPool中,在加入到RecyclerViewPool中的时候,会判断当前ViewType对应的List集合是否已经大于等于5个,如果已经达到这个最大值,则会放弃最新加入的ViewHolder。
mCachedViews中缓存的ViewHolder是带有数据的,所以这些ViewHolder是不同的;而RecyclerViewPool中缓存的ViewHolder是一样的,是不带有数据的。因为在将ViewHolder添加到pool之前,会调用ViewHolder.resetInternal方法重置数据,这样每个ViewHolder就变成一样的不带数据的。
因为在添加进mCachedViews之前,只是remove了视图,而没有detach,说明绑定的数据等信息都还在,这意味着从mCachedViews取出的视图如果符合需要的目标视图是可以直接展示的,而不需要重新绑定
2.onLayout回收流程
(1)根据onLayout的回收流程:在RecyclerView.dispatchLayout()中调用的方法解析
dispatchLayoutStep1
- Adapter的更新;
- 决定该启动哪种动画; 即是否执行ItemAnimator
- 保存当前View的信息(getLeft(), getRight(), getTop(), getBottom()等);
- 如果有必要,先跑一次布局并将信息保存下来。
dispatchLayoutStep1方法其实也是RecyclerView的预布局,在第一次执行onLayout的时候,并不会执行该方法,因为该方法的执行在流程上mFirstLayoutComplete赋值为true的时候,而第一个onLayout的时候,dispatchLayoutStep1是在dispatchLayout()中执行,dispatchLayout()是早于mFirstLayoutComplete = true;,dispatchLayoutStep1预布局会保存每个ItemView的旧信息(oldViewHolderInfo)
dispatchLayoutStep2
真正对子View做布局的地方。
- 计算锚点,以锚点开始填充RecyclerView(其实就是执行fill方法)。
- 执行fill方法,判断RecyclerView是否还有空间,如果有,执行layoutChunk方法,直至填充满。
- layoutChunk方法中,寻找到当前要添加的子view,add到RecyclerView中。
- 对子view进行measure和layout。
dispatchLayoutStep3
为动画保存View的相关信息; 触发动画; 相应的清理工作。
其实dispatchLayoutStep3()就是做了一些收尾工作,将一些变量重置,处理下动画。
mState.mLayoutStep
- 初始化为STEP_START
- 执行完dispatchLayoutStep1后,mState.mLayoutStep = State.STEP_LAYOUT;
- 执行完dispatchLayoutStep2后,mState.mLayoutStep = State.STEP_ANIMATIONS;
- 执行完dispatchLayoutStep3后,mState.mLayoutStep = State.STEP_START;
(2)具体的onLayout回收流程
mAttachedScrap和mChangedScrap的值是通过onLayout流程中放入缓存的ViewHolder的。这个放入的流程
RecyclerView.onLayout->
RecyclerView.dispatchLayout()->
RecyclerView.dispatchLayoutStep1/2/3这三个方法->
LinearLayoutManager.onLayoutChildren()->
RecyclerView.LayoutManager.detachAndScrapAttachedViews->
RecyclerView.LayoutManager.scrapOrRecycleView()->
recycler.scrapView(view);
在detachAndScrapAttachedViews方法回收中,还有一种可能会调用RecyclerView.Recycler.recycleViewHolderInternal回收holder到2-4级缓存中。
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);
// 回收到2-4级缓存中
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
// 回收到一级缓存中。
// 标记没有移除或者失效、ViewHolder没有更新,可重复使用更新ViewHolder的时候
// 加入到mAttachedScrap中
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
如果这里的if判断,ViewHolder是无效的&&没有移除&&没有改变&&没有StateIds的话,则会调用RecyclerView.Recycler.recycleViewHolderInternal回收ViewHolder到2-4级缓存中。
这里就是一级的回收,将回收后的数据保存在mChangedScrap或者mAttachedScrap中。
但是并不是所有情况都会进行一级回收,只有当ViewHolder是无效的,并且没有被remove,并且adapter没有稳定的id的时候,就不会进行一级回收,而是与onTouchEvent中的回收流程一致,进行删除表项,将mCachedViews中的缓存添加到Pool中,然后向mCachedViews中添加新的缓存。
而一级缓存只有在onLayout布局阶段才会回收然后进行添加,并且并不是所有的布局阶段都会添加一级缓存。
void recycleViewHolderInternal(ViewHolder holder) {
if (holder.isScrap() || holder.itemView.getParent() != null) {
throw new IllegalArgumentException(
"Scrapped or attached views may not be recycled. isScrap:"
+ holder.isScrap() + " isAttached:"
+ (holder.itemView.getParent() != null) + exceptionLabel());
}
if (holder.isTmpDetached()) {
throw new IllegalArgumentException("Tmp detached view should be removed "
+ "from RecyclerView before it can be recycled: " + holder
+ exceptionLabel());
}
if (holder.shouldIgnore()) {
throw new IllegalArgumentException("Trying to recycle an ignored view holder. You"
+ " should first call stopIgnoringView(view) before calling recycle."
+ exceptionLabel());
}
//noinspection unchecked
final boolean transientStatePreventsRecycling = holder
.doesTransientStatePreventRecycling();
final boolean forceRecycle = mAdapter != null
&& transientStatePreventsRecycling
&& mAdapter.onFailedToRecycleView(holder);
boolean cached = false;
boolean recycled = false;
if (DEBUG && mCachedViews.contains(holder)) {
throw new IllegalArgumentException("cached view received recycle internal? "
+ holder + exceptionLabel());
}
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) {
// 当mCachedViews存放满了,则会mCachedViews的第0个加入到RecyclerViewPool中,并且删除第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 {
// NOTE: A view can fail to be recycled when it is scrolled off while an animation
// runs. In this case, the item is eventually recycled by
// ItemAnimatorRestoreListener#onAnimationFinished.
// TODO: consider cancelling an animation when an item is removed scrollBy,
// to return it to the pool faster
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());
}
}
// even if the holder is not removed, we still call this method so that it is removed
// from view holder lists.
mViewInfoStore.removeViewHolder(holder);
if (!cached && !recycled && transientStatePreventsRecycling) {
holder.mOwnerRecyclerView = null;
}
}
四级缓存数据回收总结
一级回收都是调用的Recycler.scrapView,这个方法可能是在onLayout中被调用,也可能是在复用的时候回收隐藏但是没有被remove的ViewHolder。在onLayout的过程中,是在调用detachAndScrapAttachedViews方法中,调用scrapOrRecycleView中的Recycler.scrapView进行一级缓存的回收,可能是mChangedScrap,也可能是mAttachedScrap;而在复用阶段回收,即在fill填充的时候回收到一级缓存中,则是在tryGetViewHolderForPositionByDeadline方法根据position获取ViewHolder的时候,调用getScrapOrHiddenOrCachedHolderForPosition方法中,判断ViewHolder是隐藏且没有被remove的,调用Recycler.scrapView进行一级缓存的回收,加入到mChangedScrap或者mAttachedScrap
二级回收都是调用的Recycler.recycleViewHolderInternal方法,这个方法可能是在下面的情况中被调用
RecyclerView.removeAnimatingView
在onLayout的回收流程中LinearLayoutManager.scrapOrRecycleView
在复用流程中tryGetViewHolderForPositionByDeadline中根据position获取的ViewHolder不可用的时候
tryGetViewHolderForPositionByDeadline --> getScrapOrCachedViewForId -->quickRecycleScrapView
预加载prefetchPositionWithDeadline
removeView的时候
recycleViewHolderInternal方法主要作用是回收ViewHolder到mCachedViews中,如果不能加入到mCachedViews中,则会将mCachedViews中的第一个加入到RecyclerViewPool中,然后将mCachedViews中的第一个出队,再将新加入的ViewHolder加入到mCachedViews中
- 四级回收RecyclerViewPool都是调用的addViewHolderToRecycledViewPool,该方法会在回收阶段和复用阶段被调用。
在复用阶段,即在tryGetViewHolderForPositionByDeadline中因为获取到的ViewHolder不可用,则可能会加入到ReyclcerViewPool中,一种是通过position获取ViewHolder的时候,不可用则直接调用Recycler.recycleViewHolderInternal,一种是通过ID获取ViewHolder,即getScrapOrCachedViewForId方法中,从mCachedViews中获取的ViewHolder是不可用的时候;
在回收阶段,四级缓存的回收,最终都是调用Recycler.recycleViewHolderInternal回收四级缓存,
参考:
在自己写的时候,有参考这个作者写的一些关于RecyclerView的文章,这里就写出其中一篇作为入口
其实RecyclerView的布局流程、滑动机制、缓存机制贯穿整个RecyclerView。
RecyclerView 源码分析(一) - RecyclerView的三大流程
https://www.jianshu.com/p/61fe3f3bb7ec
https://phantomvk.github.io/2019/02/13/RecyclerView_cache/