一、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的三大流程
http://www.lxweimin.com/p/61fe3f3bb7ec
https://phantomvk.github.io/2019/02/13/RecyclerView_cache/