android进阶篇02RecyclerView回收复用机制源码解析
Posted datian1234
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android进阶篇02RecyclerView回收复用机制源码解析相关的知识,希望对你有一定的参考价值。
原文链接:https://juejin.cn/post/6960953337965445128
前言
首先我们明确一点,回收复用的是ViewHolder,并且回收复用机制一般包括四级缓存:
1.mAttachedScrap和mChangedScrap
2.mCachedViews
3.自定义缓存机制mViewCachedExtension
4.RecyclerViewPool缓存池
其中第三级自定义缓存一般不需要,这里不再讨论;滑动时主要是使用第二级和第四级缓存,这也是使用最多的情况,我们在下面会详细讨论这种情况;
第一级缓存主要用于屏幕内ViewHolder的缓存和复用,例如下拉刷新等需要重新布局的操作可以直接复用ViewHolder;
一、回收复用前的调用链分析
1、RecyclerView -> onTouchEvent
既然是滑动时进行回收复用,那自然可以想到从RecyclerView的onTouchEvent方法的MOVE分支中切入分析,注释1处会调用scrollByInternal方法;
public boolean onTouchEvent(MotionEvent e) {
、、、
case MotionEvent.ACTION_POINTER_DOWN: {
、、、
} break;
case MotionEvent.ACTION_MOVE: {
、、、
//1
if (scrollByInternal(canScrollHorizontally ? dx : 0,canScrollVertically ? dy : 0,e)){
getParent().requestDisallowInterceptTouchEvent(true);
}
} break;
、、、
}
2、RecyclerView -> scrollByInternal
scrollByInternal中又会调用scrollStep;
boolean scrollByInternal(int x, int y, MotionEvent ev) {
、、、
scrollStep(x, y, mReusableIntPair);
、、、
}
3、RecyclerView -> scrollStep
我们以注释2举例,又会调用mLayout的scrollVerticallyBy方法,我们以最常用的LinearLayoutManager举例;
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
、、、
if (dx != 0) {
consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState); //1
}
if (dy != 0) {
consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState); //2
}
}
4、LinearLayoutManager -> scrollVerticallyBy
如下所示,注释1表示scrollVerticallyBy又会调用scrollBy;
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == HORIZONTAL) {
return 0;
}
return scrollBy(dy, recycler, state); //1
}
5、LinearLayoutManager -> scrollBy
如下所示,注释1处表示又会调用fill方法,官方注释表示这是一个神奇的方法,回收与复用共同的入口;
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
、、、
final int consumed = mLayoutState.mScrollingOffset + fill(recycler, mLayoutState, state, false); //1
、、、
}
6、LinearLayoutManager -> fill
fill方法如下所示,注释1处的recycleByLayoutState是回收部分入口;注释2处的layoutChunk是复用部分入口;我们在第二部分分析recycleByLayoutState,第三部分分析layoutChunk;
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
、、、
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
、、、
recycleByLayoutState(recycler, layoutState); //1
}
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
、、、
layoutChunk(recycler, state, layoutState, layoutChunkResult); //2
、、、
}
}
二、回收部分
1、LinearLayoutManager -> recycleByLayoutState
注释1和注释2处分别表示从start和end回收,原理都是相同的,这里我们以注释1举例;
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
、、、
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace); //1
} else {
recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace); //2
}
}
2、LinearLayoutManager -> recycleViewsFromEnd
如注释1所示,又会调用recycleChildren进行回收;
private void recycleViewsFromEnd(RecyclerView.Recycler recycler, int scrollingOffset,
int noRecycleSpace) {
、、、
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); //1
return;
}
}
}
}
3、LinearLayoutManager -> recycleChildren
如下所示,注释1处表示又会调用removeAndRecycleViewAt方法;
private void recycleChildren(RecyclerView.Recycler recycler, int startIndex, int endIndex) {
、、、
if (endIndex > startIndex) {
for (int i = endIndex - 1; i >= startIndex; i--) {
removeAndRecycleViewAt(i, recycler); //1
}
}
、、、
}
4、LayoutManager -> removeAndRecycleViewAt
如下所示,又会调用recycler的recycleView方法;
public void removeAndRecycleViewAt(int index, @NonNull Recycler recycler) {
final View view = getChildAt(index);
removeViewAt(index);
recycler.recycleView(view); //1
}
5、Recycler -> recycleView
如下所示,注释1处表示又会调用recycleViewHolderInternal;
public void recycleView(@NonNull View view) {
、、、
recycleViewHolderInternal(holder); //1
}
6-1 RecyclerView # Recycler
在分析recycleViewHolderInternal方法之前,我们先简单分析一下RecyclerView的两个内部类Recycler类和RecyclerViewPool;
RecyclerView # Recycler
Recycler如下所示,注释1和注释2是一级缓存,其实就是泛型为ViewHolder的ArrayList;注释3就是二级缓存,注释4处的mViewCacheMax表示二级缓存的最大容量为2;注释5就是四级缓存RecycledViewPool;注释6就是第三级缓存,也就是自定义缓存;
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); //1
ArrayList<ViewHolder> mChangedScrap = null; //2
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); //3
private final List<ViewHolder>
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE; //4
RecycledViewPool mRecyclerPool; //5
private ViewCacheExtension mViewCacheExtension; //6
static final int DEFAULT_CACHE_SIZE = 2; //7
、、、
}
RecyclerView # RecycledViewPool
RecycledViewPool如下所示,先看注释2处的内部类ScrapData,注释3处的mScrapHeap就是缓存ViewHolder的ArrayList,注释4处表示最大容量为5,即可以缓存ViewHolder的个数为5;注释5处mScrap是一个SparseArray,其泛型为内部类ScrapData,这表示什么意思呢?就是说RecyclerViewPool对每种类型的ViewHolder最大缓存个数为5,然后可以缓存多种ViewHolder类型;
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5; //1
static class ScrapData { //2
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); //3
int mMaxScrap = DEFAULT_MAX_SCRAP; //4
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray<ScrapData> mScrap = new SparseArray<>(); //5
、、、
}
6-2 Recycler -> recycleViewHolderInternal
在recycleViewHolderInternal方法中才是执行真正的回收操作,主要是使用第二级缓存mCachedViews和第四级缓存RecyclerViewPool;注释1、2处表示mCachedViews容量满了之后会调用recycleCachedViewAt方法,然后将size–;注释3处表示往mCachedViews添加;注释4表示往缓存池添加;
void recycleViewHolderInternal(ViewHolder holder) {
、、、
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
// Retire oldest cached view
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0); //1
cachedViewSize--; //2
}
、、、
mCachedViews.add(targetCacheIndex, holder); //3
cached = true;
}
if (!cached) {
addViewHolderToRecycledViewPool(holder, true); //4
recycled = true;
}
}
}
7、Recycler -> recycleCachedViewAt
recycleCachedViewAt如下所示,注释1、2表示将mCachedViews的viewHolder移除,然后添加到RecycledViewPool;
void recycleCachedViewAt(int cachedViewIndex) {
if (DEBUG) {
Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
}
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
if (DEBUG) {
Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
}
addViewHolderToRecycledViewPool(viewHolder, true); //1
mCachedViews.remove(cachedViewIndex); //2
}
8、Recycler -> addViewHolderToRecycledViewPool
如下所示,注释1表示将ViewHolder添加到RecyclerViewPool;到此回收完毕;
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
、、、
getRecycledViewPool().putRecycledView(holder); //1
}
三、复用部分
1、LinearLayoutManager -> layoutChunk
接着第一部分的layoutChunk方法分析复用部分,如下所示,注释1表示又会调用LayoutState的next方法;
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler); //1
、、、
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
}
、、、
}
2、LayoutState -> next
next方法如下所示,注释1又调用了getViewForPosition方法;
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition); //1
mCurrentPosition += mItemDirection;
return view;
}
3、Recycler -> getViewForPosition
getViewForPosition方法如下所示,注释1又会调用到注释2;
public View getViewForPosition(int position) {
return getViewForPosition(position, false); //1
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; //2
}
4、Recycler -> tryGetViewHolderForPositionByDeadline
tryGetViewHolderForPositionByDeadline方法就是真正去寻找缓存进行复用了,这个方法比较长,这里只截取主要部分的代码,如下所示;
注释1处表示从mChangedScrap中寻找;
注释2和注释3表示从mAttachedScrap和mCachedView中去寻找;
注释4表示从自定义缓存中去寻找;
注释5表示从RecyclerViewPool中去寻找;
注释6表示如果上边所有的缓存都没有的话,就调用我们重写的Adapter的onCreateViewHolder去创建;
注释7处表示获取到ViewHolder之后再去调用Adapter的onBindViewHolder进行数据绑定,所以现在我们应该明白为什么要在我们重写的Adapter中重写这两个方法了;
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
、、、
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position); //1
fromScrapOrHiddenOrCache = holder != null;
}
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); //2
、、、
}
if (holder == null) {
、、、
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun); //3
}
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); //4
、、、
}
if (holder == null) { // fallback to pool
holder = getRecycledViewPool().getRecycledView(type); //5
、、、
}
if (holder == null) {
、、、
holder = mAdapter.createViewHolder(RecyclerView.this, type); //6
、、、
}
}
、、、
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()) {
、、、
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);//7
}
、、、
return holder;
}
四、onLayout入口
1、RecyclerView -> onLayout
前面三个部分我们是从onTouchEvent方法的MOVE分支中切入讨论的,这一部分我们从RecyclerView的布局方法onLayout方法切入分析;
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout(); //1
TraceCompat.endSection();
mFirstLayoutComplete = true;
}
2、RecyclerView -> dispatchLayout
如下所示,可以看到RecyclerView的dispatchLayout方法中主要分为三步;这里分析一下注释2的第二步;
void dispatchLayout() {
、、、
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1(); //1
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2(); //2
} 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(); //3
}
3、LinearLayoutManager -> onLayoutChildren
onLayoutChildren方法比较长,如下我们只看关键部分,注释1处又会调用removeAndRecycleViewAt;注释3、4处的fill方法正是我们前面重点讨论的方法;我们这里重点看一下注释2;
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
、、、
if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler); //1
return;
}
}
、、、
detachAndScrapAttachedViews(recycler); //2
、、、
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForStart;
fill(recycler, mLayoutState, state, false); //3
startOffset = mLayoutState.mOffset;
final int firstElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false); //4
}
、、、
}
4、LayoutManager -> detachAndScrapAttachedViews
如下所示,又会调用scrapOrRecycleView
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v); //1
}
}
5、LayoutManager -> scrapOrRecycleView
scrapOrRecycleView如下所示,注释1表示又会调用Recycler的scrapView
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
、、、
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view); //1
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
6、Recycler -> scrapView
scrapView如下所示,注释1和注释2处正是第一级缓存mAttachedScrap和mChangedScrap;至此我们RecyclerView的四级缓存大体流程也就全部分析完毕;
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
throw new IllegalArgumentException("Called scrap view with an invalid view."
+ " Invalid views cannot be reused from scrap, they should rebound from"
+ " recycler pool." + exceptionLabel());
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder); //1
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder); //2
}
}
以上是关于android进阶篇02RecyclerView回收复用机制源码解析的主要内容,如果未能解决你的问题,请参考以下文章
Android高级UI进阶RecyclerView 刷新列表数据的 notifyDataSetChanged() 为什么是昂贵的?
Android进阶之通用RecyclerView适配器打造方法
Android进阶之通用RecyclerView适配器打造方法