RecyclerView緩存分析

在之前渲染過(guò)程中,layoutChunk方法調(diào)用View view = layoutState.next(recycler)代碼,該代碼獲取一個(gè)view視圖抖苦,內(nèi)部包含緩存邏輯领炫。RecyclerView緩存共有四級(jí),以下簡(jiǎn)單說(shuō)下四級(jí)緩存遗增。

層級(jí)
一級(jí)緩存 mAttachedScrap:數(shù)據(jù)沒有變化的ViewHolder 叫惊; mChangedScrap:數(shù)據(jù)已經(jīng)改變的 ViewHolder
二級(jí)緩存 mCachedViews:滑出屏幕之外的 ViewHolder
三級(jí)緩存 mViewCacheExtensions:自定義的緩存池
四級(jí)緩存 mRecyclerPool:mCachedViews無(wú)法存放時(shí)候放置的地方,需要重新onBindViewHolder()

其他:mHiddenViews 和Recycler無(wú)關(guān)做修。在子view被移出屏幕的動(dòng)畫執(zhí)行期間暫時(shí)緩存viewHolder霍狰。

首先看下LayoutState里面的next方法。

View next(RecyclerView.Recycler recycler) {
    //執(zhí)行l(wèi)ayoutForPredictiveAnimations之后mScrapList不為null
    //mScrapList 實(shí)質(zhì)為不可修改的mAttachedScrap
    //這邊忽略饰及,一般mScrapList==null
    if (mScrapList != null) {
        return nextViewFromScrapList();
    }
    //獲取一個(gè)view
    final View view = recycler.getViewForPosition(mCurrentPosition);
    mCurrentPosition += mItemDirection;
    return view;
}

在mScrapList == null 的情況下getViewForPosition方法進(jìn)一步獲取View

@NonNull
public View getViewForPosition(int position) {
       return getViewForPosition(position, false);
}

View getViewForPosition(int position, boolean dryRun) {
      return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
}

ViewHolder tryGetViewHolderForPositionByDeadline(int position,
                                                 boolean dryRun, long deadlineNs) {
    // ...省略
    // 0) If there is a changed scrap, try to find from there
    if (mState.isPreLayout()) {
        // 預(yù)加載下蔗坯,通過(guò)mChangedScrap獲取ViewHolder
        holder = getChangedScrapViewForPosition(position);
        fromScrapOrHiddenOrCache = holder != null;
    }
   // 1) Find by position from scrap/hidden list/cache
    if (holder == null) {
        // 通過(guò)mAttachedScrap、HiddenView(隱藏的view) 燎含、mCachedViews來(lái)獲取ViewHolder
        holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
        //. ..省略
   }
   if (holder == null) {
        //...省略
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 2) Find from scrap/cache via stable ids, if exists
        if (mAdapter.hasStableIds()) { 
            // stableId設(shè)置為true的時(shí)候宾濒,相當(dāng)于調(diào)用了viewholder中的view的requestFocus()方法。這個(gè)方法的作用是給這個(gè)請(qǐng)求requestFocus()的方法的view的下面的那個(gè)view焦點(diǎn)屏箍。
            //根據(jù)ids來(lái)查找ViewHolder绘梦,設(shè)置了id,那么會(huì)忽略標(biāo)志位強(qiáng)行復(fù)用viewHolder赴魁。
            holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
                    type, dryRun);
            //...省略
        }
       if (holder == null && mViewCacheExtension != null) {
            // We are NOT sending the offsetPosition because LayoutManager does not
            // know it.
            //自定義的mViewCacheExtensions緩存獲取ViewHolder
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
            if (view != null) {
                holder = getChildViewHolder(view);
                //...省略
            }
        }
        if (holder == null) { 
             holder = getRecycledViewPool().getRecycledView(type);
             //mRecyclerPool中獲取ViewHolder
        }
         //..省略
        if (holder == null) {
            //..省略
            holder = mAdapter.createViewHolder(RecyclerView.this, type);
            //..省略
            }
            //..省略
        }
    }
     //..省略
     boolean bound = false;
     //bindViewHolder
    if (mState.isPreLayout() && holder.isBound()){
       bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
    }
    //..省略
    return holder;
}

tryGetViewHolderForPositionByDeadline 中的代碼展示完卸奉,這邊展示的是一級(jí)緩存mChangedScrap、mAttachedScrap和二級(jí)mCachedViews的邏輯颖御。以下為在一級(jí)榄棵、二級(jí)緩存中獲取ViewHolder常用的代碼。

