RecyclerView缓存机制
缓存回收是什么:缓存满的时候,根据指定的策略清理缓存。
缓存什么:ViewHolder
为什么缓存回收:屏幕中的数据移出屏幕时,需要销毁,重新创建,有性能开销。
怎么缓存:
1.屏幕内缓存:N;屏幕内滚动的item数目 【Scrap缓存】不参与滚动的回收复用
代码:
Recycler类:行数:5896-6937
public final class Recycler {
变量:
mAttachedScrap:List<ViewHolder>:表示未与RecyclerView分离的ViewHolder列表
mChangedScrap:List<ViewHolder>:表示数据已经改变的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);
}
}
2.屏幕外缓存:保证屏幕外的 view【CacheView缓存】保证屏幕外的View 在进入屏幕时无须绑定数据 即 调用OnBind
代码:Recycler类
变量:mCachedViews
方法:recycleViewHolderInternal
static final int DEFAULT_CACHE_SIZE = 2;
int mViewCacheMax = DEFAULT_CACHE_SIZE;
void recycleViewHolderInternal(ViewHolder holder) {
//此时的holder是屏幕内View,有父亲,即屏幕内缓存
if (holder.isScrap() || holder.itemView.getParent() != null) {
throw new IllegalArgumentException(
"Scrapped or attached views may not be recycled. isScrap:"
+ holder.isScrap() + " isAttached:"
+ (holder.itemView.getParent() != null) + exceptionLabel());
}
...
boolean cached = false;
boolean recycled = false;
if (DEBUG && mCachedViews.contains(holder)) {
throw new IllegalArgumentException("cached view received recycle internal? "
+ holder + exceptionLabel());
}
if (forceRecycle || holder.isRecyclable()) {
//屏幕外缓存,数量为2
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;
if (ALLOW_THREAD_GAP_WORK
&& cachedViewSize > 0
&& !mPrefetchRegistry.lastPrefetchIncludedPosition(holder.mPosition)) {
// when adding the view, skip past most recently prefetched views
int cacheIndex = cachedViewSize - 1;
while (cacheIndex >= 0) {
int cachedPos = mCachedViews.get(cacheIndex).mPosition;
if (!mPrefetchRegistry.lastPrefetchIncludedPosition(cachedPos)) {
break;
}
cacheIndex--;
}
targetCacheIndex = cacheIndex + 1;
}
//屏幕外缓存添加数据
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
//如果不用屏幕外缓存,可以加 ViewHolder 到缓存池中
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
} else {
...
}
...
mViewInfoStore.removeViewHolder(holder);
...
}
滑动过程中的回收和复用都是先处理的这个 List,这个集合里存的 ViewHolder 的原本数据信息都在,所以可以直接添加到 RecyclerView 中显示,不需要再次重新 onBindViewHolder()。
移出屏幕的 item 是一个表项,再次回到界面,ViewGroup-addView,对应代码中: mCachedViews.add(targetCacheIndex, holder);
后来调用 代码 #initChildrenHelper 即 RecyclerView.this.addView(child, index);
private void initChildrenHelper() {
mChildHelper = new ChildHelper(new ChildHelper.Callback() {
@Override
public int getChildCount() {
return RecyclerView.this.getChildCount();
}
@Override
public void addView(View child, int index) {
if (VERBOSE_TRACING) {
TraceCompat.beginSection("RV addView");
}
RecyclerView.this.addView(child, index);
if (VERBOSE_TRACING) {
TraceCompat.endSection();
}
dispatchChildAttached(child);
}
...
});
}
3.缓存池:5M 二级缓存 每个ViewType5
【RecycledViewPool】大小为 viewholder.size,存在这里的 ViewHolder 的数据信息会被重置掉,相当于 ViewHolder 是一个重新创建的一样,所以需要重新调用 onBindViewHolder 来绑定数据。
时机:当屏幕外缓存 数量 >= 2,ViewHolder就会放进缓存池 # addViewHolderToRecycledViewPool
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);
}
void addViewHolderToRecycledViewPool(@NonNull ViewHolder holder, boolean dispatchRecycled) {
...
getRecycledViewPool().putRecycledView(holder);
}
知识点:
类 :Recycler
初始化onLayout中,有一个 mAttachedxxx 集合,临时存在即将显示的第一屏的view,在最后一次onLayout结束之后,会从将该mAttachedxxx里面的view渲染到第一屏页面上。
当向上滑动过程中,都是先将滑出屏幕的view放到Recycle中,然后从Recycle中经过转换,将该view渲染到界面上。
原理:
数据发生变化后,viewholder 被 detach 掉后 缓存在 mChangedScrap 之中,在这里拿到的 viewHolder后续需要重新绑定。
滑动列表时,一旦item超出了屏幕,那么就会被放入到mCachedViews 中,如果满了,就会将“尾部”的元素移动到pool中,如果pool也满了,那么就会被丢弃,等待回收。
从 被给的 position 中 尽力地 获取 ViewHolder,或从 Recycler scrap,cache,RecycledViewPool 或直接创建。
网友评论