RecyclerView的复用机制,薪资翻倍

         final View view = mViewCacheExtension
                .getViewForPositionAndType(this, position, type);  //你返回的View要是RecyclerView.LayoutParams属性的
        if (view != null) {
            holder = getChildViewHolder(view);  //把它包装成一个ViewHolder
            ...
        }
    }
    if (holder == null) { // 从 RecyclerViewPool中获取
        holder = getRecycledViewPool().getRecycledView(type);
        ...
    }
    if (holder == null) { 
        ...
        //实在没有就会创建
        holder = mAdapter.createViewHolder(RecyclerView.this, type);
        ...
    }
}
...
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) { //动画时不会想去调用 onBindData
    ...
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
    ...
    final int offsetPosition = mAdapterHelper.findPositionOffset(position);
    bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);  //调用 bindData 方法
}

final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
final LayoutParams rvLayoutParams;
...调整LayoutParams
return holder;

}


即大致步骤是:

1.  如果执行了`RecyclerView`动画的话,尝试`根据position`从`mChangedScrap集合`中寻找一个`ViewHolder`
2.  尝试`根据position`从`scrap集合`、`hide的view集合`、`mCacheViews(一级缓存)`中寻找一个`ViewHolder`
3.  根据`LayoutManager`的`position`更新到对应的`Adapter`的`position`。 (这两个`position`在大部分情况下都是相等的,不过在`子view删除或移动`时可能产生不对应的情况)
4.  根据`Adapter position`,调用`Adapter.getItemViewType()`来获取`ViewType`
5.  根据`stable id(用来表示ViewHolder的唯一,即使位置变化了)`从`scrap集合`和`mCacheViews(一级缓存)`中寻找一个`ViewHolder`
6.  根据`position和viewType`尝试从用户自定义的`mViewCacheExtension`中获取一个`ViewHolder`
7.  根据`ViewType`尝试从`RecyclerViewPool`中获取一个`ViewHolder`
8.  调用`mAdapter.createViewHolder()`来创建一个`ViewHolder`
9.  如果需要的话调用`mAdapter.bindViewHolder`来设置`ViewHolder`。
10.  调整`ViewHolder.itemview`的布局参数为`Recycler.LayoutPrams`,并返回Holder

虽然步骤很多,逻辑还是很简单的,即从几个缓存集合中获取`ViewHolder`,如果实在没有就创建。但比较疑惑的可能就是上述`ViewHolder缓存集合`中什么时候会保存`ViewHolder`。接下来分几个`RecyclerView`的具体情形,来一点一点弄明白这些`ViewHolder缓存集合`的问题。

情形一 : 由无到有
----------

即一开始`RecyclerView`中没有任何数据,添加数据源后`adapter.notifyXXX`。状态变化如下图:

![](https://user-gold-cdn.xitu.io/2018/12/14/167abd5f746c2db9?imageView2/0/w/1280/h/960/ignore-error/1)

很明显在这种情形下`Recycler`中是不会存在任何可复用的`ViewHolder`。所以所有的`ViewHolder`都是新创建的。即会调用`Adapter.createViewHolder()和Adapter.bindViewHolder()`。那这些创建的`ViewHolder`会缓存起来吗?

这时候新创建的这些`ViewHolder`是不会被缓存起来的。 即在这种情形下: _Recycler只会通过Adapter创建ViewHolder,并且不会缓存这些新创建的ViewHolder_

情形二 : 在原有数据的情况下进行整体刷新
---------------------

就是下面这种状态:

![](https://user-gold-cdn.xitu.io/2018/12/14/167abd615618dfd5?imageView2/0/w/1280/h/960/ignore-error/1)

其实就是相当于用户在feed中做了下拉刷新。实现中的伪代码如下:

dataSource.clear()
dataSource.addAll(newList)
adapter.notifyDatasetChanged()


在这种情形下猜想`Recycler`肯定复用了老的卡片(卡片的类型不变),那么问题是 : 在用户刷新时`旧ViewHolder`保存在哪里? 如何调用`旧ViewHolder`的`Adapter.bindViewHolder()`来重新设置数据的?

其实在上一篇文章`Recycler刷新机制`中,`LinearLayoutManager`在确定好`布局锚点View`之后就会把当前`attach`在`RecyclerView`上的`子View`全部设置为`scrap状态`:

void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {

onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection); // RecyclerView指定锚点,要准备正式布局了
detachAndScrapAttachedViews(recycler); // 在开始布局时,把所有的View都设置为 scrap 状态

}


什么是scrap状态呢? 在前面的文章其实已经解释过: ViewHolder被标记为`FLAG_TMP_DETACHED`状态,并且其`itemview`的`parent`被设置为`null`。

`detachAndScrapAttachedViews`就是把所有的view保存到`Recycler`的`mAttachedScrap`集合中:

public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
for (int i = getChildCount() - 1; i >= 0; i–) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
…删去了一些判断逻辑
detachViewAt(index); //设置RecyclerView这个位置的view的parent为null, 并标记ViewHolder为FLAG_TMP_DETACHED
recycler.scrapView(view); //添加到mAttachedScrap集合中

}