ViewHolder getChangedScrapViewForPosition(int position) {
        // If pre-layout, check the changed scrap for an exact match.
        final int changedScrapSize;
        if (mChangedScrap == null || (changedScrapSize = mChangedScrap.size()) == 0) {
             return null;
        }
        // find by position
       for (int i = 0; i < changedScrapSize; i++) {
          //通過(guò)position查找viewholder
          final ViewHolder holder = mChangedScrap.get(i);
          //...省略
   
       if (mAdapter.hasStableIds()) {
            //再次校驗(yàn)id潘拱,獲取viewhodler
            final int offsetPosition = mAdapterHelper.findPositionOffset(position);
            if (offsetPosition > 0 && offsetPosition < mAdapter.getItemCount()) {
                 final long id = mAdapter.getItemId(offsetPosition);
                 for (int i = 0; i < changedScrapSize; i++) {
                      final ViewHolder holder = mChangedScrap.get(i);
                      if (!holder.wasReturnedFromScrap() && holder.getItemId() == id) {
                           holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                         return holder;
                       }
                  }
             }
        }
       return null;
 }

ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
    final int scrapCount = mAttachedScrap.size();
    // Try first for an exact, non-invalid match from scrap.
    for (int i = 0; i < scrapCount; i++) {
            //獲取一個(gè)精確的疹鳄,合法的scrap
            final ViewHolder holder = mAttachedScrap.get(i);
             //...省略
             return holder;
        }
    }
   if (!dryRun) {
        View view = mChildHelper.findHiddenNonRemovedView(position);
        if (view != null) {
            // This View is good to be used. We just need to unhide, detach and move to the
            // scrap list.
            //隱藏但未被移除的view
            final ViewHolder vh = getChildViewHolderInt(view);
            //...省略
            return vh;
        }
    }
    // Search in our first-level recycled view cache.
    //緩存獲取
    final int cacheSize = mCachedViews.size();
    for (int i = 0; i < cacheSize; i++) {
        final ViewHolder holder = mCachedViews.get(i);
        // invalid view holders may be in cache if adapter has stable ids as they can be
        // retrieved via getScrapOrCachedViewForId
        if (!holder.isInvalid() && holder.getLayoutPosition() == position
                && !holder.isAttachedToTransitionOverlay()) {
            if (!dryRun) {
                mCachedViews.remove(i);
            }
            if (DEBUG) {
                Log.d(TAG, "getScrapOrHiddenOrCachedHolderForPosition(" + position
                        + ") found match in cache: " + holder);
            }
            return holder;
        }
    }
    return null;
}

//Adapter的notifyDataSetChanged會(huì)令所有viewHolder為FLAG_UPDATE和FLAG_INVALID
//設(shè)置id,會(huì)忽略標(biāo)志位置芦岂,強(qiáng)行獲取ViewHolder瘪弓。確保不多次創(chuàng)建view。
ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
    // Look in our attached views first
   final int count = mAttachedScrap.size();
    for (int i = count - 1; i >= 0; i--) {
        final ViewHolder holder = mAttachedScrap.get(i);
        //根據(jù)id來(lái)查找ViewHolder
        if (holder.getItemId() == id && !holder.wasReturnedFromScrap()) {
            if (type == holder.getItemViewType()) {
                holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
                if (holder.isRemoved()) {
                    // this might be valid in two cases:
                    // > item is removed but we are in pre-layout pass
                    // >> do nothing. return as is. make sure we don't rebind
                    // > item is removed then added to another position and we are in
                    // post layout.
                    // >> remove removed and invalid flags, add update flag to rebind
                    // because item was invisible to us and we don't know what happened in
                    // between.
                    if (!mState.isPreLayout()) {
                        holder.setFlags(ViewHolder.FLAG_UPDATE, ViewHolder.FLAG_UPDATE
                                | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED);
                    }
                }
                return holder;
            } else if (!dryRun) {
                // if we are running animations, it is actually better to keep it in scrap
                // but this would force layout manager to lay it out which would be bad.
                // Recycle this scrap. Type mismatch.
                mAttachedScrap.remove(i);
                removeDetachedView(holder.itemView, false);
                quickRecycleScrapView(holder.itemView);
            }
        }
    }

    // Search the first-level cache
    final int cacheSize = mCachedViews.size();
    for (int i = cacheSize - 1; i >= 0; i--) {
        final ViewHolder holder = mCachedViews.get(i);
        if (holder.getItemId() == id && !holder.isAttachedToTransitionOverlay()) {
            if (type == holder.getItemViewType()) {
                if (!dryRun) {
                    mCachedViews.remove(i);
                }
                return holder;
            } else if (!dryRun) {
                recycleCachedViewAt(i);
                return null;
            }
        }
    }
    return null;
}

