android RecyclerView緩存機(jī)制

一:前言

RecyclerView是我們開發(fā)中很常用的一個(gè)控件,但是阿簡沒有仔細(xì)的去了解以下其實(shí)現(xiàn)原理杯矩,最近正好在搞一個(gè)需求,看了下源碼,一起學(xué)習(xí)下~

二:關(guān)于RecyclerView

在我們使用RecyclerView的時(shí)候不免使用Adapter去處理數(shù)據(jù)渐排,處理完了數(shù)據(jù)之后再recyclerView.setAdapter(_)去顯示相關(guān)item,可以看出灸蟆,UI和數(shù)據(jù)是分開的驯耻,通過對recyclerView源碼的學(xué)習(xí),recyclerView在設(shè)計(jì)的時(shí)候也遵循這個(gè)規(guī)則

三:RecyclerView.class中Recycler內(nèi)部類說明

Recycler內(nèi)部類:
mAttachedScrap:緩存當(dāng)前屏幕可見的ViewHolder
mChangedScrap:ViewHolder更新的時(shí)候會保存在這個(gè)ArrayList里面
mCachedViews:緩存滑動時(shí)即將與RecyclerView分離的ViewHolder,按子View的position或id緩存可缚,默認(rèn)最多存放2個(gè)
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;

四:什么時(shí)候會使用到緩存機(jī)制霎迫?入口在哪?

4.1:
RecyclerView是一個(gè)自定義viewGroup城看,所以我們從onTouchEvent(MotionEvent event)女气,當(dāng)event等于MotionEvent.ACTION_MOVE就是滑動的時(shí)候開始分析:

            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()一直點(diǎn)進(jìn)去之后的調(diào)用鏈
OnTouch --> scrollByInternal --> scrollStep --> mLayout.scrollVerticallyBy --> scrollBy --> fill --> layoutChunk --> layoutState.next --> getViewForPosition --> tryGetViewHolderForPositionByDeadline

4.2:
當(dāng)RecyclerView中item發(fā)生變化的時(shí)候,就是Adapter調(diào)用了notifydatasetchanged這些一系列的方法時(shí)候测柠,會觸發(fā)以下調(diào)用鏈:
notifyDataSetChanged-->mObservable.notifyChanged --> (RecyclerViewDataObserver)mObservers.get(i).onChanged --> requestLayout
可以看到最后調(diào)用到了requestLayout炼鞠,所以會重新執(zhí)行RecyclerView的onMeasure-onLayout-onDraw方法,然后就會開始調(diào)用緩存操作:
onMeasure()/onLayout() -->dispatchLayoutStep1()/dispatchLayoutStep2()--> mLayout.onLayoutChildren -->onLayoutChildren() -->fill()->layoutChunk() -->next() -->getViewForPosition()-->tryGetViewHolderForPositionByDeadline

五:緩存機(jī)制分析

由第四步可知最后都是調(diào)用到了fill()->layoutChunk() -->next() -->getViewForPosition()-->tryGetViewHolderForPositionByDeadline
那我們來看tryGetViewHolderForPositionByDeadline做了什么(這個(gè)方法也是我們常說的四級緩存的實(shí)現(xiàn))

//根據(jù)傳入的position獲取ViewHolder 
ViewHolder tryGetViewHolderForPositionByDeadline(int position,
        boolean dryRun, long deadlineNs) {
    ...省略    
    boolean fromScrapOrHiddenOrCache = false;
    ViewHolder holder = null;
    //預(yù)布局 屬于特殊情況 從mChangedScrap中獲取ViewHolder
    if (mState.isPreLayout()) {
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }
    if (holder == null) {
        //1轰胁、嘗試從mAttachedScrap中獲取ViewHolder,此時(shí)獲取的是屏幕中可見范圍中的ViewHolder
        //2谒主、mAttachedScrap緩存中沒有的話,繼續(xù)從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榛斯,該緩存需要開發(fā)者實(shí)現(xiàn)
            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狀態(tài)驮俗,所以需要重新執(zhí)行Adapter#onBindViewHolder綁定數(shù)據(jù)
                holder.resetInternal();
                if (FORCE_INVALIDATE_DISPLAY_LIST) {
                    invalidateDisplayListInt(holder);
                }
            }
        }
        if (holder == null) {
            ...省略
          //5懂缕、若以上緩存中都沒有找到對應(yīng)的ViewHolder,最終會調(diào)用Adapter中的onCreateViewHolder創(chuàng)建一個(gè)
            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王凑、如果需要綁定數(shù)據(jù)搪柑,會調(diào)用Adapter#onBindViewHolder來綁定數(shù)據(jù)
        bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
    ...省略
    return holder;
}

可見以下流程圖:


六:RecyclerView.class中RecycledViewPool內(nèi)部類說明

通過上一步的分析可知,前面三級緩存是不用重新綁定viewHolder的索烹,只有從RecycledViewPool中獲取到的RecycledViewPool需要重新綁定數(shù)工碾。
通過下面一種場景可以幫助理解:如一個(gè)APP的主界面ViewPager有多個(gè)fragment,其中某兩個(gè)fragment都會用到RecyclerView,而且其中的一些item還是相同的百姓,那么此時(shí)我們?yōu)榱斯?jié)約內(nèi)存渊额,就可以使用
RecycledViewPool讓這兩個(gè)RecyclerView共享同一個(gè)四級緩存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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市瓣戚,隨后出現(xiàn)的幾起案子端圈,更是在濱河造成了極大的恐慌,老刑警劉巖子库,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舱权,死亡現(xiàn)場離奇詭異,居然都是意外死亡仑嗅,警方通過查閱死者的電腦和手機(jī)宴倍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門张症,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鸵贬,你說我怎么就攤上這事俗他。” “怎么了阔逼?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵兆衅,是天一觀的道長。 經(jīng)常有香客問我嗜浮,道長羡亩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任危融,我火速辦了婚禮畏铆,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吉殃。我一直安慰自己辞居,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布蛋勺。 她就那樣靜靜地躺著瓦灶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪抱完。 梳的紋絲不亂的頭發(fā)上倚搬,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機(jī)與錄音乾蛤,去河邊找鬼。 笑死捅僵,一個(gè)胖子當(dāng)著我的面吹牛家卖,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播庙楚,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼上荡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了馒闷?” 一聲冷哼從身側(cè)響起酪捡,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎纳账,沒想到半個(gè)月后逛薇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡疏虫,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年永罚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了啤呼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡呢袱,死狀恐怖官扣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情羞福,我是刑警寧澤惕蹄,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站治专,受9級特大地震影響卖陵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜看靠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一赶促、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧挟炬,春花似錦鸥滨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至粥喜,卻和暖如春凸主,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背额湘。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工卿吐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锋华。 一個(gè)月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓嗡官,卻偏偏與公主長得像,于是被迫代替她去往敵國和親毯焕。 傳聞我的和親對象是個(gè)殘疾皇子衍腥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容