系列文章:
前言
此前已经介绍完RecyclerView
的绘制流程,在绘制流程中我们还残留RecyclerView
的缓存机制的问题没有解释。
在分析ListView
过程中,我们先分析了ListView
中缓存的核心实现类RecycleBin
,类似的在RecyclerView中也存在一个Recycler类,其中代码逻辑比RecycleBin
复杂。其定位是RecyclerView的View管理者,其功能包括生成新View,复用旧View,回收View,重新绑定View。外部只需调用其相关接口即可,而无需关心其内部的具体实现细节。
下面先列出Recycler中涉及到缓存机制的相关变量:
public final class Recycler {
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
private final List<ViewHolder>
mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;
private RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
static final int DEFAULT_CACHE_SIZE = 2;
}
RecyclerView缓存机制初步分析
注意:以下涉及到
LayoutManager
的子类均以LinearLayoutManager
为例
LayoutState.next方法
在上篇博客中我们提到RecyclerView中的缓存机制开始于LayoutState.next
方法,下面我们就进入next
方法一步一步来解析RecyclerView的缓存机制:
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
可以看到这里除了我们之前提到的Recycler
的缓存外,还存在缓存mScrapList
,mScrapList
被LinearLayoutManager
持有,其声明如下:
List<RecyclerView.ViewHolder> mScrapList = null;
上述声明说明mScrapList
是一个ViewHolder
类型的List
。当LinearLayoutManager
需要布局特定视图时,它会设置mScrapList
,在这种情况下,LayoutState
将仅从该列表返回视图,如果找不到则返回null。nextViewFromScrapList
的源码如下:
private View nextViewFromScrapList() {
final int size = mScrapList.size();
for (int i = 0; i < size; i++) {
final View view = mScrapList.get(i).itemView;
final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) view.getLayoutParams();
if (lp.isItemRemoved()) {
continue;
}
if (mCurrentPosition == lp.getViewLayoutPosition()) {
assignPositionFromScrapList(view);
return view;
}
}
return null;
}
上述具体代码暂不分析,Recycler
是被RecyclerView
持有,缓存的重头戏还是RecyclerView
的内部类Recycler
,当mScrapList
为null时,则调用了Recycler的getViewForPosition
方法。
Recycler.getViewForPosition方法
此方法就是从Recycler
处获取View:
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
View getViewForPosition(int position, boolean dryRun) {
return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}
getViewForPosition
方法最后调用的是tryGetViewHolderForPositionByDeadline
方法,此方法的注释写的很清楚,就是试图从Recycler
的scrap,cache,RecycldViewPool获取ViewHolder,都获取不到则直接创建ViewHolder,很明显分析此方法代码就要从这四种情况来讨论。
Recycler.tryGetViewHolderForPositionByDeadline方法
// Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
// cache, the RecycledViewPool, or creating it directly.
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) {
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 (holder == null) { // fallback to pool
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
+ position + ") fetching from shared pool");
}
holder = getRecycledViewPool().getRecycledView(type);
...
}
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);
}
...
return holder;
}
下面就一步一步来分析tryGetViewHolderForPositionByDeadline
方法吧:
非常规缓存:从mChangedScrap中获取ViewHodler
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
public boolean isPreLayout() {
return mInPreLayout;
}
首先判断mInPreLayout
变量,它默认为false,当有动画时此变量才为true,再来看看getChangedScrapViewForPosition()
方法,源码如下:
ViewHolder getChangedScrapViewForPosition(int position) {
...
// find by position
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getLayoutPosition() == position) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
// find by id
if (mAdapter.hasStableIds()) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
final long id = mAdapter.getItemId(offsetPosition);
for (int i = 0; i < changedScrapSize; i++) {
final ViewHolder holder = mChangedScrap.get(i);
if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
}
}
return null;
}
从上面可以看出,getChangedScrapViewForPosition()
方法通过两种方式从Recycler
中的mChangedScrap
获取ViewHolder
,同时只有有动画时,mChangedScrap
才起作用,这也是为什么没有将mChangedScrap
放在常规缓存中。
第一级缓存:从mAttachedScrap和mCacheViews中获取View(通过位置尝试)
先大概看下相关源码:
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
...
}
}
此段代码中先通过getScrapOrHiddenOrCachedHolderForPosition
方法来获取ViewHolder
,源码如下:
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
...
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
...
}
if (!dryRun) {
View view = mChildHelper.findHiddenNonRemovedView(position);
...
}
// Search in our first-level recycled view cache.
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
...
}
return null;
}
此方法大概可以分为以下三步:
- 先从
mAttachedScrap
中获取ViewHolder
; - 从
HiddenViews
中获取View
,再根据View
获取ViewHolder
; - 从
mCachedViews
中获取ViewHolder
。
其流程上可以总结如下:
第一级缓存:从mAttachedScrap和mCacheViews中获取ViewHolder(通过id尝试)
final int type = mAdapter.getItemViewType(offsetPosition);
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
第一级缓存的第二种尝试先执行了mAdapter.getItemViewType(offsetPosition)
,getItemViewType
方法是RecyclerView
用于多类型子View的相关方法。可以看出对于前面通过位置获取ViewHolder的第一级缓存没有考虑子View的type情况。
接下来又对mAdapter.hasStableIds()
进行了判断,此方法返回的是mHasStableIds
变量,关于hasStableIds
方法这里给出一些Android 源码注释,但是苦于无法很好的进行翻译,故而贴出英文版:
Returns true if this adapter publishes a unique value that can act as a key for the item at a given position in the data set. If that item is relocated in the data set, the ID returned for that item should be the same.
若hasStableIds()
方法返回true,则接着调用getScrapOrCachedViewForId
方法,源码如下:
ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
// Look in our attached views first
final int count = mAttachedScrap.size();
for (int i = count - 1; i >= 0; i--) {
final ViewHolder holder = mAttachedScrap.get(i);
...
}
// Search the first-level cache
final int cacheSize = mCachedViews.size();
for (int i = cacheSize - 1; i >= 0; i--) {
final ViewHolder holder = mCachedViews.get(i);
...
}
return null;
}
此方法的代码逻辑和上述的差不多,但在判断方面多了有关id和type的判断(具体源码可以见前文tryGetViewHolderForPositionByDeadline
方法源码处),因此当我们将mHasStableIds
变量设为true后,我们需要重写holder.getItemId() 方法,来为每一个item设置一个单独的id,这也和hasStableIds()方法的官方注释相符合。
其流程上可以总结如下:
第二级缓存:从mViewCacheExtension中获取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);
if (view != null) {
holder = getChildViewHolder(view);
...
}
}
mViewCacheExtension
用于自定义缓存,mViewCacheExtension
是一个ViewCacheExtension
对象,来看下ViewCacheExtension
定义:
public abstract static class ViewCacheExtension {
/**
* Returns a View that can be binded to the given Adapter position.
*/
public abstract View getViewForPositionAndType(Recycler recycler, int position, int type);
}
这个类是个抽象类,只定义了一个抽象方法,开发者若想实现自定义缓存,则需实现此方法。可以看出自定义缓存没什么限制,完全由开发者自己实现算法。此外普通的缓存操作都是有存放和获取的接口,而此类中只提供了获取接口,没有存放接口。
第三级缓存:从RecycledViewPool中获取ViewHolder
接着看余下的代码:
if (holder == null) { // fallback to pool
...
holder = getRecycledViewPool().getRecycledView(type);
if (holder != null) {
holder.resetInternal();
if (FORCE_INVALIDATE_DISPLAY_LIST) {
invalidateDisplayListInt(holder);
}
}
}
这个缓存是针对RecycledViewPool
的,先调用getRecycledViewPool()
方法获取RecycledViewPool
对象:
RecycledViewPool getRecycledViewPool() {
if (mRecyclerPool == null) {
mRecyclerPool = new RecycledViewPool();
}
return mRecyclerPool;
}
如果mRecyclerPool
为null,则新建一个并返回,获取到mRecyclerPool
后,调用了RecycledViewPool.getRecycledView()
方法。
RecyclerViewPool类
先来看下此类的大概结构:
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
static class ScrapData {
@UnsupportedAppUsage
ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
SparseArray<ScrapData> mScrap = new SparseArray<>();
private int mAttachCount = 0;
...
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;
}
...
可以看出RecycledViewPool
使用了SparseArray
这个数据结构,SparseArray
内部的key
就是我们的ViewType
,value
存放的是RecycledViewPool.ScrapData
类型数据,ScrapData
中有ArrayList<ViewHolder>
类型数据,默认最大容量为5。
此外,getRecycledView()
方法先通过mScrap
由ViewType
获取scrapData
后,去除并返回mScrapHeap
中的最后一个数据。
第四级缓存:创建ViewHolder(onCreateViewHolder方法)
if (holder == null) {
...
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
}
创建ViewHolder
主要调用了Adapter
的createViewHolder
方法:
public final VH createViewHolder(@NonNull ViewGroup parent, int viewType) {
...
final VH holder = onCreateViewHolder(parent, viewType);
...
}
从上可以看到最终调用了onCreateViewHolder
方法,而此方法正是我们继承RecyclerView.Adapter
的自定义Adapter所要实现的抽象方法。此方法中我们会创建一个ViewHolder
并返回。因此如果能正确创建ViewHolder
,我们最终肯定能获取到一个ViewHolder
。
onBindViewHolder方法
通过以上多级缓存方式获取到ViewHolder
后,可能还需要调用onBindViewHolder
方法,而且我们知道在使用RecyclerView
时自定义的Adapter中重写了onCreateViewHolder
方法和onBindViewHolder
方法,这两个方法是Adapter中最重要的。那么再何种情况下会回调onBindViewHolder
方法呢?
...
else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
...
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
当ViewHolder
符合以上代码中几个条件之一时,便会调用tryBindViewHolderByDeadline
方法,此方法源码如下:
private boolean tryBindViewHolderByDeadline(ViewHolder holder, int offsetPosition,
int position, long deadlineNs) {
...
mAdapter.bindViewHolder(holder, offsetPosition);
...
}
tryBindViewHolderByDeadline
中调用了bindViewHolder
方法,其源码如下:
public final void bindViewHolder(VH holder, int position) {
...
onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
...
}
可见在此方法中最终回调了onBindViewHolder
方法,而开发者一般会在此方法中做一些View的内容设置工作。
总结
至此我们基本解释了RecyclerView的四级缓存,来做个总结:
-
RecyclerView
有四级缓存:mAttachedScrap,mCacheViews,ViewCacheExtension,RecycledViewPool,创建ViewHolder; -
mAttachedScrap,mCacheViews
在第一次尝试的时候只是对View的复用,不区分type,只通过位置来寻找,但在第二次尝试的时候是区分了type,是对于ViewHolder
的复用,ViewCacheExtension,RecycledViewPool
是对于ViewHolder
的复用,而且区分type; - 如果缓存ViewHolder时发现超过了mCachedView的限制,会将最老的ViewHolder(也就是mCachedView缓存队列的第一个ViewHolder)移到RecycledViewPool中。
放上腾讯Bugly的相关博客对RecyclerView
的缓存的流程图吧(自己手动画了下,加强理解):
RecyclerView缓存机制深度分析
mAttachedScrap作用
介绍mAttachedScrap
的作用之前,我们先需要了解Detach和Remove
两个概念的区别:
-
Detach
用于将ViewGroup
中的子View从父View的子View数组中移除,子View的ParentView属性置为null;这个操作是一个轻量级临时的Remove,被Detach的子View还是和View树有千丝万缕的联系,在后面会被重新Attach到父View中。 - Remove用于真正的移除子View,不仅从父View的子View数组中移除,其他和View树各项联系也会被彻底斩断,比如焦点被清除。
介绍完Detach和Remove
的区别后,我们再来说mAttachedScrap
的真正作用。我们知道任何ViewGroup
都会经历两次onLayout过程,因此对应的子View就会经历detach和attach过程,在这个过程中,mAttachedScrap
就起到了缓存作用,保证ViewHolder
不需要重新调用onBindViewHolder
方法。下面具体分析下此过程:
- 在第一次
onLayout
过程中,会调用LayoutManager.onLayoutChildren
方法对子View布局,其中会调用detachAndScrapAttachedViews()
方法对子View进行回收,但在第一次布局过程中还没有子View,所以此方法在第一次onLayout过程中无作用:
public void detachAndScrapAttachedViews(Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
接着LayoutManager.onLayoutChildren
方法又会调用fill方法来填充屏幕,fill方法中调用了layoutState.next去获取View,接着利用了前文所说的多级缓存去获取ViewHolder,但是在第一次布局中前三级缓存都不能获取到ViewHolder或View,因此只能调用onCreateViewHolder方法来创建ViewHolder。
- 在第二次onLayout过程中,又会调用
LayoutManager.onLayoutChildren
方法对子View布局,再来看看此时的detachAndScrapAttachedViews()
方法执行情况,由于此时已经又子View了,所以便会执行scrapOrRecycleView
方法:
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
...
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
首先调用了getChildViewHolderInt
方法,该方法源码较为简单,返回子View对应的ViewHolder,接下来又对ViewHolder
进行验证,如果ViewHolder无效且未被移除且不拥有稳定的id,则将该View移除(Remove)并调用recycleViewHolderInternal
方法进行回收ViewHolder,此方法是将ViewHolder加入mCacheViews
和RecyclerViewPool
中;否则detach该View,并调用了recycler.scrapView方法,将ViewHolder
加入mAttachedScrap
或mChangedScrap
中,此处我们先来看scrapView
方法,后续再看recycleViewHolderInternal
方法。
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.");
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
在此方法中首先调用了getChildViewHolderInt
方法获取子View对应的ViewHolder,接下来对该ViewHolder
进行了相关条件判断,符合第一个条件则将该ViewHolder加入mAttachedScrap中,否则将该ViewHolder加入mChangedScrap中。
综上可见,mAttachedScrap
的主要作用就是在RecyclerView第二次layout过程中,其大小一般即为首次加载时屏幕上能放下的最大子View数。(例如:首次加载时,屏幕上有10个子View,则mAttachedScrap
的大小即为10)
mCacheViews作用
mCacheViews
真正起作用是RecyclerView发生滑动时,类似于ListView
中RecycleBin
的scrapView
。再来看一下关于mCacheViews
的两处缓存代码:
- 第一处:
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
if (!dryRun) {
mCachedViews.remove(i);
}
if (DEBUG) {
Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
+ ") found match in cache: " + holder);
}
return holder;
}
}
第一处获取的时候是遍历mCacheViews
,当缓存的ViewHolder和所需要的position相同的并且有效才可以复用,满足复用条件时,当dryRun为false时,从mCacheViews中移除此ViewHolder。
- 第二处:
final int cacheSize = mCachedViews.size();
for (int i = cacheSize - 1; i >= 0; i--) {
final ViewHolder holder = mCachedViews.get(i);
if (holder.getItemId() == id) {
if (type == holder.getItemViewType()) {
if (!dryRun) {
mCachedViews.remove(i);
}
return holder;
} else if (!dryRun) {
recycleCachedViewAt(i);
return null;
}
}
}
第一处获取的时候也是遍历mCacheViews
,当缓存的ViewHolder和所需要的id和和ViewType都相同的才可以复用,满足复用条件时,当dryRun为false时,从mCacheViews中移除此ViewHolder。
看完了如何从mCacheViews
中获取ViewHolder
,我们还需要说明下是如何向mCacheViews
中添加ViewHolder
的,前面介绍mAttachedScrap
作用时,提到了recycleViewHolderInternal
方法,此方法是将ViewHolder
加入mCacheViews
和RecyclerViewPool
中。下面便来具体看看代码:
void recycleViewHolderInternal(ViewHolder holder) {
...
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();
// 如果size大于等于最大容量,则删除第一个
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}
...
// 加入mCacheViews
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
// 否则加入RecyclerViewPool中
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
...
}
此方法的逻辑比较清晰,大概分为以下几点:
- 如果
mCachedViews
现有大小>=
默认大小,则会移除mCachedViews
中的第一个ViewHolder
,并调用recycleCachedViewAt
方法使被移除的ViewHolder
加入到RecyclerViewPool
中,最后将需要加入缓存的ViweHolder
加入到CacheViews
中; - 如果加入到
mCacheViews
中失败了,则加入到RecyclerViewPool
中。
至于为什么mCacheViews
在RecyclerView
滑动时起作用,后文阐述RecyclerView
滑动过程中的缓存机制中再描述。
RecyclerViewPool作用
前面已经介绍过RecyclerViewPool
的结构了,是通过RecyclerViewPool
的getRecycledView
方法来获取ViewHolder
的,前文已说明过此方法,在此方法中获取到ViewHolder
后移除了RecyclerViewPool
中的缓存。
已清楚如何通过RecyclerViewPool
获取ViewHolder
,再来看下是在哪里向RecyclerViewPool
中添加ViewHolder
的,正好前文叙述mCacheViews
作用时提到了recycleViewHolderInternal
方法,此方法分为两点,第一点中当mCachedViews现有大小>=默认大小时会将移除的ViewHolder通过recycleCachedViewAt方法使被移除的ViewHolder
加入到RecyclerViewPool
中,recycleCachedViewAt调用了addViewHolderToRecycledViewPool方法;第二点是加入到mCacheViews中失败了,则加入到RecyclerViewPool中,调用了addViewHolderToRecycledViewPool方法将ViewHolder加入RecyclerPool中,因此来看addViewHolderToRecycledViewPool
源码:
void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
...
getRecycledViewPool().putRecycledView(holder);
}
此方法最终调用了putRecycledView
方法:
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList 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);
}
其中resetInternal
方法值得我们注意,其中所有被put进入RecyclerPool中的ViewHolder都会被重置,这也就意味着RecyclerPool中的ViewHolder再被复用的时候是需要重新调用onBindViewHolder方法,这一点可以区分和mCacheViews
中缓存的区别。
RecyclerView滑动时的缓存机制
分析所有View的滑动时,都需要分析onTouchEvent
方法,下面来看下RecyclerView
的nTouchEvent
方法:
public boolean onTouchEvent(MotionEvent e) {
...
case MotionEvent.ACTION_MOVE: {
...
if (scrollByInternal(canScrollHorizontally ? dx : 0,canScrollVertically ? dy : 0, vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
...
return true;
}
我们重点关注onTouchEvent
的ACTION_MOVE
事件,可以看到,其中对canScrollHorizontally
和canScrollVertically
进行了判断,并最终将偏移量传给了scrollByInternal
方法,而在scrollByInternal
方法中,调用了LayoutManager
的scrollHorizontallyBy
或scrollVerticallyBy
方法,这两个方法的具体实现都在子类LayoutManager中,以scrollVerticallyBy
为例,其最后调用了scrollBy
方法:
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
...
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
...
}
可以看到这里调用了fill方法,又回到了分析RecyclerView绘制流程中,fill中真正填充子View的方法是layoutChunk(),具体过程可参考RecyclerView绘制流程。
至此,已基本理清楚关于RecyclerView
的缓存机制问题了,再来个关于这几种不同缓存方式的对比总结吧: