探究RecyclerView复用缓存机制
- RecyclerView中onCreateViewHolder与onBindViewHolder执行现象
- RecyclerView的复用机制及onCreateViewHolder与onBindViewHolder执行时机
- RecyclerView的回收机制
- 问题结论
RecyclerView中onCreateViewHolder与onBindViewHolder执行现象
首先我们看下出现RecyclerView出现的情况,这里我们将布局管理器设置为GridLayoutManager,显示行数设置为3
rv = (RecyclerView) findViewById(R.id.rv);
rv.setLayoutManager(new GridLayoutManager(this, 3));
rv.addItemDecoration(new DividerItemDecoration(this, LinearLayout.VERTICAL));
final List<String> list = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
list.add("" + i);
}
final CustomAdapter adapter = new CustomAdapter(this, list);
rv.setAdapter(adapter);
并且在Adapter中的onCreateViewHolder和onBindViewHolder方法中打印log.
@Override
public CustomViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_rv, parent, false);
Log.e(TAG, "onCreateViewHolder: " + getItemCount());
return new CustomViewHolder(view);
}
@Override
public void onBindViewHolder(CustomViewHolder holder, int position) {
holder.tv.setText(list.get(position));
Log.e(TAG, "onBindViewHolder: " + position);
}
然后上下滑动屏幕,运行输出log日志情况如下:
很明显 onBindViewHolder继续执行,而onCreateViewHolder则不再执行.
这里我们再把GridLayoutManager的显示行数设置为8,看看又会是一个什么效果?
rv.setLayoutManager(new GridLayoutManager(this, 8));
这次的话,onCreateViewHolder也会每次都执行, 那这到底是什么原因导致的呢? 嘿嘿,当然就是RecyclerView的回收复用机制了.
RecyclerView的复用机制及onCreateViewHolder与onBindViewHolder执行时机
RecyclerView的源代码这么多,那我从哪分析呢? 其实我们想哈,复用是不是在我们手指滑动的时候产生的呢, 那我们不妨先看看 RecyclerView的onTouchEvent是如何处理move事件,这里我就说下关键方法的跳转流程,
在处理move事件的逻辑中,首先是执行scrollByInternal方法, 在scrollByInternal方法中执行了scrollStep方法,
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
......
if (dx != 0) {
consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
}
if (dy != 0) {
consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
}
......
}
接着去执行水平/垂直滚动逻辑,这里以垂直滚动为例继续往下执行,在scrollVerticallyBy方法(LinearLayoutManager)中又执行了scrollBy方法,在scrollBy中执行了fill方法,跳到fill方法里面继续查看,执行了layoutChunk方法,又在layoutChunk方法里面执行了layoutState.next(recycler),点击这个方法继续分析下去,最后执行到了tryGetViewHolderForPositionByDeadline方法, 话语啰嗦啰嗦,我这里再贴张图,
tryGetViewHolderForPositionByDeadline方法中也就是我们如何获取到holder的机制了,看看是从RecyclerView的哪一级缓存中取得holder.这里我们先说说RecyclerView存在的四级缓冲;
- mChangeScrap和mAttachedScrap (用来缓存还在屏幕内的ViewHolder)
- mCachedViews (用来缓存移除屏幕之外的ViewHolder)
- mViewCacheExtension (开发给用户的自定义扩展缓存,需要用户自己管理View的创建和缓存)
- RecycledViewPool (ViewHolder的缓存池)
holder = getChangedScrapViewForPosition(position);
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
if (holder == null) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
}
if (holder == null && mViewCacheExtension != null) {
final View view = mViewCacheExtension
.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
}
}
if (holder == null) { // fallback to pool
holder = getRecycledViewPool().getRecycledView(type);
}
}
大致把这个方法中关于如何取出holder的梳理出来后,我们可能会想,要是这几个缓冲中都取不出来holder的话又该怎么办呢? 别急,代码还没分析完呢, 实在都取不出来的话,那我们就只能自己创造了.
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
}
继续分析createViewHolder方法的话,就回到了我们常在adpater中写的代码了
@Override
public CustomViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.item_rv, parent, false);
Log.e(TAG, "onCreateViewHolder: " + getItemCount());
return new CustomViewHolder(view);
}
原来我们写的适配器代码中onCreateViewHolder是这样被执行到的啊!
一路下来,ViewHolder总算是拿到了,接下来就该bind了,和数据进行绑定.
在tryGetViewHolderForPositionByDeadline方法里面,我们看到了ViewHolder绑定数据的代码
tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
它又执行了什么呢?
private boolean tryBindViewHolderByDeadline(@NonNull ViewHolder holder, int offsetPosition,
int position, long deadlineNs) {
......
mAdapter.bindViewHolder(holder, offsetPosition);
.....
}
接着也就回到了我们写的adapter中的onBindViewHolder方法.
@Override
public void onBindViewHolder(CustomViewHolder holder, int position) {
holder.tv.setText(list.get(position));
Log.e(TAG, "onBindViewHolder: " + position);
}
这下可算是搞明白这两个方法到底是在什么时候执行的了…
RecyclerView的回收机制
不过,最初的问题还是没有解决, 改变GridLayoutManager显示的行数怎么onCreateViewHolder也会每次都执行呢? 还是别急, 嘿嘿, 其实上面的分析我们也只是分析了RecyclerView的复用情况,也就是如何取出ViewHolder, ViewHolder怎么存的我们还没有分析, 我们把RecyclerView的的回收机制分析后也就明白了上面的情况到底是什么原因了.
那从哪开始分析RecyclerView的缓存机制呢? 这个我们就直接从LinearLayoutManager的onLayoutChildren方法开始分析,至于说是如何来到这个方法的, 后面我们再细说, 在onLayoutChildren方法中会执行detachAndScrapAttachedViews(recycler),在detachAndScrapAttachedViews方法里又执行了scrapOrRecycleView(recycler, i, v);
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
这里会根据ViewHolde的状态去执行if else逻辑, ViewHolder的状态变化如何理解呢? 其实就是我们有时候会在一个adpater中写多个ViewHolder的情况. 知道了这个以后, 我们先看看ViewHolder状态不变的情况下执行的recycler.recycleViewHolderInternal(viewHolder)方法
void recycleViewHolderInternal(ViewHolder holder) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
/**
**if中的逻辑被执行,仅当ViewHolder状态不发生
*改变情况下
*/
// Retire oldest cached view
int cachedViewSize = mCachedViews.size();
/**
* 检查当前CachedView的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);
cached = true;
}
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
我们在看下recycleCachedViewAt(0) 背后做了什么事情
void recycleCachedViewAt(int cachedViewIndex) {
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
显然,如果CachedViews 如果超出了最大数量,则会将头部的ViewHolder放到RecycledViewPool缓冲池中, 换言之,RecycledViewPool缓冲池的保存的ViewHolder也都来自于CachedViews. CachedViews的默认大小我们很容易看到设置的是2.
接着查看addViewHolderToRecycledViewPool干的事情
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
getRecycledViewPool().putRecycledView(holder);
}
缓冲池中如何保存ViewHolder的逻辑终于来了.
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();
scrapHeap.add(scrap);
}
private ScrapData getScrapDataForType(int viewType) {
/**
** 根据viewType从SparseArray结构中取出ScrapData
**/
ScrapData scrapData = mScrap.get(viewType);
if (scrapData == null) {
scrapData = new ScrapData();
mScrap.put(viewType, scrapData);
}
return scrapData;
}
这里,我们要知道RecycledViewPool并不是简单的一个顺序表结构,而是这样的一个结构.DEFAULT_MAX_SCRAP=5 表明一个viewtype最多存放5个ViewHolder.
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
而SparseArray 我们可以简单理解为一个key是int类型的map对象;这里我们看到这行代码,表明如果viewType对应存储的ViewHolder数量如果超出默认最大值的话,则直接丢弃. 也就是说RecycledViewPool缓存池中存放的只是没有数据的ViewHolder,而CachedView存储的则是已经bind数据的ViewHolder,所以不能像RecycledViewPool那样直接丢弃,而是将ViewHolder扔给了RecycledViewPool.
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
写了这么多,此时再来一张图就更清晰了.
问题结论
最初抛出的那个问题到这里也能解决了: GridLayoutManager设置显示3列, CacheView能缓存2, RecyclerViewPool每一个viewType对应结构也能容纳得下5个ViewHolder, 总而言之,在承受范围内,自然是不用去create ViewHolder的; 而设置显示8列则不用多说了.
至于回收的时候,为什么从LinearLayoutManager的onLayoutChildren方法开始分析,这里就贴出一张图来说明是如何跳到这里的吧.