Android RecyclerView四級(jí)緩存源碼層詳細(xì)分析
RecyclerView是一個(gè)非常重要的控件,是任何一個(gè)研發(fā)都需要掌握的,這個(gè)控件的設(shè)計(jì)也是非常優(yōu)秀的,值得我們?nèi)W(xué)習(xí)。RecyclerView的核心就是緩存機(jī)制,RecyclerView為了提升效率使用了4級(jí)緩存:
- mChangeScrap與 mAttachedScrap:用來緩存還在屏幕內(nèi)的 ViewHolder,是ViewHolder的ArrayList 集合。
- mCacheView:緩存將要隱藏ViewHolder 下次將要顯示的ViewHolder 先從這個(gè)緩存里邊獲取,也是ViewHolder的 ArrayList 集合。
- mViewChcheExtension:需要用戶自己實(shí)現(xiàn)的緩存,這一級(jí)系統(tǒng)會(huì)調(diào)用一個(gè)抽象方法,這個(gè)方法需要用戶自己實(shí)現(xiàn)。
- mRecyclerPool:緩存池 ,這個(gè)用戶根據(jù)不同的ViewType保存緩存池 ,這個(gè)緩存池是一個(gè)二維數(shù)組 外部是ScrapData 的SparseArray數(shù)組,內(nèi)部是ArrayList數(shù)組。
1.緩存的使用流程源碼分析-滑動(dòng)入口
當(dāng)用戶在滑動(dòng)Item的時(shí)候會(huì)進(jìn)行ViewHolder的復(fù)用,下面來看滑動(dòng)方法:RecyclerView的onTouchEvent方法case MotionEvent.ACTION_MOVE
@Override
public boolean onTouchEvent(MotionEvent e) {
if (mLayoutFrozen || mIgnoreMotionEventTillDown) {
return false;
}
if (dispatchOnItemTouch(e)) {
cancelTouch();
return true;
}
if (mLayout == null) {
return false;
}
...
switch (action) {
case MotionEvent.ACTION_DOWN:
...
case MotionEvent.ACTION_MOVE: {
...
if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
//入口在這里 因?yàn)榛瑒?dòng)的時(shí)候會(huì)發(fā)生緩存操作 所以一個(gè)入口在這里
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;
case MotionEvent.ACTION_POINTER_UP: {
onPointerUp(e);
} break;
case MotionEvent.ACTION_UP:
...
vtev.recycle();
return true;
}scrollByInternal 方法就是使用緩存的入口方法
下面來看scrollByInternal方法
boolean scrollByInternal(int x, int y, MotionEvent ev) {
int unconsumedX = 0, unconsumedY = 0;
int consumedX = 0, consumedY = 0;
consumePendingUpdateOperations();
if (mAdapter != null) {
eatRequestLayout();
onEnterLayoutOrScroll();
Trace.beginSection(TRACE_SCROLL_TAG);
if (x != 0) {
consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
unconsumedX = x - consumedX;
}
if (y != 0) {
consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
unconsumedY = y - consumedY;
}
Trace.endSection();
repositionShadowingViews();
onExitLayoutOrScroll();
resumeRequestLayout(false);
}
...
return consumedX != 0 || consumedY != 0;
}
這里區(qū)分橫向和縱向滑動(dòng):scrollHorizontallyBy與scrollVerticallyBy
下面分析縱向滑動(dòng)的情況scrollVerticallyBy(橫向類似):
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state);
}
這里調(diào)用了scrollBy方法,繼續(xù)往下跟
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);
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;
}
這里有個(gè)關(guān)鍵方法:fill,當(dāng)布局或者上下滾動(dòng)的時(shí)候會(huì)調(diào)用fill方法。
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) { //布局或者上下滾動(dòng)的時(shí)候會(huì)調(diào)用
// 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(recycler, layoutState); //回收ViewHolder
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
layoutChunk(recycler, state, layoutState, layoutChunkResult); //循環(huán)調(diào)用 這里是layout的核心
if (layoutChunkResult.mFinished) {
break;
}
...
}
if (DEBUG) {
validateChildOrder();
}
return start - layoutState.mAvailable;
}
layoutChunk這個(gè)方法是使用緩存的入口,recycleByLayoutState這個(gè)是進(jìn)行ViewHolder緩存的入口。
下面來看layoutChunk:
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
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;
}
...
result.mFocusable = view.isFocusable();
}
這個(gè)方法里邊調(diào)用了layoutState的next方法得到一個(gè)View,那么關(guān)鍵就是next方法了
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
這個(gè)方法又調(diào)用了recycler.getViewForPosition方法,最終調(diào)到了tryGetViewHolderForPositionByDeadline這個(gè)方法。
下面來分析tryGetViewHolderForPositionByDeadline這個(gè)方法,整個(gè)ViewHolder的復(fù)用流程都在這里,這里是最核心的位置:
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());
}
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) { //通過位置從mChangeScrap緩存中獲取ViewHolder
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {//通過position的方式從mAttachScrap或者mCacheViews中獲取ViewHolder
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
...
}
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());
}
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun); //通過id的方式從mAttachScrap或者mCacheViews中獲取ViewHolder
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
if (holder == null && mViewCacheExtension != null) { //從用戶自定義緩存獲取ViewHolder
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
...
}
if (holder == null) { // 從緩存池獲取ViewHolder
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);
}
}
}
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;
} //如果還是獲取不到ViewHolder,那么就需要通過createViewHolder創(chuàng)建了
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");
}
}
}
...
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);
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
//這里會(huì)調(diào)用到onBindViewHolder方法進(jìn)行數(shù)據(jù)的綁定
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
...
return holder;
}
- getChangedScrapViewForPosition:通過位置從mChangeScrap緩存中獲取ViewHolder。
- getScrapOrHiddenOrCachedHolderForPosition:通過position的方式從mAttachScrap或者mCacheViews中獲取ViewHolder。
- getScrapOrCachedViewForId:通過id的方式從mAttachScrap或者mCacheViews中獲取ViewHolder
- mViewCacheExtension.getViewForPositionAndType:從用戶自定義緩存獲取ViewHolder(這里系統(tǒng)未做實(shí)現(xiàn),需要用戶自定義)
- getRecycledViewPool().getRecycledView(type):從緩存池獲取ViewHolder
- mAdapter.createViewHolder:如果從各個(gè)緩存中獲取不到ViewHolder,那么就需要通過createViewHolder創(chuàng)建了
- tryBindViewHolderByDeadline:這里會(huì)調(diào)用到onBindViewHolder方法進(jìn)行數(shù)據(jù)的綁定
以上就是整個(gè)ViewHolder獲取過程,首先從緩存池獲取,獲取不到才會(huì)創(chuàng)建,然后進(jìn)行數(shù)據(jù)綁定。
2.RecyclerView的緩存流程
在進(jìn)行l(wèi)ayout操作的時(shí)候就會(huì)進(jìn)行ViewHolder的緩存操作,將創(chuàng)建好的ViewHolder緩存到緩存池,以便直接使用,下面分析一下ViewHolder是如何緩存到緩存池中的。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Trace.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout(); //這里是擺放的入口
Trace.endSection();
mFirstLayoutComplete = true;
}
下面是dispatchLayout:
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
// leave the state in START
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
// leave the state in START
return;
}
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
// First 2 steps are done in onMeasure but looks like we have to run again due to
// changed size.
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
下面來看dispatchLayoutStep2:
private void dispatchLayoutStep2() {
eatRequestLayout();
onEnterLayoutOrScroll();
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
mState.mLayoutStep = State.STEP_ANIMATIONS;
onExitLayoutOrScroll();
resumeRequestLayout(false);
}
這個(gè)方法中會(huì)調(diào)用onLayoutChildren方法,這個(gè)方法是緩存的核心所在。
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
...
onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
detachAndScrapAttachedViews(recycler); //分離并廢棄附加視圖
...
}
這個(gè)方法內(nèi)容較多,做了省略。detachAndScrapAttachedViews這個(gè)方法會(huì)將ViewHolder緩存到緩存池中。
public void detachAndScrapAttachedViews(Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
調(diào)到了scrapOrRecycleView方法
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder); //這是一個(gè)收集的情況
} else {
detachViewAt(index);
recycler.scrapView(view); //這是一個(gè)情況
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}- recycleViewHolderInternal :這個(gè)方法主要是緩存到mCacheViews或者RecyclerViewPool中
- scrapView:這個(gè)情況會(huì)將ViewHolder緩存到mAttachScrap中或者mChangedScrap中
下面來分析recycleViewHolderInternal:
void recycleViewHolderInternal(ViewHolder holder) { //主要處理CacheViews 和RecyclerPool 的緩存
...
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// Retire oldest cached view
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}
int targetCacheIndex = cachedViewSize;
if (ALLOW_THREAD_GAP_WORK
&& cachedViewSize > 0
&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
// when adding the view, skip past most recently prefetched views
int cacheIndex = cachedViewSize - 1;
while (cacheIndex >= 0) {
int cachedPos = mCachedViews.get(cacheIndex).mPosition;
if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
break;
}
cacheIndex--;
}
targetCacheIndex = cacheIndex + 1;
}
mCachedViews.add(targetCacheIndex, holder); //這里是加入到mCachedViews中
cached = true;
}
if (!cached) { //這里是加入到RecycledViewPool緩存池中
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
...
}- mCachedViews.add(targetCacheIndex, holder):將ViewHolder加入到mCachedViews中
- addViewHolderToRecycledViewPool:加入到RecycledViewPool緩存池中
下面是scrapView部分:
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
throw new IllegalArgumentException("Called scrap view with an invalid view."
+ " Invalid views cannot be reused from scrap, they should rebound from"
+ " recycler pool.");
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
根據(jù)不同的情況會(huì)將ViewHolder緩存到mAttachedScrap或者mChangedScrap中
3.RecyclerView緩存總結(jié)
RecyclerView 緩存的是ViewHolder
RecyclerView采用了四級(jí)緩存:緩存的分類是根據(jù)功能區(qū)分
- mAttachedScrap : 緩存可見的ViewHolder 用于 執(zhí)行onLayout的時(shí)候 ArrayList 集合
- mCacheView:緩存將要隱藏ViewHolder 下次將要顯示的ViewHolder 先從這個(gè)緩存里邊獲取 ArrayList 集合
- mViewChcheExtension:需要用戶自己實(shí)現(xiàn)的緩存
- mRecyclerPool:緩存池,這個(gè)用戶根據(jù)不同的ViewType保存緩存池 , ScrapData包含一個(gè)ArrayList mScrap 是一個(gè)SparseArray數(shù)組,所以緩存池是一個(gè)二維數(shù)組。
ViewHolder的創(chuàng)建流程
- 先從mAttachedScrap 緩存 查找ViewHolder
- 然后從mCacheView 查找
- 然后從mViewCacheExtension
- 然后 從來mRecyclerPool查找
- 如果還是沒有 就需要調(diào)用onCreateViewHolder方法來新創(chuàng)建
到此這篇關(guān)于Android RecyclerView四級(jí)緩存源碼層詳細(xì)分析的文章就介紹到這了,更多相關(guān)Android RecyclerView內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Android實(shí)現(xiàn)RecyclerView嵌套流式布局的詳細(xì)過程
- Android RecyclerView實(shí)現(xiàn)吸頂動(dòng)態(tài)效果流程分析
- Android RecyclerView緩存復(fù)用原理解析
- Android RecyclerView使用入門介紹
- Android開發(fā)RecyclerView單獨(dú)刷新使用技巧
- Android開發(fā)RecyclerView實(shí)現(xiàn)折線圖效果
- Android?手寫RecyclerView實(shí)現(xiàn)列表加載
- Android獲取RecyclerView滑動(dòng)距離方法詳細(xì)講解
相關(guān)文章
Android實(shí)現(xiàn)精確到天時(shí)分秒的搶購倒計(jì)時(shí)
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)精確到天時(shí)分秒的搶購倒計(jì)時(shí),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02
BootStrapValidator與My97日期校驗(yàn)的實(shí)例代碼
這篇文章給大家介紹了bootstrapvalidator與my97日期校驗(yàn)的實(shí)例代碼,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-01-01
Android開發(fā)實(shí)戰(zhàn)之漂亮的ViewPager引導(dǎo)頁
這篇文章主要介紹了Android開發(fā)實(shí)戰(zhàn)中漂亮ViewPager引導(dǎo)頁的制作過程,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-08-08
Android 三行代碼實(shí)現(xiàn)高斯模糊效果
這篇文章主要介紹了Android 三行代碼實(shí)現(xiàn)高斯模糊效果,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-09-09

