Android RecyclerView緩存復(fù)用原理解析
一、牽出緩存
都有哪些緩存,作用是什么,為什么這么設(shè)計(jì)
1.緩存還在屏幕內(nèi)的ViewHolder——Scrap緩存
Scrap是RecyclerView中最輕量的緩存,它不參與滑動時的回收復(fù)用,只是作為重新布局時的一種臨時緩存,緩存(保存)動作只發(fā)生在重新布局時,布局完成后就要清空緩存。它的目的是,緩存當(dāng)界面重新布局(不包括初始化第一次)的前后都出現(xiàn)在屏幕上的ViewHolder,這樣就省去了不必要的CreateView和bindView的工作
mAttachedScrap
mAttachedScrap的數(shù)據(jù)結(jié)構(gòu):
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
它是用來保存將會原封不動的ViewHolder,例如調(diào)用notifyItemChanged方法時,在布局前先把那些屏幕內(nèi)沒有改變的ViewHolder保存在mAttachedScrap中,在布局時復(fù)用mAttachedScrap中的ViewHolder,布局結(jié)束后清空mAttachedScrap,緩存的動作只發(fā)生在布局前,復(fù)用的動作只發(fā)生在布局時,布局后清空
mChangeScrap
mChangeScrap的數(shù)據(jù)結(jié)構(gòu):
ArrayList<ViewHolder> mChangedScrap
它是用來保存位置會發(fā)生移動的ViewHolder,注意只是位置發(fā)生移動,內(nèi)容仍舊是原封不動,例如Remove掉一個Item,在布局前就能知道屏幕內(nèi)哪些View是原封不動的,這些原封不動的保存在mAttachedScrap中,哪些View是只變換位置的,這些只變換位置的保存在mChangeScrap中,在布局時不變的復(fù)用mAttachedScrap中的,只有位置變化的復(fù)用mChangeScrap中的,緩存的動作只發(fā)生在布局前,復(fù)用的動作只發(fā)生在布局時,布局后清空
用一個例子說明
上圖描述的是我們在一個RecyclerView中刪除B項(xiàng),并且調(diào)用了notifyItemRemoved()時,mAttachedScrap與mChangedScrap分別會臨時存儲的View情況。此時,A是在刪除前后完全沒有變化的,它會被臨時放入mAttachedScrap。B是我們要刪除的,它也會被放進(jìn)mAttachedScrap,但是會被額外標(biāo)記REMOVED,并且在之后會被移除。C和D在刪除B后會向上移動位置,因此他們會被臨時放入mChangedScrap中。E在此次操作前并沒有出現(xiàn)在屏幕中,它不屬于Scrap需要管轄的,Scrap只會緩存屏幕上已經(jīng)加載出來的ViewHolder。在刪除時,A,B,C,D都會進(jìn)入Scrap,而在刪除后,A,C,D都會回來,其中C,D只進(jìn)行了位置上的移動,其內(nèi)容沒有發(fā)生變化。
RecyclerView的局部刷新,依賴的就是Scrap的臨時緩存,我們需要通過notifyItemRemoved()、notifyItemChanged()等系列方法通知RecyclerView哪些位置發(fā)生了變化,這樣RecyclerView就能在處理這些變化的時候,使用Scrap來緩存其它內(nèi)容沒有發(fā)生變化的ViewHolder,于是就完成了局部刷新。需要注意的是,如果我們使用notifyDataSetChanged()方法來通知RecyclerView進(jìn)行更新,其會標(biāo)記所有屏幕上的View為FLAG_INVALID,從而不會嘗試使用Scrap來緩存一會兒還會回來的ViewHolder,而是統(tǒng)統(tǒng)直接扔進(jìn)RecycledViewPool池子里,回來的時候就要重新走一遍綁定的過程。
Scrap只是作為布局時的臨時緩存,它和滑動時的緩存沒有任何關(guān)系,它的detach和重新attach只臨時存在于布局的過程中。布局結(jié)束時Scrap列表應(yīng)該是空的,其成員要么被重新布局出來,要么將被移除,總之在布局過程結(jié)束的時候,兩個Scrap列表中都不應(yīng)該再存在任何東西。
2.緩存屏幕之外的ViewHolder——CacheView
CacheView是在RecyclerView列表位置產(chǎn)生變動的時候,對剛剛移出屏幕的View進(jìn)行回收復(fù)用的緩存列表,它的數(shù)據(jù)結(jié)構(gòu)是:
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
mCacheViews的緩存動作發(fā)生在滑動時,當(dāng)有Item滑出屏幕外,就會原封不動的保存到mCacheViews中,復(fù)用動作發(fā)生在滑動回來的時候,場景是當(dāng)上下小距離滑動時,剛劃出去的Item又劃回來,不用再重新創(chuàng)建和重新綁定數(shù)據(jù)
注意mCachedViews是有大小限制的,默認(rèn)最大是2,當(dāng)超過2時會怎樣呢?
if (forceRecycle || holder.isRecyclable()) { 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(); //??mViewCacheMax的值是2 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; } }
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) { recycleCachedViewAt(0); cachedViewSize--; }
當(dāng)mCachedViews的長度大于等于2時,就會移除索引為0的ViewHolder,這第0個是最早緩存進(jìn)來的,這體現(xiàn)了LRU緩存的特性,淘汰最近不常用的,然后因?yàn)槭茿rrayList所以索引是1的會替補(bǔ)到索引是0的位置,然后下面把新加進(jìn)來的VIewHolder放到索引是1的位置。
3.mViewCacheExtension
這是Google工程師預(yù)留給程序員的,可以做自己的緩存邏輯。
4.RecycledViewPool
RecycledViewPool是最后一層緩存:
RecycledViewPool getRecycledViewPool() { if (mRecyclerPool == null) { mRecyclerPool = new RecycledViewPool(); } return mRecyclerPool; } public static class RecycledViewPool { static class ScrapData { final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); int mMaxScrap = DEFAULT_MAX_SCRAP; long mCreateRunningAverageNs = 0; long mBindRunningAverageNs = 0; } SparseArray<ScrapData> mScrap = new SparseArray<>(); }
我們可以在RecyclerView中找到RecycledViewPool,可以看見它的保存形式是和上述的Srap、CacheView都不同的,它的數(shù)據(jù)結(jié)構(gòu)是一個SparseArray,它的Value的數(shù)據(jù)類型是ScrapData,ScrapData中主要維護(hù)了一個ViewHolder的ArrayList。
原因是RecycledViewPool保存的是以ViewHolder的viewType為區(qū)分(我們在重寫RecyclerView的onCreateViewHolder()時可以發(fā)現(xiàn)這里有個viewType參數(shù),可以借助它來實(shí)現(xiàn)展示不同類型的列表項(xiàng))的多個列表。
與前兩者不同,RecycledViewPool在進(jìn)行回收的時候,目標(biāo)只是回收一個該viewType的ViewHolder對象,并沒有保存下原來ViewHolder的內(nèi)容,在保存之前會進(jìn)行ViewHolder的格式化清空數(shù)據(jù)內(nèi)容,因?yàn)榍蹇蘸蟮腣iewHolder都是一樣的,所以它只保存前五個,后面的直接丟掉,并沒有使用LRU緩存邏輯,在復(fù)用時,將會調(diào)用bindViewHolder()按照我們在onBindViewHolder()描述的綁定步驟進(jìn)行重新綁定,從而搖身一變變成了一個新的列表項(xiàng)展示出來。
同樣,RecycledViewPool也有一個最大數(shù)量限制,默認(rèn)情況下是5。在沒有超過最大數(shù)量限制的情況下,Recycler會盡量把將被廢棄的ViewHolder回收到RecycledViewPool中,以期能被復(fù)用。值得一提的是,RecycledViewPool只會按照ViewType進(jìn)行區(qū)分,只要ViewType是相同的,甚至可以在多個RecyclerView中進(jìn)行通用的復(fù)用,只要為它們設(shè)置同一個RecycledViewPool就可以了。
總的來看,RecyclerView著重在兩個場景使用緩存與回收復(fù)用進(jìn)行了性能上的優(yōu)化。一是,在數(shù)據(jù)更新時,利用Scrap實(shí)現(xiàn)局部更新,盡可能地減少沒有被更改的View進(jìn)行無用地重新創(chuàng)建與綁定工作。二是,在快速滑動的時候,重復(fù)利用已經(jīng)滑過的ViewHolder對象,以盡可能減少重新創(chuàng)建ViewHolder對象時帶來的壓力。總體的思路就是:只要沒有改變,就直接重用;只要能不創(chuàng)建或重新綁定,就盡可能地偷懶。
二、到底是四級緩存還是三級緩存
Google工程師告訴我們有三層緩存,分別是:CacheView、mViewCacheExtension、RecycledViewPool
其實(shí)我們發(fā)現(xiàn)還有一個:Scrap
到此這篇關(guān)于Android RecyclerView緩存復(fù)用原理解析的文章就介紹到這了,更多相關(guān)Android RecyclerView 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Android實(shí)現(xiàn)RecyclerView嵌套流式布局的詳細(xì)過程
- Android RecyclerView實(shí)現(xiàn)吸頂動態(tài)效果流程分析
- Android RecyclerView四級緩存源碼層詳細(xì)分析
- Android RecyclerView使用入門介紹
- Android開發(fā)RecyclerView單獨(dú)刷新使用技巧
- Android開發(fā)RecyclerView實(shí)現(xiàn)折線圖效果
- Android?手寫RecyclerView實(shí)現(xiàn)列表加載
- Android獲取RecyclerView滑動距離方法詳細(xì)講解
相關(guān)文章
android基礎(chǔ)教程之a(chǎn)ndroid的listview與edittext沖突解決方法
這篇文章主要介紹了android的listview與edittext沖突解決方法,需要的朋友可以參考下2014-02-02Android開發(fā)手冊TextView控件及陰影效果實(shí)現(xiàn)
這篇文章主要為大家介紹了Android開發(fā)手冊TextView控件及陰影效果的實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Android編程之selector下設(shè)置背景屬性值的方法
這篇文章主要介紹了Android編程之selector下設(shè)置背景屬性值的方法,結(jié)合實(shí)例形式分析了Android的selector背景選擇器相關(guān)使用技巧,需要的朋友可以參考下2016-01-01Android控件SeekBar仿淘寶滑動驗(yàn)證效果
這篇文章主要為大家詳細(xì)介紹了Android控件SeekBar仿淘寶滑動驗(yàn)證效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11詳解Android應(yīng)用開發(fā)中Intent的作用及使用方法
這篇文章主要介紹了Android應(yīng)用開發(fā)中Intent的作用與用法,包括如何激活A(yù)ctivity組件與Intent的投遞等,需要的朋友可以參考下2016-03-03Flutter?EventBus事件總線的應(yīng)用詳解
這篇文章主要為大家介紹了Flutter?EventBus事件總線的應(yīng)用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-08-08Android 實(shí)現(xiàn)密碼輸入框動態(tài)明文/密文切換顯示效果
在項(xiàng)目中遇到需要提供給用戶一個密碼輸入框明文/密文切換顯示的需求,今天小編借腳本之家平臺給大家分享下Android 實(shí)現(xiàn)密碼輸入框動態(tài)明文/密文切換顯示效果,需要的朋友參考下2017-01-01Android ViewPager實(shí)現(xiàn)無限循環(huán)的實(shí)例
這篇文章主要介紹了Android ViewPager實(shí)現(xiàn)無限循環(huán)的實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-07-07