前言
我们都知道,RecyclerView 在 layout 子 View 时,都通过回收复用机制来管理。
在我没有深入了解ReyclerView缓存机制之前,我对RecyclerView认识是:RecyclerView维护着一个容器保存着ItemView,当我们在用的时候就从这里面取,这样就达到了复用的效果。是不是很简单,哈哈!!!然而现实狠狠给了我三个耳光(我们有四级缓存)。
what fuck!!!为什么需要四级缓存?这样的疑问估计就是我这种码农和Google 🐂🍺工程师之间的差距吧。自叹不如!所以为了提升自己的能力,我得好好 read the fuck code 了。
经过自己阅读代码,以及网上关于回收复用机制的分析讲解的文章也有一大堆了,分析得也都很详细,什么四级缓存啊,先去 mChangedScrap 取再去哪里取啊之类的。所以我决定,我要写一篇只有我能看懂的文章记录下我对RecyclerView回收复用机制的理解!
下面进入正题
正题
- 核心类介绍
- 回收和复用分析入口
- 回收的机制
- 复用的逻辑
核心类介绍
在分析回收复用机制之前,我们需要对回收复用机制的核心类有个了解,下面是RecyclerView回收复用中几个核心类的核心功能:
-
ViewHolder:对于
Adapter
来说,一个ViewHolder
就对应一个data
。它也是Recycler缓存池
的基本单元。- itemView : 会被当做
child view
来add
到RecyclerView
中。 - mPosition : 标记当前的
ViewHolder
在Adapter
中所处的位置。 - mItemViewType : 这个
ViewHolder
的Type
,在ViewHolder
保存到RecyclerPool
时,主要靠这个类型来对ViewHolder
做复用。 - mFlags : 标记
ViewHolder
的状态,比如FLAG_BOUND(显示在屏幕上)
、FLAG_INVALID(无效,想要使用必须rebound)
、FLAG_REMOVED(已被移除)
等。
- itemView : 会被当做
-
Adapter:它的工作是把
data
和View
绑定,即上面说的一个data
对应一个ViewHolder
。主要负责ViewHolder
的创建以及数据变化时通知RecyclerView
。 -
LayoutManager:它是
RecyclerView
的布局管理者,RecyclerView
在onLayout
时,会利用它来layoutChildren
,它决定了RecyclerView
中的子View的摆放规则。但不止如此, 它做的工作还有:- 测量子View
- 对子View进行布局
- 对子View进行回收
- 子View动画的调度
- 负责
RecyclerView
滚动的实现 - ...
-
Recycler:对于
LayoutManager
来说,它是ViewHolder
的提供者。对于RecyclerView
来说,它是ViewHolder
的管理者,是RecyclerView
最核心的实现。四级缓存机制,后面具体介绍 -
RecyclerView:继承ViewGroup,列表视图的容器,串联
Recycler
、LayoutManager
、Adapter
的核心类
小结
通过前面核心类的介绍,我们知道ViewHolder的mFlags标记着当前View的状态,通过这些标志 我们可以知道哪些ViewHolder可以回收,哪些可以复用。
回收和复用分析入口
RecyclerView回收入口有很多, 但是RecyclerView 的回收或者复用必然涉及到add view 和 remove view 的操作,还有Adapter notifyData 等 刷新操作,都会触发 view layout 流程。 所以我将从onLayout的流程入手分析回收和复用的机制。
回收的机制
下面是整个onLayout视图回收的时序图
我们看下重要的几个方法
scrapOrRecycleView
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
//ViewHolder 失效 并且未被 移除
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
//将View remove
removeViewAt(index);
//viewHolder存到缓存中
recycler.recycleViewHolderInternal(viewHolder);
} else {//ViewHolder 在屏幕内,有效
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
detachAndScrapAttachedViews
会遍历所有RecyclerView的child View 然后调用scrapOrRecycleView
这个方法会将所有的viewHolder存放到对应的缓存中
缓存到mCachedViews和mRecyclerPool中
recycleViewHolderInternal
void recycleViewHolderInternal(ViewHolder holder) {
省略代码...
final boolean transientStatePreventsRecycling = holder
.doesTransientStatePreventRecycling();
@SuppressWarnings("unchecked")
final boolean forceRecycle = mAdapter != null
&& transientStatePreventsRecycling
&& mAdapter.onFailedToRecycleView(holder);
boolean cached = false;
boolean recycled = false;
if (forceRecycle || holder.isRecyclable()) {
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();
//如果mCachedViews缓存已满,先移除最先缓存的holder
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}
省略代码...
// 添加 holder 到mCachedViews缓存中
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
//如果前面未缓存,将holder缓存到RecycledViewPool中
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
省略代码...
}
省略代码...
}
recycleCachedViewAt
void recycleCachedViewAt(int cachedViewIndex) {
if (DEBUG) {
Log.d(TAG, "Recycling cached view at index " + cachedViewIndex);
}
ViewHolder viewHolder = mCachedViews.get(cachedViewIndex);
if (DEBUG) {
Log.d(TAG, "CachedViewHolder to be recycled: " + viewHolder);
}
addViewHolderToRecycledViewPool(viewHolder, true);
mCachedViews.remove(cachedViewIndex);
}
取出mCachedViews中的缓存并缓存到RecycledViewPool中
addViewHolderToRecycledViewPool
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
clearNestedRecyclerViewIfNotNested(holder);
View itemView = holder.itemView;
if (mAccessibilityDelegate != null) {
AccessibilityDelegateCompat itemDelegate = mAccessibilityDelegate.getItemDelegate();
AccessibilityDelegateCompat originalDelegate = null;
if (itemDelegate instanceof RecyclerViewAccessibilityDelegate.ItemDelegate) {
originalDelegate =
((RecyclerViewAccessibilityDelegate.ItemDelegate) itemDelegate)
.getAndRemoveOriginalDelegateForItem(itemView);
}
// Set the a11y delegate back to whatever the original delegate was.
ViewCompat.setAccessibilityDelegate(itemView, originalDelegate);
}
if (dispatchRecycled) {
dispatchViewRecycled(holder);
}
holder.mOwnerRecyclerView = null;
getRecycledViewPool().putRecycledView(holder);
}
最终调用getRecycledViewPool().putRecycledView()
缓存holder
小结
- mCachedViews缓存非
ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN
的holder - RecycledViewPool缓存的holder,会调用
holder.resetInternal()
将holder内部数据重置 - mCachedViews 和 RecycledViewPool 只会缓存不在屏幕中的ViewHolder
- mCachedViews有缓存个数限制 默认2个,可以通过调用
setViewCacheSize
设置缓存个数 - RecycledViewPool缓存各种ItemType的holder,每一种holder 最多默认缓存5个,可以通过
setMaxRecycledViews
设置对应ItemType的缓存个数,超过个数限制者不缓存后面新添的holder
缓存到mAttachedScrap和mChangedScrap中
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);
//缓存不需要更新的holder
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
//缓存需要更新的holder
mChangedScrap.add(holder);
}
}
- mAttachedScrap 缓存不需要更新,等到用的时候直接拿来用的holder,也就是数据没有变化
- mChangedScrap 用来缓存需要更新的holder
- mAttachedScrap和mChangedScrap缓存的holder的ItemView都是添加到RecyclerView的 childView
复用的逻辑
@Nullable
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;
}
// 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());
}
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;
}
}
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());
}
}
}
if (holder == null) { // fallback to pool
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ position + ") fetching from shared pool");
}
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");
}
}
}
省略代码...
return holder;
}
分几种情况去获取ViewHolder
- getChangedScrapViewForPosition -- mChangeScrap 与动画相关
- getScrapOrHiddenOrCachedHolderForPosition -- mAttachedScrap 、mCachedViews
- getScrapOrCachedViewForId -- mAttachedScrap 、mCachedViews (ViewType,itemid)
- mViewCacheExtension.getViewForPositionAndType -- 自定义缓存 -- 一般不会使用
- getRecycledViewPool().getRecycledView -- 从缓冲池里面获取