以上完成了ViewHolder的創(chuàng)建盔腔,接下來(lái)查看各個(gè)緩存的生成杠茬。

  • mChangedScrap 和 mAttachedScrap
    以下代碼會(huì)將變化的viewholder存入到mChangedScrap、不變的viewholder存入到mAttachedScrap
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
        //..省略
       detachAndScrapAttachedViews(recycler);
       //..省略
}

public void detachAndScrapAttachedViews(@NonNull Recycler recycler) {
    final int childCount = getChildCount();
    for (int i = childCount - 1; i >= 0; i--) {
        final View v = getChildAt(i);
        scrapOrRecycleView(recycler, i, v);
    }
}

private void scrapOrRecycleView(Recycler recycler, int index, View view) {
    final ViewHolder viewHolder = getChildViewHolderInt(view);
    if (viewHolder.shouldIgnore()) {
        if (DEBUG) {
            Log.d(TAG, "ignoring view " + viewHolder);
        }
        return;
    }
    if (viewHolder.isInvalid() && !viewHolder.isRemoved()
            && !mRecyclerView.mAdapter.hasStableIds()) {
        removeViewAt(index);
        recycler.recycleViewHolderInternal(viewHolder);
    } else {
        detachViewAt(index);
        recycler.scrapView(view);
        mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
    }
}
  • mCachedViews
    當(dāng)item被滑出屏幕后會(huì)被保存在mCachedViews中弛随,在dispatchLayoutStep1方法中調(diào)用
    processAdapterUpdatesAndSetAnimationFlags時(shí)候會(huì)堅(jiān)持mCachedViews瓢喉,如果mCachedViews有變化,則會(huì)mCachedViews中移除掉ViewHolder舀透。
void recycleViewHolderInternal(ViewHolder holder) {
 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) {
        addViewHolderToRecycledViewPool(holder, true);
        recycled = true;
    }
}
  • mRecyclerPool
    當(dāng)mCachedViews被填滿溢出的viewholder栓票,會(huì)被移動(dòng)到RecyclerRool中。
  • mHiddenViews 和 mUnmodifiableAttachedScrap :mHiddenViews當(dāng)view被移除屏幕又要執(zhí)行動(dòng)畫,所有會(huì)暫存到mHiddenViews中走贪。mUnmodifiableAttachedScrap實(shí)為不可修改的mAttachedScrap佛猛,在執(zhí)行動(dòng)畫區(qū)間會(huì)暫時(shí)充當(dāng)mAttachedScrap中的一項(xiàng),只有動(dòng)畫只想完成后會(huì)刪除mAttachedScrap中的viewholder坠狡。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末继找,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子逃沿,更是在濱河造成了極大的恐慌婴渡,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凯亮,死亡現(xiàn)場(chǎng)離奇詭異边臼,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)假消,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門柠并,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人富拗,你說(shuō)我怎么就攤上這事臼予。” “怎么了媒峡?”我有些...
    開封第一講書人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵瘟栖,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我谅阿,道長(zhǎng),這世上最難降的妖魔是什么酬滤? 我笑而不...
    開封第一講書人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任签餐,我火速辦了婚禮,結(jié)果婚禮上盯串,老公的妹妹穿的比我還像新娘氯檐。我一直安慰自己,他們只是感情好体捏,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開白布冠摄。 她就那樣靜靜地躺著,像睡著了一般几缭。 火紅的嫁衣襯著肌膚如雪河泳。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,610評(píng)論 1 305
  • 那天年栓,我揣著相機(jī)與錄音拆挥,去河邊找鬼。 笑死某抓,一個(gè)胖子當(dāng)著我的面吹牛纸兔,可吹牛的內(nèi)容都是我干的惰瓜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼汉矿,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼崎坊!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起洲拇,我...
    開封第一講書人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤流强,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后呻待,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體打月,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年蚕捉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了奏篙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡迫淹,死狀恐怖秘通,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情敛熬,我是刑警寧澤肺稀,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站应民,受9級(jí)特大地震影響话原,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜诲锹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一繁仁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧归园,春花似錦黄虱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至桥爽,卻和暖如春朱灿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背聚谁。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工母剥, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓环疼,卻偏偏與公主長(zhǎng)得像习霹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子炫隶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

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