RecyclerView源码解析之缓存机制

RecyclerView源码解析之缓存机制

一、简介

RecyclerView是谷歌官方出的一个用于大量数据展示的新控件,可以代替传统的ListView,更加强大和灵活。

事实上,RecyclerView在性能上对比listView并没有显著的提示,RecyclerView更大的亮点在于提供了局部刷新的接口,通过局部刷新,就能避免调用许多无用的bindView。

因此列表页需要支持动画,或者频繁更新或局部刷新,建议使用RecyclerView,而且通过viewType方便对item进行扩展;如果只是一个简单的列表,使用listView更加方便;

二、缓存机制

列表控件的核心无非就是看如何缓存和复用item,达到效率最大化,因此有必要从源码的角度来仔细分析一下。

分析代码之前,先看下缓存的结构(四级缓存):
《RecyclerView源码解析之缓存机制》

源码不建议从头看,代码太多也看不完,需要找到合适的切入点,这里的切入点就是Recycler.getViewForPosition

        View getViewForPosition(int position, boolean dryRun) {
            //省略非关键代码
            // 1) Find from scrap by position
            if (holder == null) {
                holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
                if (holder != null) {
                    //省略非关键代码
                }
            }

                //此处省略非关键代码
                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 recycler
                    holder = getRecycledViewPool().getRecycledView(type);
                    if (holder != null) {
                        //此处省略非关键代码
                    }
                }
                if (holder == null) {
                    holder = mAdapter.createViewHolder(RecyclerView.this, type);
                }
            }
            //此处省略非关键代码
            return holder.itemView;
        }

为了理清楚流程,删除了很多非关键代码,我们可以看到先是
getScrapViewForPosition(包含mAttachedScrap&mCacheViews)->mViewCacheExtension->mRecycledViewPool->mAdapter.createViewHolder(..),从缓存中获取viewHolder,找不到最终调用createViewHolder创建viewHolder,createViewHolder这个只是一个抽象方法,由用户自己实现。

完整流程图如下:
《RecyclerView源码解析之缓存机制》

当item滑离屏幕的时候,会被缓存起来,这里的缓存指的是:mCacheViews和mRecycledViewPool,mCacheViews是在哪里被赋值的?很容易找到recycleViewHolderInternal方法

        void recycleViewHolderInternal(ViewHolder holder) {
            // 省略非关键代码
            if (forceRecycle || holder.isRecyclable()) {
                if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED
                        | ViewHolder.FLAG_UPDATE)) {
                    // Retire oldest cached view
                    int cachedViewSize = mCachedViews.size();
                    if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
                        recycleCachedViewAt(0);
                        cachedViewSize --;
                    }
                    if (cachedViewSize < mViewCacheMax) {
                        mCachedViews.add(holder);
                        cached = true;
                    }
                }
                if (!cached) {
                    addViewHolderToRecycledViewPool(holder);
                    recycled = true;
                }
            } else if (DEBUG) {
                Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will "
                        + "re-visit here. We are still removing it from animation lists");
            }
        // ...
        }

mViewCacheMax值为2,意味着mCachedViews最多缓存2个viewHolder,当超过2个的时候,会把mCachedViews.get(0)的viewHolder调用addViewHolderToRecycledViewPool接口转存到mRecycledViewPool,然后将最近移出屏幕的viewHolder存到mCachedViews中

举个例子:
《RecyclerView源码解析之缓存机制》

屏幕上首先看到的3个item对应的viewHolder都是调用createViewHolder创建的,创建好了后调用onBindViewHolder绑定数据,这个时候我们将test0滑出屏幕

// recycleViewHolderInternal接口逻辑
if (cachedViewSize < mViewCacheMax) {
    mCachedViews.add(holder);
    cached = true;
}

由于最开始mCachedViews是空的,条件成立,test0对应的viewHolder加入到mCachedViews,接着将test1滑出屏幕也是加入到mCachedViews中,此时mCachedViews已经满了,再将test2滑出屏幕情况执行的是,test0会从mCachedViews中移除,转存到mRecycledViewPool中

// recycleViewHolderInternal接口逻辑
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
    recycleCachedViewAt(0);
    cachedViewSize --;
}

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);
            mCachedViews.remove(cachedViewIndex);
        }

这个时候mRecycledViewPool存的是test0,而mCachedViews存的是test1和test2,屏幕看到的是test3、test4和test5,当test6需要得到viewHolder的时候,其实是从mRecycledViewPool中获取的test0,test0被复用。到这里,屏幕显示3个viewHolder,另外3个缓存到mCachedViews&mRecycledViewPool中,所以总共创建了6个viewHolder,也就是后续列表滑动都是这6个viewHolder循环复用

另外mViewCacheExtension是自定义的缓存这里不做讨论,而mAttachedScrap是在layout过程中被赋值的,感兴趣可以查下源码~

三、对比ListView

ListView二级缓存
《RecyclerView源码解析之缓存机制》

ListView和RecyclerView缓存机制基本一致:

1). mActiveViews和mAttachedScrap功能相似,意义在于快速重用屏幕上可见的列表项ItemView,而不需要重新createView和bindView;

2). mScrapView和mCachedViews + mReyclerViewPool功能相似,意义在于缓存离开屏幕的ItemView,目的是让即将进入屏幕的ItemView重用.

3). RecyclerView的优势在于a.mCacheViews的使用,可以做到屏幕外的列表项ItemView进入屏幕内时也无须bindView快速重用;b.mRecyclerPool可以供多个RecyclerView共同使用,在特定场景下,如viewpaper+多个列表页下有优势.客观来说,RecyclerView在特定场景下对ListView的缓存机制做了补强和完善。

    原文作者:chengyuan9160
    原文地址: https://blog.csdn.net/jett2357/article/details/72864353
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