学而不思则罔,思而不学则殆
相关文档:
RecyclerView学习(一)之应用
RecyclerView学习(二)之缓存探索
RecyclerView学习(三)之缓存原理分析
结合上一篇文章:RecyclerVIew缓存探索
本篇文章来分析研究一下缓存原理
从上一篇文章分析我们得出,当我们列表滑动到一定的时候就不在调用onCreateViewHolder方法,产生新的Holder;但是我们的onBindViewHolder方法却还在回调,返回的Holder却是老的对象;而且Holder的总个数大于屏幕上实际显示的列表个数
看一下代码,这段代码在RecyclerView.Recycler中,是RecyclerView中的一个final类型的内部类,这个方法代码比较长,我们删除干扰项,理出主干逻辑
/**
* Attempts to get the ViewHolder for the given position, either from the Recycler scrap,
* cache, the RecycledViewPool, or creating it directly.
*
* If a deadlineNs other than {@link #FOREVER_NS} is passed, this method early return
* rather than constructing or binding a ViewHolder if it doesn't think it has time.
* If a ViewHolder must be constructed and not enough time remains, null is returned. If a
* ViewHolder is aquired and must be bound but not enough time remains, an unbound holder is
* returned. Use {@link ViewHolder#isBound()} on the returned object to check for this.
*
* @param position Position of ViewHolder to be returned.
* @param dryRun True if the ViewHolder should not be removed from scrap/cache/
* @param deadlineNs Time, relative to getNanoTime(), by which bind/create work should
* complete. If FOREVER_NS is passed, this method will not fail to
* create/bind the holder if needed.
*
* @return ViewHolder for requested position
*/
@Nullable
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
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) {
//根据Adapter的getItemViewType获取不同的type
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 && 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) { // fallback to pool
holder = getRecycledViewPool().getRecycledView(type);
...
}
if (holder == null) {
long start = getNanoTime();
...
//调用mAdapter的createViewHolder创建新的Holder-说明缓存中没有可复用的,需要新建
holder = mAdapter.createViewHolder(RecyclerView.this, type);
...
long end = getNanoTime();
mRecyclerPool.factorInCreateTime(type, end - start);
if (DEBUG) {
Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
}
}
}
...
return holder;
}
我们先来看一下这个方法的说明
Attempts to get the ViewHolder for the given position, either from the Recycler scrap, cache, the RecycledViewPool, or creating it directly.
尝试从回收器废件、缓存、回收的viewpool或直接创建它 的方式来获取ViewHolder通过给定的position
从这段代码中我们发现holder总共有5次被赋值的逻辑
- holder = getChangedScrapViewForPosition(position);
- holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
- holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
- holder = getChildViewHolder(view);
- holder = mAdapter.createViewHolder(RecyclerView.this, type);
以上这5次赋值逻辑除了最后一次,前面的4次都是去缓存中拿数据,这里只是拿缓存,那什么时候设置缓存呢?后面会分析
这里拿缓存也是有优先级的
缓存的主要控制类,如下是内部属性,
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;
RecycledViewPool mRecyclerPool;
private ViewCacheExtension mViewCacheExtension;
static final int DEFAULT_CACHE_SIZE = 2;
...方法...
}
结论
- mAttachedScrap 是一个对象为ViewHolder的列表
- mChangedScrap 也是一个对象为VIewHolder的列表
- mCachedViews 也是一个对象为VIewHolder的列表
- mUnmodifiableAttachedScrap 也是一个对象为VIewHolder的列表
- mRecyclerPool 看名称是缓存池
- mViewCacheExtension 看名称也是缓存
- DEFAULT_CACHE_SIZE 默认大小2
- Recycler是个存在ViewHolder和缓存的类
在来看看RecycledViewPool 和ViewCacheExtension 是什么样的?
先来看看RecycledViewPool 主要源码,方法什么的先不看,只看属性
/**
* RecycledViewPool lets you share Views between multiple RecyclerViews.
*
* If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
* and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
*
* RecyclerView automatically creates a pool for itself if you don't provide one.
*/
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
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<>();
private int mAttachCount = 0;
//...方法...
}
RecycledViewPool 类说明,很重要,也说的很清楚
/**
* RecycledViewPool lets you share Views between multiple RecyclerViews.
*
* If you want to recycle views across RecyclerViews, create an instance of RecycledViewPool
* and use {@link RecyclerView#setRecycledViewPool(RecycledViewPool)}.
*
* RecyclerView automatically creates a pool for itself if you don't provide one.
*/
- RecycledViewPool 让你可以在不同的RecyclerView之前分享View
- 如果你想在不同的RecyclerView之前回收View,需要创建一个RecycledViewPool,通过setRecycledViewPool设置
- RecyclerView会自动为自己创建池,如果你没有提供一个
- 说的很明白,是什么,怎么用,以及RecyclerView的兜底方案
再来看看RecycledViewPool 的对象属性
- DEFAULT_MAX_SCRAP 定义了一个默认的最大缓存个数
- mScrap 是一个对象为ScrapData的列表
- ScrapData本身也是一个保留ViewHolder的列表,默认对大个数为DEFAULT_MAX_SCRAP
- mAttachCount 计数的
- mScrap 列表的key值是 ViewHolder Type
- 可用通过setMaxRecycledViews(int viewType, int max)设置不同的tyep的最大缓存数量
- 如果我们什么都不是设置的话,就会每个tyep默认有5个缓存在这儿(讲解了上一篇文章的一个问题,缓存的个数问题)
ViewCacheExtension是一个抽象类,需要我们去实现,先看看源码
/**
* ViewCacheExtension is a helper class to provide an additional layer of view caching that can
* be controlled by the developer.
*
* When {@link Recycler#getViewForPosition(int)} is called, Recycler checks attached scrap and
* first level cache to find a matching View. If it cannot find a suitable View, Recycler will
* call the {@link #getViewForPositionAndType(Recycler, int, int)} before checking
* {@link RecycledViewPool}.
*
* Note that, Recycler never sends Views to this method to be cached. It is developers
* responsibility to decide whether they want to keep their Views in this custom cache or let
* the default recycling policy handle it.
*/
public abstract static class ViewCacheExtension {
/**
* Returns a View that can be binded to the given Adapter position.
*
* This method should not create a new View. Instead, it is expected to return
* an already created View that can be re-used for the given type and position.
* If the View is marked as ignored, it should first call
* {@link LayoutManager#stopIgnoringView(View)} before returning the View.
*
* RecyclerView will re-bind the returned View to the position if necessary.
*
* @param recycler The Recycler that can be used to bind the View
* @param position The adapter position
* @param type The type of the View, defined by adapter
* @return A View that is bound to the given position or NULL if there is no View to re-use
* @see LayoutManager#ignoreView(View)
*/
@Nullable
public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
int type);
}
代码很少,一个抽象类+一个抽象方法
这个状态不得不说,缓存使用都是根据ViewHolder的状态来进行判断的
状态 | 说明 |
---|---|
FLAG_BOUND = 1 << 0 | ViewHolder已经绑定好 |
FLAG_UPDATE = 1 << 1 | 数据已经过期,需要重新绑定 |
FLAG_INVALID = 1 << 2 | ViewHolder的数据是无效的,类型有可能不在匹配,需要完全重新绑定 |
FLAG_REMOVED = 1 << 3 | 表示数据已经从集合中移除,但是视图任然可以用作动画 |
FLAG_NOT_RECYCLABLE = 1 << 4 | 这个ViewHolder不能回收复用;通过setIsRecyclable方法设置;一般用于在动画期间保持视图 |
FLAG_RETURNED_FROM_SCRAP = 1 << 5 | 视图是从缓存中返回的;当从废件返回时,ViewHolder将保留在废件列表中,直到布局过程的结束,如果未将其添加回RecyclerView,则由RecyclerView回收 |
FLAG_IGNORE = 1 << 7 | 此ViewHolder完全由LayoutManager管理。除非更换LayoutManager,否则我们不会对其进行报废、回收或移除。它对LayoutManager仍然是完全可见的。 |
FLAG_TMP_DETACHED = 1 << 8 | 当视图与父视图分离时,我们设置此标志,以便在需要删除或重新添加视图时采取正确的操作。 |
FLAG_ADAPTER_POSITION_UNKNOWN = 1 << 9 | 当我们无法再确定此视图支架的适配器位置时设置,直到它反弹到新位置为止 |
FLAG_ADAPTER_FULLUPDATE = 1 << 10 | 调用addChangePayload(null)时设置 |
FLAG_MOVED = 1 << 11 | 动画相关属性 |
FLAG_APPEARED_IN_PRE_LAYOUT = 1 << 12 | 当视图固定器出现在预布局中时,由ItemAnimator使用,我的理解为预布局 |
以上的flag我也没有了解全,只是知道有这么一个东西,用来标识ViewHolder的各种状态,标识各种状态的flag的其中一个作用就是复用缓存判断,想了解各种状态什么时候赋值的话,需要仔细去阅读源码,调试其赋值的逻辑,作者后面如果有时间会针对这个flag做一个状态流转图(立一个flag)
但是以上都不影响我们研究缓存原理,我们只要知道复用缓存的时候需要判断这个状态,至于状态怎么来的可以暂时不关心,这个属于另一个研究课题
现在我们来看一下获取ViewHolder的五大步骤,就是开头的五个复制方法
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
//
ViewHolder getChangedScrapViewForPosition(int position) {
// If pre-layout, check the changed scrap for an exact match.
final int changedScrapSize;
if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
return null;
}
// 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;
}
分析
- 这里有一个 mState{final State mState = new State();},你只要了解它是一个RecyclerView的状态类就可以了
- 如果成立会从mChangedScrap这个列表中获取有没有缓存,如果有,会设置Flag = ViewHolder.FLAG_RETURNED_FROM_SCRAP,在返回
// 1) Find by position from scrap/hidden list/cache
if (holder == null) {
//1.1 从缓存中获取ViewHolder
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;
}
}
}
/**
* Returns a view for the position either from attach scrap, hidden children, or cache.
*
* @param position Item position
* @param dryRun Does a dry run, finds the ViewHolder but does not remove
* @return a ViewHolder that can be re-used for this position.
*/
ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
final int scrapCount = mAttachedScrap.size();
// Try first for an exact, non-invalid match from scrap.
for (int i = 0; i < scrapCount; i++) {
//从mAttachedScrap获取
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;
}
}
if (!dryRun) {
//从隐藏的视图中获取
View view = mChildHelper.findHiddenNonRemovedView(position);
if (view != null) {
// This View is good to be used. We just need to unhide, detach and move to the
// scrap list.
final ViewHolder vh = getChildViewHolderInt(view);
mChildHelper.unhide(view);
int layoutIndex = mChildHelper.indexOfChild(view);
if (layoutIndex == RecyclerView.NO_POSITION) {
throw new IllegalStateException("layout index should not be -1 after "
+ "unhiding a view:" + vh + exceptionLabel());
}
mChildHelper.detachViewFromParent(layoutIndex);
scrapView(view);
vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
| ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
return vh;
}
}
// Search in our first-level recycled view cache.
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
//从mCachedViews中获取
final ViewHolder holder = mCachedViews.get(i);
// invalid view holders may be in cache if adapter has stable ids as they can be
// retrieved via getScrapOrCachedViewForId
if (!holder.isInvalid() && holder.getLayoutPosition() == position
&& !holder.isAttachedToTransitionOverlay()) {
if (!dryRun) {
mCachedViews.remove(i);
}
if (DEBUG) {
Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
+ ") found match in cache: " + holder);
}
return holder;
}
}
return null;
}
以上获取的缓存都是系统提供的
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.
//从ViewCacheExtension中获取缓存
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");
}
//从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;
}
//新建ViewHolder
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");
}
}
}
以上就是缓存原理分析,有很多地方不是清楚,欢迎指教,共同进步