很多文章都是先从获取viewholder来分析他的缓存机制的,这里打算翻过来,先讲解一下都在什么情况下,哪些viewholder会被放入哪些缓存中。
看本文之前最好先对rv的整体布局流程有一个简单的了解,但是不知道也没关系啦。
不过viewholder有很多标记位用来表示状态,我们还是要先看一下。
标记位 | 注释 |
---|---|
FLAG_BOUND | 在viewholder的被onbind时会添加此标记 |
FLAG_UPDATE | 标记该ViewHolder显示的数据过时,需要重新onbind,但其position 和itemid还正确 |
FLAG_INVALID | 不合法,需要全部重新绑定 |
FLAG_REMOVED | 该viewholder表示的数据已经被移除了。该view还可能被用于执行移除动画 |
FLAG_NOT_RECYCLABLE | 禁止回收 |
FLAG_RETURNED_FROM_SCRAP | 此ViewHolder是从scrap中返回的,这意味着我们希望对此itemView进行addView调用。 从废料中返回时,ViewHolder会保留在scrap列表中,直到布局传递结束为止,如果未将其添加回RecyclerView,则由RecyclerView进行回收。 |
FLAG_IGNORE | 该viewholder全权由layoutmanager管理,我们不对其执行废弃或者回收或者移除操作直到layoutmanager被替换。 |
FLAG_TMP_DETACHED | 当View被detach时,我们设置此标志,以便在需要删除它或将其重新添加时可以采取正确的措施。 |
FLAG_ADAPTER_POSITION_UNKNOWN | 此时无法确定viewholder的位置,直到被重新绑定一个position |
其实还有一些标记位,不过暂时看不到,就先不了解了。其中有一些比如remove/update等的标记位,是在dispatchlayoutstep1中因为数据集更新而被添加上的,会在接下来的布局中对其进行相应的处理。具体的逻辑可以参考我对Rv数据更新的文章,这里就不赘述了。
回收
下面进入正题,在rv中有那么几层缓存,不过到底分成几层呢我觉得也不好区分,下面先一一列出。
mChangedScrap/mAttachedScrap
我们把这两个放在一起说。以使用linearlayoutmanager为例子,通过对他们俩的add方法跟踪,发现了两条调用链。其中一条涉及到从缓存中取数据的方法,我们暂时先不看。另外一条如下:
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
···
detachAndScrapAttachedViews(recycler);
···
}
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);
}
}
当linearlayoutmanager开始布局child时,如果新的数据集合不为空,则会先将之前rx上所有的child回收。
这里我们再看一下scrapOnRecyclerView方法
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
···
if (viewHolder.isInvalid() && !viewHolder.isRemoved()//如果被标记了非法但没有remove标记且没有stableid,根据之前看到的标记位,说明这种view需要重新绑定才行,所以不能直接通过position重用。如果也没有stableId的话就只能分配给下一层缓存了
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);//移除掉此view
recycler.recycleViewHolderInternal(viewHolder);//通过recycleViewHolderInternal回收,这就涉及另一种回收了,暂时不看
} else {//可以重用的情况
detachViewAt(index);//暂时从child数组中移除,不用刷新页面
recycler.scrapView(view);//将此view回收
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);//通知Viewinfostore 这个涉及动画,先不管
}
}
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
···//如果是被移除的或者没有update标记或者可在执行动画是重用
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);//放入mAttachedScrap缓存中
} else {//否则说明是被标记了更新的view
if (mChangedScrap == null) {
mChangedScrap = new ArrayList();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);//放入mChangedScrap缓存中
}
}
到这里,这一层缓存的回收就结束了。
HiddenChild
这一层这一层不能算是缓存。不过在获取viewholder的时候,也会来这里面查找,所以我就放了过来。
依然先追踪一下调用链。
首先在rv的addAnimatingView方法中
private void addAnimatingView(ViewHolder viewHolder) {
final View view = viewHolder.itemView;
final boolean alreadyParented = view.getParent() == this;
mRecycler.unscrapView(getChildViewHolder(view));
if (viewHolder.isTmpDetached()) {
// re-attach
mChildHelper.attachViewToParent(view, -1, view.getLayoutParams(), true);
} else if (!alreadyParented) {
mChildHelper.addView(view, true);//将view添加乳hidden
} else {
mChildHelper.hide(view);
}
}
这里可以看出来,hidden是和动画相关的。有些时候,数据集已经发生了改变,某一条数据已经不存在了,不过因为动画的原因,这个view还在需要在屏幕上显示。此时他就会处于hidden的状态。rv使用childHelper来封装对viewgroup中view的添加移除等操作,来保证不会因为上面的原因导致发生误差。具体可以看我rv之childHelper,这里就不赘述了。
mCachedViews
当viewholder不满足mChangedScrap/mAttachedScrap的条件,或者在其他情况下,一般都是通过调用如下方法来回收view的,比如在滚动布局时,移出屏幕的view最终就是通过此方法回收。
void recycleViewHolderInternal(ViewHolder holder) {
···
final boolean transientStatePreventsRecycling = holder
.doesTransientStatePreventRecycling();
final boolean forceRecycle = mAdapter != null
&& transientStatePreventsRecycling
&& mAdapter.onFailedToRecycleView(holder);
boolean cached = false;
boolean recycled = false;
···
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0//默认为2
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {//如果view (合法|没有被remove|没有被更新|adapterPosition有效)(比如因为滚动被移出屏幕的viewholder)就会被放入cached中缓存
// Retire oldest cached view
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {//如果没有空间
recycleCachedViewAt(0);//将队列头出队并放入RecycledViewPool
cachedViewSize--;
}
int targetCacheIndex = cachedViewSize;
if (ALLOW_THREAD_GAP_WORK
&& cachedViewSize > 0
&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {//设计到gapworker预读取 如果缓存的不是预读取的数据,则要把这个数据插到预读取的数据之前,以防缓存满的时候预读取的数据被先移除
// 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) {//缓存失败,通过RecycledViewPool去存储
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
···
mViewInfoStore.removeViewHolder(holder);//被回收了,从中移除
if (!cached && !recycled && transientStatePreventsRecycling) {
holder.mOwnerRecyclerView = null;
}
}
这里可以看到,预读取的数据就会被放到cached中。
最后
RecycledViewPool
条件不符合上面几层缓存或者被从cache中挤出来的viewholder就会来到RecycledViewPool中了。
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
clearNestedRecyclerViewIfNotNested(holder);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE)) {
holder.setFlags(0, ViewHolder.FLAG_SET_A11Y_ITEM_DELEGATE);//清除FLAG_SET_A11Y_ITEM_DELEGATE
ViewCompat.setAccessibilityDelegate(holder.itemView, null);//移除辅助功能代理
}
if (dispatchRecycled) {
dispatchViewRecycled(holder);//通知被回收了
}
holder.mOwnerRecyclerView = null;//
getRecycledViewPool().putRecycledView(holder);//放入RecycledViewPool
}
这里的逻辑比较简单,我们主要分析一下RecycledViewPool的结构。
RecycledViewPool中持有一个以viewtype为key的SparseArray,其value的类型为
static class ScrapData {
final ArrayList mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;//最大长度 默认为5
long mCreateRunningAverageNs = 0;//平均创建时间
long mBindRunningAverageNs = 0;//平均绑定时间
}
类似hashmap的结构。所以说默认每个类型最大能存放五个。
另外,这个RecycledViewPool提供了几个比较有意思的方法
void factorInCreateTime(int viewType, long createTimeNs) {
ScrapData scrapData = getScrapDataForType(viewType);
scrapData.mCreateRunningAverageNs = runningAverage(
scrapData.mCreateRunningAverageNs, createTimeNs);
}
void factorInBindTime(int viewType, long bindTimeNs) {
ScrapData scrapData = getScrapDataForType(viewType);
scrapData.mBindRunningAverageNs = runningAverage(
scrapData.mBindRunningAverageNs, bindTimeNs);
}
boolean willCreateInTime(int viewType, long approxCurrentNs, long deadlineNs) {
long expectedDurationNs = getScrapDataForType(viewType).mCreateRunningAverageNs;
return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs);
}
boolean willBindInTime(int viewType, long approxCurrentNs, long deadlineNs) {
long expectedDurationNs = getScrapDataForType(viewType).mBindRunningAverageNs;
return expectedDurationNs == 0 || (approxCurrentNs + expectedDurationNs < deadlineNs);
}
long runningAverage(long oldAverage, long newValue) {
if (oldAverage == 0) {
return newValue;
}
return (oldAverage / 4 * 3) + (newValue / 4);
}
上面四个方法,用于更新平均创建/绑定该类型holder的耗时,以及用平均时间作为基准判断在指定时间内是否能完成对该holder的创建/绑定。具体有什么用呢?暂时我们先放一放。
另外,可以通过多个rv公用一个RecycledViewPool来实现viewholder的回收共用。
ViewCacheExtension
这是一个开发者可以自行扩展的缓存,一般情况下用不到。暂时先不看。
到此为止,rv的三或者四级缓存的回收操作我们就全部分析完了。
下面我们就看一下取出数据的逻辑。
取出
linearlayout通过next方法获取一个viewholder并将它添加到rv上,具体细节可以看我对LinearLayoutManager的分析,这里不赘述了。
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();//在执行动画相关时不为null,我们暂时不考虑这种情况
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
这里的mScrapList就是持有的recycler中对mAttachedScrap的包装。
private final List
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
通过Collections.unmodifiableList包装成只可读,不可写的list。
通过getViewForPosition方法,最终调用了tryGetViewHolderForPositionByDeadline方法来获取viewholder。下面分析一下这个重量级的方法。
tryGetViewHolderForPositionByDeadline
因为这个方法比较长,会分段看一下。
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
···
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
···
}
首先,先通过getChangedScrapViewForPosition查找。
getChangedScrapViewForPosition
ViewHolder getChangedScrapViewForPosition(int position) {
// If pre-layout, check the changed scrap for an exact match.
final int changedScrapSize;
if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
return null;
}
// find by position
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
// find by id
if (mAdapter.hasStableIds()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
final long id = mAdapter.getItemId(offsetPosition);
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
}
}
return null;
}
继续tryGetViewHolderForPositionByDeadline。
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
···
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
···
}
}
···
}
如果没有在mChangedScrap中找到缓存,则通过getScrapOrHiddenOrCachedHolderForPosition继续查找。
getScrapOrHiddenOrCachedHolderForPosition
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
···
return null;
}
此方法我们分成三段看。首先从mAttachedScrap中寻找。有几个判断条件:
- !holder.wasReturnedFromScrap() 我们之前看到这个标记位,设置了此标记说明别人此时已经抢先获取了此viewholder缓存了。
- holder.getLayoutPosition() == position 要查找的position。
- !holder.isInvalid() 设置了此标记为说明不合法了,需要全部重新绑定包括position,就没意义了。
- mState.mInPreLayout || !holder.isRemoved() 没有被移除或者处于预布局状态。
符合上述条件,则设置FLAG_RETURNED_FROM_SCRAP,返回。
当mAttachedScrap中没有找到时:
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
···
if (!dryRun) {
View view = mChildHelper.findHiddenNonRemovedView(position);
if (view != null) {
final ViewHolder vh = getChildViewHolderInt(view);
mChildHelper.unhide(view);
int layoutIndex = mChildHelper.indexOfChild(view);
if (layoutIndex == RecyclerView.NO_POSITION) {
throw new IllegalStateException("layout index should not be -1 after "
+ "unhiding a view:" + vh + exceptionLabel());
}
mChildHelper.detachViewFromParent(layoutIndex);
scrapView(view);
vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
| ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
return vh;
}
}
···
}
下一步就是去hidden中查找了。如果找到了,把它放入scrap缓存中并添加flag。这里一般也是用于动画逻辑。
如果在hidden中也找不到呢?我们继续看。根据方法名也能猜出来,下一步是去cache中查找了。
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
···
// Search in our first-level recycled view cache.
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
// invalid view holders may be in cache if adapter has stable ids as they can be
// retrieved via getScrapOrCachedViewForId
if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
if (!dryRun) {
mCachedViews.remove(i);
}
if (DEBUG) {
Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
+ ") found match in cache: " + holder);
}
return holder;
}
}
return null;
}
遍历cacheView查找对应的,然后返回。
到这里getScrapOrHiddenOrCachedHolderForPosition方法就结束了,我们继续跟踪。
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
···
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {//看到了这里
if (!dryRun) {
holder.addFlags(ViewHolder.FLAG_INVALID);
if (holder.isScrap()) {//
removeDetachedView(holder.itemView, false);
holder.unScrap();//从scrap中移除
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();//清理标记位
}
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
···
}
从这三级缓存中查找到holder之后,首先要验证这个holder的合法性,也就是是不是可用。
boolean validateViewHolderForOffsetPosition(ViewHolder holder) {
// if it is a removed holder, nothing to verify since we cannot ask adapter anymore
// if it is not removed, verify the type and id.
if (holder.isRemoved()) {
if (DEBUG && !mState.isPreLayout()) {
throw new IllegalStateException("should not receive a removed view unless it"
+ " is pre layout" + exceptionLabel());
}
return mState.isPreLayout();
}
if (holder.mPosition < 0 || holder.mPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid view holder "
+ "adapter position" + holder + exceptionLabel());
}
if (!mState.isPreLayout()) {
// don't check type if it is pre-layout.
final int type = mAdapter.getItemViewType(holder.mPosition);
if (type != holder.getItemViewType()) {
return false;
}
}
if (mAdapter.hasStableIds()) {
return holder.getItemId() == mAdapter.getItemId(holder.mPosition);
}
return true;
}
如果是被remove掉的viewholder,则无需验证,直接判断当前绘制阶段就可以了。至于为什么呢,暂时先不管。
如果position不合法,抛出异常。
如果当前不是preLayout阶段,则判断itemViewType。
如果有stableId,判断ItemId。
经过合法性验证,如果合法,则直接设置fromScrapOrHiddenOrCache true。否则这个viewholder就不能用,我们给她添加不合法的标签,然后回收掉。
下面我们继续看tryGetViewHolderForPositionByDeadline
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
···
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);//找到执行改变之后的position
···
final int type = mAdapter.getItemViewType(offsetPosition);//获取目标type
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {//如果存在stableid,再次根据stableid 去缓存中查找
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
if (holder == null && mViewCacheExtension != null) {//去ViewCacheExtension中查找
// 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());
}
}
}
if (holder == null) { // fallback to pool
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ position + ") fetching from shared pool");
}
holder = getRecycledViewPool().getRecycledView(type);//去RecyclerViewPool中查找
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)) {//预读取的情况下,剩下的时间不够加载的了,为了不占用主线程时间
return null;//直接返回null
}
holder = mAdapter.createViewHolder(RecyclerView.this, type);//createViewHolder
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");
}
}
}
···
}
上面的代码 首先获取目标viewtype
- 如果有stableId,先通过stableId进行查找。
- mViewCacheExtension中查找。
- RecycledViewPool中查找
- 都找不到,通过createViewHolder进行创建。
到目前为止,已经结束了获取或者创建holder的操作。继续看下去
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
···
if (fromScrapOrHiddenOrCache && !mState.isPreLayout() && holder
.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST)) {
holder.setFlags(0, ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);//清理此标志位
if (mState.mRunSimpleAnimations) {//动画相关,我们暂时不看
···
}
}
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()) {
···
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);//再次绑定viewholder
}
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;//将view和viewholder绑定
rvLayoutParams.mPendingInvalidate = fromScrapOrHiddenOrCache && bound;//是否需要在将此view add到parent上时刷新。如果数据绑定成功且此holder是通过ScrapOrHiddenOrCache回收来的,则需要刷新
return holder;
}
到此为止,获取viewholder的整个流程我们就看完了。