深入 RecyclerView 学习—缓存机制,kotlin 带参数的单例模式
private int mRequestedCacheMax = DEFAULT_CACHE_SIZE;
int mViewCacheMax = DEFAULT_CACHE_SIZE;
// 缓存池
RecycledViewPool mRecyclerPool;
// 自定义缓存
private ViewCacheExtension mViewCacheExtension;
static final int DEFAULT_CACHE_SIZE = 2; // 对应 mCachedViews 默认大小
}
上面的代码也很容易看到 RecyclerView 缓存的位置,这里大概可以分为 4 种类别:
Scrap 缓存:对应当前已加载的视图,也就是屏幕中的视图;
Cache 缓存:刚刚移出屏幕的 Item,默认大小是 2 个。缓存中的 Item 匹配后可以直接展示。根据 FIFO 原则先把老的数据放入下一级缓存中(比如:RecycledViewPool),然后添加新数据;
ViewCacheExtension 缓存:用来给开发者自定义的缓存。通过 type 和 position 来查找缓存;
RecycledViewPool 缓存:默认的缓存数量是 5 个,优先级没有 Cache 缓存高,同时缓存中的 Item 需要再次调用 onBindViewHolder()方法;
[](
)缓存原理分析
首先看源码是现有大致的思路,根据上面讲到的缓存流程来看,在 RecyclerView 滑动时会查找缓存中是否有匹配的数据。所以缓存机制的工作是从滑动开始的,那么滑动就会触发 View 的工作流程(measure、layout、draw)根据接下来这个思路去看代码。
具体特别细节的源码就不再一一叙述,看源码也不是为了记住所有的细节,而是为了学习主要的逻辑跟设计的模式。
RecyclerView 的测量、绘制都是委托给 LayoutManager 的,在 LinearLayoutManager 中,可以找到相关的代码。
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
//...
detachAndScrapAttachedViews(recycler);
//...
}
在 LinearLayoutManager 的 onLayoutChildren()方法中只截取了这一行代码,它的作用就是处理 Scrap 缓存以及 detach 的 ViewHolder。
/**
Temporarily detach and scrap all currently attached child views. Views will be scrapped
into the given Recycler. The Recycler may prefer to reuse scrap views before
other views that were previously recycled.
@param recycler Recycler to scrap views into
*/
public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
final int childCount = getChildCount();
for (int i = childCount - 1; i >= 0; i--) {
final View v = getCh
ildAt(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()) {
// ViewHolder 是 indalid 的
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
// ViewHolder 对应的 View 是 attach 的
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
在 scrapOrRecycleView()方法中有两个逻辑分支,分别处理在屏幕中的 Item 以及需要回收的 Item。我们先看如何处理屏幕中的 Item。
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()) {
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
这里主要处理的就是将 ViewHolder 加入 Scrap 缓存中。接下来分析另一种情况。
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();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
cachedViewSize--;
}
int targetCacheIndex = cachedViewSize;
//...
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
//...
}
recycleViewHolderInternal()方法中主要是对 Cache 缓存和 RecycledViewPool 缓存进行处理。判断依据主要是根据 ViewHolde 的标志位进行判断。
对于 Cache 缓存:缓存池满了以后就移出第 1 个然后加入新的缓存数据。
对于 RecycledViewPool 缓存:主要根据 ViewHolder 的标志位进行判断(比如 VIewHolder 有 FLAG_REMOVED 标志)就会加入到 RecycledViewPool 缓存。
在上面的代码中主要分析了 RecyclerView 中缓存数据的来源,接下来继续分析如何使用缓存。按照之前的思路,在 LayoutManager 执行 onLayoutChildren()方法时会获取新的 Item 这个时候就会触发 RecyclerView 的缓存机制。
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
//...
fill(recycler, mLayoutState, state, false);
//...
}
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
//...
layoutChunk(recycler, state, layoutState, layoutChunkResult);
//...
}
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
//...
View view = layoutState.next(recycler);
//...
}
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
mCurrentPosition += mItemDirection;
return view;
}
这次就不啰嗦了,直接把前面需要执行的流程都列了出来,可以看到 LayoutManager 是通过 fill()方法来填充布局的,在这个过程中最终会通过 Recycler 的 getViewForPosition()方法获取到需要添加的 View。接下来就从 Recycler 中进行分析。
在 Recycler 中获取 View 的方法最终是在 tryGetViewHolderForPositionByDeadline()方法执行的:
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
boolean dryRun, long deadlineNs) {
评论