序言
这篇文章会分析一下RecyclerView的回收机制 主要讲一下回收结构以及如何选择回收池
Recycler
我们先看一下Recycler类 这个类在缓存机制中起了非常重要的作用 所有缓存的viewholder都会通过这个类来存储
我们看一下类结构
public final class Recycler {
//一级缓存 缓存当前屏幕显示的view
final ArrayList mAttachedScrap = new ArrayList<>();
//当notifyItemChange的时候 会使用这个对象
ArrayList mChangedScrap = null;
//二级缓存 缓存刚划出屏幕的viewholder 可以直接拿来复用 通过下面可以看到 默认大小是2个
final ArrayList mCachedViews = new ArrayList();
private final List
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;
//四级缓存 从mCachedViews溢出的view会放到这里
RecycledViewPool mRecyclerPool;
//三级缓存 给用户用来自定义缓存机制 一般来说没啥用
private ViewCacheExtension mViewCacheExtension;
static final int DEFAULT_CACHE_SIZE = 2;
LayoutManager.next()方法
我们以LinearLayoutManager
为例 看一下next方法 我们知道 recyclerview布局过程中 会调用onLayoutChildren
方法 然后通过LayoutManager.LayoutState.next
获取View
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
看一下调用链
LinearLayoutManager.next->
RecyclerView.Recycler.getViewForPosition->
RecyclerView.Recycler.tryGetViewHolderForPositionByDeadline
这边会调用到上面我们分析的Recycler
类
最终会通过tryGetViewHolderForPositionByDeadline
方法获取ViewHolder
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
.......
//通过position获取 会先从mAttachedScrap获取 如果获取不到 会从mCachedViews中获取
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!validateViewHolderForOffsetPosition(holder)) {
// recycle holder (and unscrap if relevant) since it can't 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();
}
recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
+ "position " + position + "(offset:" + offsetPosition + ")."
+ "state:" + mState.getItemCount() + exceptionLabel());
}
//通过id获取
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap/cache via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
//尝试从自定义CacheExtension获取
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) {
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());
}
}
}
//从RecyclerViewPool获取
if (holder == null) { // fallback to pool
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ position + ") fetching from shared pool");
}
//从RecycledViewPool取
holder = getRecycledViewPool().getRecycledView(type);
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)) {
// abort - we have a deadline we can't meet
return null;
}
//直接创建
holder = mAdapter.createViewHolder(RecyclerView.this, type);
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");
}
}
}
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);
//调用bindViewHolder 然后会调用adapter.onBindViewHolder方法
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
......
return holder;
}
我们可以看到 从缓存中查找顺序为:
- 先从mAttachedScrap获取
- 再从mCachedViews获取(默认大小为2)
- 从自定义CacheExtension获取(不太好用)
- 从RecyclerViewPool获取
- 都获取不到就直接创建
Recyclerview滚动和回收机制
当Recyclerview滚动时 肯定是需要不断layout 这部分工作是交给LayoutManager来处理
LayoutManager重新layout子view之前 会将所有view先缓存到mAttachedScrap中 然后根据滑动距离 找出哪些需要layout
看一下源码
调用顺序
LinearLayoutManager.onLayoutChildren->
Recycler.detachAndScrapAttachedViews->
Recycler.scrapOrRecycleView->
Recycer.scrapView
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);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
当布局完成时 会对刚没有布局的item进行回收 具体代码如下
void recycleViewHolderInternal(ViewHolder holder) {
......
if (forceRecycle || holder.isRecyclable()) {
//如果条件成立 就加入到mCachedViews中
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);
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) {
//否则加入到RecyclerViewPool中
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
......
}
看一下上面的代码 如果回收状态(INVALID、REMOVED、UPDATE、POSITION_UNKNOWN)没有变更的话 就会放到mCachedViews中 否则放到RecyclerViewPool中
除了滚动的过程 会发生item回收和重新布局 当数据更新时 也会执行item回收和重新布局
分析一下notifyDataSetChanged
看一下方法调用链
RecyclerView.Adapter.notifyDataSetChanged->
AdapterDataObservable.notifyChanged->
RecyclerViewDataObserver.onChanged->
RecyclerView.processDataSetCompletelyChanged->
RecyclerView.markKnownViewsInvalid->
Recycler.markKnownViewsInvalid->
Recycler.recycleAndClearCachedViews->
Recycler.recycleCachedViewAt->
Recycler.addViewHolderToRecycledViewPool
我们可根据调用链 可以看到 会先将所有的ViewHolder放入RecyclerViewPool中
然后执行requestLayout
方法 进行重新布局 从RecyclerView中获取并重新进行onBindView操作 我参考了一些网上的文章 说是会将viewholder放入changedScrap中 但是我在源码中没有找到 知道的大佬希望可以指点一下
insert和remove也是大同小异了 就不分析了
总结
我们上面分析了RecyclerView 获取viewHolder的过程 仔细分析了四级缓存的作用
也分析了RecyclerView数据刷新时的工作流程 更清晰的明确了各缓存的职能 包括源码注释也是非常清楚
我们可以写一个demo 然后滑动过程来查看一下各级缓存的变化情况 不同的LayoutManage还会有不同的表现 可以自行尝试