**所以在这种情形下`LinearLayoutManager`在真正摆放`子View`之前,会把所有`旧的子View`按顺序保存到`Recycler`的`mAttachedScrap集合`中**

接下来继续看,`LinearLayoutManager`在布局时如何复用`mAttachedScrap集合`中的`ViewHolder`。

前面已经说了`LinearLayoutManager`会当前布局子View的位置向`Recycler`要一个子View,即调用到`tryGetViewHolderForPositionByDeadline(position..)`。我们上面已经列出了这个方法的逻辑,其实在前面的第二步:

**尝试`根据position`从`scrap集合`、`hide的view集合`、`mCacheViews(一级缓存)`中寻找一个`ViewHolder`**

即从`mAttachedScrap`中就可以获得一个`ViewHolder`:

ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
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;
}
}

}


即如果`mAttachedScrap中holder`的位置和`入参position`相等,并且`holder`是有效的话这个`holder`就是可以复用的。所以综上所述,在情形二下所有的`ViewHolder`几乎都是复用`Recycler中mAttachedScrap集合`中的。 并且重新布局完毕后`Recycler`中是不存在可复用的`ViewHolder`的。

情形三 : 滚动复用
----------

这个情形分析是在`情形二`的基础上向下滑动时`ViewHolder`的复用情况以及`Recycler`中`ViewHolder`的保存情况, 如下图:

![](https://user-gold-cdn.xitu.io/2018/12/14/167abd63ba2079ab?imageView2/0/w/1280/h/960/ignore-error/1)

在这种情况下滚出屏幕的View会优先保存到`mCacheViews`, 如果`mCacheViews`中保存满了,就会保存到`RecyclerViewPool`中。

在前一篇文章`RecyclerView刷新机制`中分析过,`RecyclerView`在滑动时会调用`LinearLayoutManager.fill()`方法来根据滚动的距离来向`RecyclerView`填充子View,其实在个方法在填充完子View之后就会把滚动出屏幕的View做回收:

int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {

int remainingSpace = layoutState.mAvailable + layoutState.mExtra;

while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {

layoutChunk(recycler, state, layoutState, layoutChunkResult); //填充一个子View

    if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
        layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
        if (layoutState.mAvailable < 0) {
            layoutState.mScrollingOffset += layoutState.mAvailable;
        }
        recycleByLayoutState(recycler, layoutState); //根据滚动的距离来回收View
    }
}

}


即`fill`每填充一个`子View`都会调用`recycleByLayoutState()`来回收一个`旧的子View`,这个方法在层层调用之后会调用到`Recycler.recycleViewHolderInternal()`。这个方法是`ViewHolder`回收的核心方法,不过逻辑很简单:

 
上一篇:Android从源码分析RecyclerView四级缓存复用机制一(缓存ViewHolder)


下一篇:Android 相关源码分析,真服了