android RecyclerView缓存机制

一:前言

RecyclerView是我们开发中很常用的一个控件,但是阿简没有仔细的去了解以下其实现原理,最近正好在搞一个需求,看了下源码,一起学习下~

二:关于RecyclerView

在我们使用RecyclerView的时候不免使用Adapter去处理数据,处理完了数据之后再recyclerView.setAdapter(_)去显示相关item,可以看出,UI和数据是分开的,通过对recyclerView源码的学习,recyclerView在设计的时候也遵循这个规则

三:RecyclerView.class中Recycler内部类说明

Recycler内部类:
mAttachedScrap:缓存当前屏幕可见的ViewHolder
mChangedScrap:ViewHolder更新的时候会保存在这个ArrayList里面
mCachedViews:缓存滑动时即将与RecyclerView分离的ViewHolder,按子View的position或id缓存,默认最多存放2个
mViewCacheExtension:用户自定义缓存
mRecyclerPool:缓存池(后面说明)

    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;

四:什么时候会使用到缓存机制?入口在哪?

4.1:
RecyclerView是一个自定义viewGroup,所以我们从onTouchEvent(MotionEvent event),当event等于MotionEvent.ACTION_MOVE就是滑动的时候开始分析:

            case MotionEvent.ACTION_MOVE: {
               ...省略
                    if (scrollByInternal(
                            canScrollHorizontally ? dx : 0,
                            canScrollVertically ? dy : 0,
                            vtev)) {
                        getParent().requestDisallowInterceptTouchEvent(true);
                    }
                    if (mGapWorker != null && (dx != 0 || dy != 0)) {
                        mGapWorker.postFromTraversal(this, dx, dy);
                    }
                }
            } break;

我们来看一下从scrollByInternal()一直点进去之后的调用链
OnTouch --> scrollByInternal --> scrollStep --> mLayout.scrollVerticallyBy --> scrollBy --> fill --> layoutChunk --> layoutState.next --> getViewForPosition --> tryGetViewHolderForPositionByDeadline

4.2:
当RecyclerView中item发生变化的时候,就是Adapter调用了notifydatasetchanged这些一系列的方法时候,会触发以下调用链:
notifyDataSetChanged-->mObservable.notifyChanged --> (RecyclerViewDataObserver)mObservers.get(i).onChanged --> requestLayout
可以看到最后调用到了requestLayout,所以会重新执行RecyclerView的onMeasure-onLayout-onDraw方法,然后就会开始调用缓存操作:
onMeasure()/onLayout() -->dispatchLayoutStep1()/dispatchLayoutStep2()--> mLayout.onLayoutChildren -->onLayoutChildren() -->fill()->layoutChunk() -->next() -->getViewForPosition()-->tryGetViewHolderForPositionByDeadline

五:缓存机制分析

由第四步可知最后都是调用到了fill()->layoutChunk() -->next() -->getViewForPosition()-->tryGetViewHolderForPositionByDeadline
那我们来看tryGetViewHolderForPositionByDeadline做了什么(这个方法也是我们常说的四级缓存的实现)

//根据传入的position获取ViewHolder 
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    ...省略    
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
    //预布局 属于特殊情况 从mChangedScrap中获取ViewHolder
    if (mState.isPreLayout()) {
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }
    if (holder == null) {
        //1、尝试从mAttachedScrap中获取ViewHolder,此时获取的是屏幕中可见范围中的ViewHolder
        //2、mAttachedScrap缓存中没有的话,继续从mCachedViews尝试获取ViewHolder
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
     ...省略
    }
    if (holder == null) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        ...省略
        final int type = mAdapter.getItemViewType(offsetPosition);
        //如果Adapter中声明了Id,尝试从id中获取,这里不属于缓存
        if (mAdapter.hasStableIds()) {
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
        }
        if (holder == null && mViewCacheExtension != null) {
            3、从自定义缓存mViewCacheExtension中尝试获取ViewHolder,该缓存需要开发者实现
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
            if (view != null) {
                holder = getChildViewHolder(view);
            }
        }
        if (holder == null) { // fallback to pool
            //4、从缓存池mRecyclerPool中尝试获取ViewHolder
            holder = getRecycledViewPool().getRecycledView(type);
            if (holder != null) {
                //如果获取成功,会重置ViewHolder状态,所以需要重新执行Adapter#onBindViewHolder绑定数据
                holder.resetInternal();
                if (FORCE_INVALIDATE_DISPLAY_LIST) {
                    invalidateDisplayListInt(holder);
                }
            }
        }
        if (holder == null) {
            ...省略
          //5、若以上缓存中都没有找到对应的ViewHolder,最终会调用Adapter中的onCreateViewHolder创建一个
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
        }
    }
 
    boolean bound = false;
    if (mState.isPreLayout() && holder.isBound()) {
        holder.mPreLayoutPosition = position;
    } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        //6、如果需要绑定数据,会调用Adapter#onBindViewHolder来绑定数据
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
    ...省略
    return holder;
}

可见以下流程图:


六:RecyclerView.class中RecycledViewPool内部类说明

通过上一步的分析可知,前面三级缓存是不用重新绑定viewHolder的,只有从RecycledViewPool中获取到的RecycledViewPool需要重新绑定数。
通过下面一种场景可以帮助理解:如一个APP的主界面ViewPager有多个fragment,其中某两个fragment都会用到RecyclerView,而且其中的一些item还是相同的,那么此时我们为了节约内存,就可以使用
RecycledViewPool让这两个RecyclerView共享同一个四级缓存pool

RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool();
MyAdapter myAdapter = new MyAdapter(datas);
RecyclerView.ViewHolder viewHolder = myAdapter.createViewHolder(recyclerView, 0);
pool.putRecycledView(viewHolder);
recyclerView.setRecycledViewPool(pool);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(myAdapter);

END

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

推荐阅读更多精彩内容