0%

Android控件-RecyclerView3-缓存

RecyclerView的缓存其实更多的是概念性的东西,新增了种类繁多的flag,和listview相比,区分了createViewHolder和bindViewHolder步骤,带来了部分性能上的提升。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Recycler 用来管理 scrapped 或者 detached item views 并且把它们用于重用。
// scrapped view 是指依然 attach 到 RecyclerView 但是已经被标记为 remove 或者reuse 的view,这类view可以被重新绑定和使用
//通常 Recycler 会被 LayoutManager 用来获取view, 这些view代表adapter中一个特定位置或者ID所对应的的数据。如果将要被重用的//view 被认为是 "dirty" 的,此时会要求adapter重新绑定数据和view,否则这个view可以直接被LayoutManager重用。
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;
}

ViewHolder定义了一些flag状态,这些状态是RecyclerView缓存的核心内容 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/**
* This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType
* are all valid.
ViewHolder 已经绑定到了一个位置,它的数据,mPosition,mItemId和 mItemViewType 都是有效的
*/
static final int FLAG_BOUND = 1 << 0;

/**
* The data this ViewHolder's view reflects is stale and needs to be rebound
* by the adapter. mPosition and mItemId are consistent.
ViewHolder 的view 对应的数据需要更新和重新绑定,mPosition和mItemId没变
*/
static final int FLAG_UPDATE = 1 << 1;

/**
* This ViewHolder's data is invalid. The identity implied by mPosition and mItemId
* are not to be trusted and may no longer match the item view type.
* This ViewHolder must be fully rebound to different data.
ViewHolder 的数据完全无效,mPosition,mItemId,mItemViewType 都变了,必须重新完全绑定到一个不同的数据
*/
static final int FLAG_INVALID = 1 << 2;

/**
* This ViewHolder points at data that represents an item previously removed from the
* data set. Its view may still be used for things like outgoing animations.
ViewHolder 的view 对应的数据被移除了,这个view可能仍然会被用在动画中
*/
static final int FLAG_REMOVED = 1 << 3;

/**
* This ViewHolder should not be recycled. This flag is set via setIsRecyclable()
* and is intended to keep views around during animations.
ViewHolder 不应该被回收,用于在动画期间使用
*/
static final int FLAG_NOT_RECYCLABLE = 1 << 4;

/**
* This ViewHolder is returned from scrap which means we are expecting an addView call
* for this itemView. When returned from scrap, ViewHolder stays in the scrap list until
* the end of the layout pass and then recycled by RecyclerView if it is not added back to
* the RecyclerView.
*/
static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5;

/**
* This ViewHolder is fully managed by the LayoutManager. We do not scrap, recycle or remove
* it unless LayoutManager is replaced.
* It is still fully visible to the LayoutManager.
ViewHolder 完全被 LayoutManager 控制,Recycler 不处理它
*/
static final int FLAG_IGNORE = 1 << 7;

/**
* When the View is detached form the parent, we set this flag so that we can take correct
* action when we need to remove it or add it back.
*/
static final int FLAG_TMP_DETACHED = 1 << 8;

/**
* Set when we can no longer determine the adapter position of this ViewHolder until it is
* rebound to a new position. It is different than FLAG_INVALID because FLAG_INVALID is
* set even when the type does not match. Also, FLAG_ADAPTER_POSITION_UNKNOWN is set as soon
* as adapter notification arrives vs FLAG_INVALID is set lazily before layout is
* re-calculated.
*/
static final int FLAG_ADAPTER_POSITION_UNKNOWN = 1 << 9;

/**
* Set when a addChangePayload(null) is called
*/
static final int FLAG_ADAPTER_FULLUPDATE = 1 << 10;

/**
* Used by ItemAnimator when a ViewHolder's position changes
*/
static final int FLAG_MOVED = 1 << 11;

/**
* Used by ItemAnimator when a ViewHolder appears in pre-layout
*/
static final int FLAG_APPEARED_IN_PRE_LAYOUT = 1 << 12;

虽然缓存机制是RecyclerView提供的,但是它还需要LayoutManager来配合调用相应的方法,在 LinearLayoutManager的 onlayoutChildren 方法里面 调用了 RecyclerView 的 detachAndScrapAttachedViews() :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
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);
}
}

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;
}
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}

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<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}

detachAndScrapAttachedViews会对 RecyclerView所有的 可见view 进行scrap或者 recycle 操作,同时满足三个条件的viewHolder会被recycle,否则会被scrap:

  1. viewHolder被标记为无效了,一个典型的场景就是adapter调用了notifydatasetChanged()方法,调用markKnownViewsInvalid()对所有的holder添加 invalidate 和 update标记。
  2. viewHolder对应的item 没有被移除出adapter
  3. Adapter没有指定hasStableIds,默认没有指定

对于recycle的viewHolder,如果能够添加到 mCachedViews,则添加进去,否则添加到 RecycledViewPool 里面去。mCachedViews有一个默认最大值是2,如果超过了,会把位置0的数据放到 RecycledViewPool 里面去

对于被scrap的viewHolder又做了两种区分,被标记为 FLAG_REMOVED|FLAG_INVALID 或者 没有 FLAG_UPDATE 标记 或者 canReuseUpdatedViewHolder()的holder会被添加到 mAttachedScrap ,否则添加到 mChangedScrap 。这里完全没看出来这两种有什么区别,在使用上,获取viewHolder缓存的时候, prelayout 阶段会先从 mChangedScrap 中去查找, 其他阶段会跳过 mChangedScrap 直接从 mAttachedScrap 中查找。

在LinearLayoutManager的onlayoutChildren方法中,添加view的时候会尝试去获取viewHolder,最终会调用到Recycler的获取缓存的方法中:

1
2
3
4
5
6
7
8
9
10
11
12
//LinearLayoutManager::layoutChunk
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
...
}

//Recycler::tryGetViewHolderForPositionByDeadline
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
...
}

在 tryGetViewHolderForPositionByDeadline 中,就是尝试依次从各种缓存里面取出viewHolder的过程,如果是从 RecyclerPool 或者 createViewHolder 中取得的VH,都是未绑定的状态,此时一定会执行bind操作,对于其他的来源(scrap,hiddenview,cached),则会根据条件筛选来决定是不是要执行bind操作。这里就是与ListView区别的地方了,listView在getView方法中会全部重新绑定一遍数据,这里是如果VH带有update标记或者invalidate标记才重新bind,否则这个VH包含的view可以直接使用。

对于RecyclerView其实还有很多的东西,比如position的计算过程,滑动中的缓存过程,嵌套滑动的处理,这些有空再看