这是RecyclerView
缓存机制系列文章的第一篇,系列文章的目录以下:java
若是想直接看结论能够移步到第四篇末尾(你会后悔的,过程更加精彩)。缓存
这一篇试着从已知的知识出发在源码中寻觅未知的“RecyclerView复用机制”。bash
(ps: 下文中的 粗斜体字 表示引导源码阅读的心里戏)app
触发复用的众多时机中必然包含下面这种:“当移出屏幕的表项从新回到界面”。表项本质上是一个View,屏幕上的表项必然须要依附于一棵View树,即必然有一个父容器调用了addView()
。而 RecyclerView
继承自 ViewGroup
,遂以RecyclerView.addView()
为切入点向上搜寻复用的代码。less
在RecyclerView.java
中全局搜索“addView”,发现RecyclerView()
并无对addView()
函数重载,但找到一处addView()
的调用:ide
//RecyclerView是ViewGroup的子类
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
...
private void initChildrenHelper() {
mChildHelper = new ChildHelper(new ChildHelper.Callback() {
...
@Override
public void addView(View child, int index) {
if (VERBOSE_TRACING) {
TraceCompat.beginSection("RV addView");
}
//直接调用ViewGroup.addView()
RecyclerView.this.addView(child, index);
if (VERBOSE_TRACING) {
TraceCompat.endSection();
}
dispatchChildAttached(child);
}
}
}
...
}
复制代码
以ChildHelper.Callback.addView()
为起点沿着调用链继续向上搜寻,经历ChildHelper.addView()
---LayoutManager.addViewInt()
---LayoutManager.addView()
最终到达LayoutManager.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;
}
LayoutParams params = (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);
}
}
...
}
复制代码
addView(view)
中传入的view
是函数layoutState.next()
的返回值。猜想该函数是用来得到下一个表项的。表项不止一个,应该有一个循环不断的得到下一个表项才对。 沿着刚才的调用链继续往上搜寻,就会发现:的确有一个循环!布局
public class LinearLayoutManager extends RecyclerView.LayoutManager implements ItemTouchHelper.ViewDropHandler, RecyclerView.SmoothScroller.ScrollVectorProvider {
...
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
//recyclerview 剩余空间
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
//不断填充,直到空间消耗完毕
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
if (VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
//填充一个表项
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
}
...
}
}
复制代码
而fill()
是在onLayoutChildren()
中被调用:post
/**
* Lay out all relevant child views from the given adapter.
* 布局全部给定adapter中相关孩子视图
* 注释太长了,省略了不相关信息
* @param recycler Recycler to use for fetching potentially cached views for a
* position
* @param state Transient state of RecyclerView
*/
public void onLayoutChildren(Recycler recycler, State state) {
Log.e(TAG, "You must override onLayoutChildren(Recycler recycler, State state) ");
}
复制代码
看完注释,感受前面猜想应该是正确的。onLayoutChildren()
是用来布局RecyclerView
中全部的表项的。回头去看一下layoutState.next()
,表项复用逻辑应该就在其中。性能
/**
* Helper class that keeps temporary state while {LayoutManager} is filling out the empty
* space.
*/
static class LayoutState {
/**
* Gets the view for the next element that we should layout.
* 得到下一个元素的视图用于布局
* Also updates current item index to the next item, based on {@link #mItemDirection}
*
* @return The next element that we should layout.
*/
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
//调用了Recycler.getViewForPosition()
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
}
复制代码
最终调用了Recycler.getViewForPosition()
,Recycler是回收器的意思,感受离想要找的“复用”逻辑愈来愈近了。 Recycler
究竟是作什么用的? :
/**
* A Recycler is responsible for managing scrapped or detached item views for reuse.
* Recycler负责管理scrapped和detached表项的复用
* <p>A "scrapped" view is a view that is still attached to its parent RecyclerView but
* that has been marked for removal or reuse.</p>
*/
public final class Recycler {
...
}
复制代码
终于找到你~~ ,Recycler
用于表项的复用!沿着Recycler.getViewForPosition()
的调用链继续向下搜寻,找到了一个关键函数(函数太长了,为了防止头晕,只列出了关键节点):
/**
* Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
* cache, the RecycledViewPool, or creating it directly.
* 尝试得到指定位置的ViewHolder,要么从scrap,cache,RecycledViewPool中获取,要么直接从新建立
* @return ViewHolder for requested position
*/
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
boolean fromScrapOrHiddenOrCache = false;
ViewHolder holder = null;
//0 从changed scrap集合中获取ViewHolder
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
//1. 经过position从attach scrap或一级回收缓存中获取ViewHolder
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
...
}
if (holder == null) {
...
final int type = mAdapter.getItemViewType(offsetPosition);
//2. 经过id在attach scrap集合和一级回收缓存中查找viewHolder
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
...
}
//3. 从自定义缓存中获取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);
...
}
//4.从缓存池中拿ViewHolder
if (holder == null) { // fallback to pool
...
holder = getRecycledViewPool().getRecycledView(type);
...
}
//全部缓存都没有命中,只能建立ViewHolder
if (holder == null) {
...
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
}
}
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
}
//只有invalid的viewHolder才能绑定视图数据
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);
//得到ViewHolder后,绑定视图数据
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
...
return holder;
}
复制代码
ViewHolder
,难道回收和复用的是ViewHolder
? 函数开头声明了局部变量ViewHolder holder = null;
最终返回的也是这个局部变量,而且有4处holder == null
的判断,这样的代码结构是否是有点像缓存?每次判空意味着上一级缓存未命中并继续尝试新的获取方法?缓存是否是有不止一种存储形式? 让咱们一次一次地看:ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
...
}
复制代码
只有在mState.isPreLayout()
为true
时才会作此次尝试,这应该是一种特殊状况,先忽略。
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
//下面一段代码蕴含着一个线索,买个伏笔,先把他略去
...
}
...
}
复制代码
getScrapOrHiddenOrCachedHolderForPosition()
得到ViewHolder
。ViewHolder
的调用链继续往下://省略非关键代码
/**
* Returns a view for the position either from attach scrap, hidden children, or cache.
* 从attach scrap,hidden children或者cache中得到指定位置上的一个ViewHolder
*/
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
//1.在attached scrap中搜索ViewHolder
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;
}
}
//2.从移除屏幕的视图中搜索ViewHolder,找到了以后将他存入scrap回收集合中
if (!dryRun) {
View view = mChildHelper.findHiddenNonRemovedView(position);
if (view != null) {
final ViewHolder vh = getChildViewHolderInt(view);
mChildHelper.unhide(view);
int layoutIndex = mChildHelper.indexOfChild(view);
...
mChildHelper.detachViewFromParent(layoutIndex);
scrapView(view);
vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
| ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
return vh;
}
}
// Search in our first-level recycled view cache.
//3.在缓存中搜索ViewHolder
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
//若找到ViewHolder,还须要对ViewHolder的索引进行匹配判断
if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
...
return holder;
}
}
return null;
}
复制代码
依次从三个地方搜索ViewHolder
:1. mAttachedScrap
2. 隐藏表项 3. mCachedViews
,找到当即返回。 其中mAttachedScrap
和mCachedViews
做为Recycler
的成员变量,用来存储一组ViewHolder
:
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
...
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
...
RecycledViewPool mRecyclerPool;
}
复制代码
ViewHolder
,且以ArrayList
为结构存储在Recycler
对象中。RecycledViewPool mRecyclerPool;
看着也像是回收容器,那待会是否是也会到这里拿 ViewHolder
?mCachedViews
中获取ViewHolder
对象后,还须要对其索引进行判断,这就意味着 mCachedViews
中缓存的ViewHolder
只能复用于指定位置 ,打个比方:手指向上滑动,列表向下滚动,第2个表项移出屏幕,第4个表项移入屏幕,此时再滑回去,第2个表项再次出现,这个过程当中第4个表项不能复用被回收的第2个表项的ViewHolder
,由于他们的位置不一样,而再次进入屏幕的第2个表项就能够成功复用。 待会能够对比一下其余复用是否也须要索引判断ViewHolder
的代码补全:ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
//下面一段代码蕴含这一个线索,买个伏笔,先把他略去
if (holder != null) {
//检验ViewHolder有效性
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can not be used
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
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
...
}
复制代码
若是成功得到ViewHolder
则检验其有效性,若检验失败则将其回收。好不容易获取了ViewHoler
对象,一言不合就把他回收?难道对全部复用的 ViewHolder
都有这么严格的检验吗? 暂时没法回答这些疑问,仍是先把复用逻辑看完吧:
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
//只有当Adapter设置了id,才会进行此次查找
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
...
}
复制代码
这一次尝试调用的函数名(“byId”)和上一次(“byPosition”)只是后缀不同。上一次是经过表项位置,这一次是经过表项id。内部实现也几乎同样,判断的依据从表项位置变成表项id。为表项设置id属于特殊状况,先忽略。
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
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) {
//得到view对应的ViewHolder
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());
}
}
}
...
}
复制代码
通过从mAttachedScrap
和mCachedViews
获取ViewHolder
未果后,继续尝试经过ViewCacheExtension
获取:
/**
* ViewCacheExtension is a helper class to provide an additional layer of view caching that can
* be controlled by the developer.
* ViewCacheExtension提供了额外的表项缓存层,用户帮助开发者本身控制表项缓存
* <p>
* When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and
* first level cache to find a matching View. If it cannot find a suitable View, Recycler will
* call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking
* {@link RecycledViewPool}.
* 当Recycler从attached scrap和first level cache中未能找到匹配的表项时,它会在去RecycledViewPool中查找以前,先尝试从自定义缓存中查找
* <p>
*/
public abstract static class ViewCacheExtension {
/**
* Returns a View that can be binded to the given Adapter position.
* <p>
* This method should <b>not</b> create a new View. Instead, it is expected to return
* an already created View that can be re-used for the given type and position.
* If the View is marked as ignored, it should first call
* {@link LayoutManager#stopIgnoringView(View)} before returning the View.
* <p>
* RecyclerView will re-bind the returned View to the position if necessary.
*/
public abstract View getViewForPositionAndType(Recycler recycler, int position, int type);
}
复制代码
注释揭露了不少信息:ViewCacheExtension用于开发者自定义表项缓存,且这层缓存的访问顺序位于mAttachedScrap
和mCachedViews
以后,RecycledViewPool
以前。这和Recycler. tryGetViewHolderForPositionByDeadline()
中的代码逻辑一致,那接下来的第五次尝试,应该是从 RecycledViewPool
中获取 ViewHolder
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
if (holder == null) {
...
//从回收池中获取ViewHolder对象
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
...
}
复制代码
前四次尝试都未果,最后从RecycledViewPool
中获取ViewHolder
。稍等片刻!相对于从mAttachedScrap
和 mCachedViews
中获取 ViewHolder
,此处并无严格的检验逻辑。为啥要区别对待不一样的缓存? 大大的问号悬在头顶,但如今暂时没法解答,仍是接着看RecycledViewPool
的结构吧~
public final class Recycler {
...
RecycledViewPool mRecyclerPool;
//得到RecycledViewPool实例
RecycledViewPool getRecycledViewPool() {
if (mRecyclerPool == null) {
mRecyclerPool = new RecycledViewPool();
}
return mRecyclerPool;
}
...
}
public static class RecycledViewPool {
...
//从回收池中获取ViewHolder对象
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;
}
...
}
复制代码
函数中只要访问了类成员变量,它的复杂度就提升了,由于类成员变量的做用于超出了函数体,使得函数就和类中其余函数耦合,因此不得不进行阅读更多以帮助理解该函数:
public static class RecycledViewPool {
//同类ViewHolder缓存个数上限
private static final int DEFAULT_MAX_SCRAP = 5;
/**
* Tracks both pooled holders, as well as create/bind timing metadata for the given type.
* 回收池中存放单个类型ViewHolder的容器
*/
static class ScrapData {
//同类ViewHolder存储在ArrayList中
ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
//每种类型的ViewHolder最多存5个
int mMaxScrap = DEFAULT_MAX_SCRAP;
}
//回收池中存放全部类型ViewHolder的容器
SparseArray<ScrapData> mScrap = new SparseArray<>();
...
//ViewHolder入池 按viewType分类入池,一个类型的ViewType存放在一个ScrapData中
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
//若是超限了,则放弃入池
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
if (DEBUG && scrapHeap.contains(scrap)) {
throw new IllegalArgumentException("this scrap item already exists");
}
scrap.resetInternal();
//回收时,ViewHolder从列表尾部插入
scrapHeap.add(scrap);
}
//从回收池中获取ViewHolder对象
public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
//复用时,从列表尾部获取ViewHolder(优先复用刚入池的ViewHoler)
return scrapHeap.remove(scrapHeap.size() - 1);
}
return null;
}
}
复制代码
RecycledViewPool
中最关键的一个成员变量和两个函数。至此能够得出结论:RecycledViewPool
中的ViewHolder
存储在SparseArray
中,而且按viewType
分类存储(便是Adapter.getItemViewType()的返回值),同一类型的ViewHolder
存放在ArrayList
中,且默认最多存储5个。mCachedViews
,从mRecyclerPool
中成功获取ViewHolder
对象后并无作合法性和表项位置校验,只检验viewType
是否一致。因此 从mRecyclerPool
中取出的ViewHolder
只能复用于相同viewType
的表项。ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {
...
//全部缓存都没有命中,只能建立ViewHolder
if (holder == null) {
...
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
}
...
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);
//为表项绑定数据
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
}
复制代码
ViewHolder
,只能从新建立并绑定数据。沿着调用链往下,就会找到熟悉的onCreateViewHolder()
和onBindViewHolder()
。缓存有优先级一说,在使用图片二级缓存(内存+磁盘)时,会先尝试去优先级高的内存中获取,若未命中再去磁盘中获取。优先级越高意味着性能越好。RecyclerView
的缓存机制中是否也能套用“缓存优先级”这一逻辑?
虽然为了获取ViewHolder
作了5次尝试(共从6个地方获取),先排除3种特殊状况,即从mChangedScrap
获取、经过id获取、从自定义缓存获取,正常流程中只剩下3种获取方式,优先级从高到低依次是:
mAttachedScrap
获取mCachedViews
获取mRecyclerPool
获取这样的缓存优先级是否是意味着,对应的复用性能也是从高到低?(复用性能越好意味着所作的昂贵操做越少)
ViewHodler
并从新绑定数据ViewHolder
但从新绑定数据ViewHolder
且不从新绑定数据毫无疑问,全部缓存都未命中的状况下会发生最坏状况。剩下的两种状况应该由3种获取方式来分摊,猜想优先级最低的 mRecyclerPool
方式应该命中次好状况,而优先级最高的 mAttachedScrap
应该命中最好状况,去源码中验证一下:
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
//1.从attached scrap回收集合中
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
//只有当holder是有效时才返回
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position
&& !holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved())) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
}
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
if (holder == null) {
...
//从回收池中获取ViewHolder对象
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
//重置ViewHolder
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
...
//若是表项没有绑定过数据 或 表项须要更新 或 表项无效 且表项没有被移除时绑定表项数据
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);
//为表项绑定数据
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
...
}
public abstract static class ViewHolder {
/**
* This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType
* are all valid.
* 绑定标志位
*/
static final int FLAG_BOUND = 1 << 0;
/**
* This ViewHolder’s data is invalid. The identity implied by mPosition and mItemId
* are not to be trusted and may no longer match the item view type.
* This ViewHolder must be fully rebound to different data.
* 无效标志位
*/
static final int FLAG_INVALID = 1 << 2;
//判断ViewHolder是否无效
boolean isInvalid() {
//将当前ViewHolder对象的flag和无效标志位作位与操做
return (mFlags & FLAG_INVALID) != 0;
}
//判断ViewHolder是否被绑定
boolean isBound() {
//将当前ViewHolder对象的flag和绑定标志位作位与操做
return (mFlags & FLAG_BOUND) != 0;
}
/**
* 将ViewHolder重置
*/
void resetInternal() {
//将ViewHolder的flag置0
mFlags = 0;
mPosition = NO_POSITION;
mOldPosition = NO_POSITION;
mItemId = NO_ID;
mPreLayoutPosition = NO_POSITION;
mIsRecyclableCount = 0;
mShadowedHolder = null;
mShadowingHolder = null;
clearPayload();
mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET;
clearNestedRecyclerViewIfNotNested(this);
}
}
复制代码
温故知新,回看 mRecyclerPool
复用逻辑时,发如今成功得到ViewHolder
对象后,当即对其重置(将flag置0)。这样就知足了绑定数据的判断条件(由于0和非0位与以后必然为0)。 一样的,在才mAttachedScrap
中获取ViewHolder
时,只有当其是有效的才会返回。因此猜想成立:从mRecyclerPool
中复用的ViewHolder
须要从新绑定数据,从mAttachedScrap
中复用的ViewHolder
不要从新出建立也不须要从新绑定数据。
RecyclerView
中,并非每次绘制表项,都会从新建立ViewHolder
对象,也不是每次都会从新绑定ViewHolder
数据。RecyclerView
经过Recycler
得到下一个待绘制表项。Recycler
有4个层次用于缓存ViewHolder
对象,优先级从高到底依次为ArrayList<ViewHolder> mAttachedScrap
、ArrayList<ViewHolder> mCachedViews
、ViewCacheExtension mViewCacheExtension
、RecycledViewPool mRecyclerPool
。若是四层缓存都未命中,则从新建立并绑定ViewHolder
对象RecycledViewPool
对ViewHolder
按viewType
分类存储(经过SparseArray
),同类ViewHolder
存储在默认大小为5的ArrayList
中mRecyclerPool
中复用的ViewHolder
须要从新绑定数据,从mAttachedScrap
中复用的ViewHolder
不须要从新建立也不须要从新绑定数据。mRecyclerPool
中复用的ViewHolder
,只能复用于viewType
相同的表项,从mCachedViews
中复用的ViewHolder
,只能复用于指定位置的表项。这篇文章粗略的回答了关于“复用”的4个问题,即“复用什么?”、“从哪里得到复用?”、“何时复用?”、“复用优先级”。读到这里,可能会有不少疑问:
scrap view
是什么?changed scrap view
和attached scrap view
有什么区别?ViewHolder
是在何时被缓存的?分析完“复用”,后续文章会进一步分析“回收”,但愿到时候这些问题都能迎刃而解。