04-RecyclerView缓存机制与实践

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 或直接创建。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,406评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,732评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,711评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,380评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,432评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,301评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,145评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,008评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,443评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,649评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,795评论 1 347
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,501评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,119评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,731评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,865评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,899评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,724评论 2 354

推荐阅读更多精彩内容