網(wǎng)站首頁 編程語言 正文
一、牽出緩存
都有哪些緩存,作用是什么,為什么這么設(shè)計(jì)
1.緩存還在屏幕內(nèi)的ViewHolder——Scrap緩存
Scrap是RecyclerView中最輕量的緩存,它不參與滑動(dòng)時(shí)的回收復(fù)用,只是作為重新布局時(shí)的一種臨時(shí)緩存,緩存(保存)動(dòng)作只發(fā)生在重新布局時(shí),布局完成后就要清空緩存。它的目的是,緩存當(dāng)界面重新布局(不包括初始化第一次)的前后都出現(xiàn)在屏幕上的ViewHolder,這樣就省去了不必要的CreateView和bindView的工作
mAttachedScrap
mAttachedScrap的數(shù)據(jù)結(jié)構(gòu):
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
它是用來保存將會(huì)原封不動(dòng)的ViewHolder,例如調(diào)用notifyItemChanged方法時(shí),在布局前先把那些屏幕內(nèi)沒有改變的ViewHolder保存在mAttachedScrap中,在布局時(shí)復(fù)用mAttachedScrap中的ViewHolder,布局結(jié)束后清空mAttachedScrap,緩存的動(dòng)作只發(fā)生在布局前,復(fù)用的動(dòng)作只發(fā)生在布局時(shí),布局后清空
mChangeScrap
mChangeScrap的數(shù)據(jù)結(jié)構(gòu):
ArrayList<ViewHolder> mChangedScrap
它是用來保存位置會(huì)發(fā)生移動(dòng)的ViewHolder,注意只是位置發(fā)生移動(dòng),內(nèi)容仍舊是原封不動(dòng),例如Remove掉一個(gè)Item,在布局前就能知道屏幕內(nèi)哪些View是原封不動(dòng)的,這些原封不動(dòng)的保存在mAttachedScrap中,哪些View是只變換位置的,這些只變換位置的保存在mChangeScrap中,在布局時(shí)不變的復(fù)用mAttachedScrap中的,只有位置變化的復(fù)用mChangeScrap中的,緩存的動(dòng)作只發(fā)生在布局前,復(fù)用的動(dòng)作只發(fā)生在布局時(shí),布局后清空
用一個(gè)例子說明
上圖描述的是我們?cè)谝粋€(gè)RecyclerView中刪除B項(xiàng),并且調(diào)用了notifyItemRemoved()時(shí),mAttachedScrap與mChangedScrap分別會(huì)臨時(shí)存儲(chǔ)的View情況。此時(shí),A是在刪除前后完全沒有變化的,它會(huì)被臨時(shí)放入mAttachedScrap。B是我們要?jiǎng)h除的,它也會(huì)被放進(jìn)mAttachedScrap,但是會(huì)被額外標(biāo)記REMOVED,并且在之后會(huì)被移除。C和D在刪除B后會(huì)向上移動(dòng)位置,因此他們會(huì)被臨時(shí)放入mChangedScrap中。E在此次操作前并沒有出現(xiàn)在屏幕中,它不屬于Scrap需要管轄的,Scrap只會(huì)緩存屏幕上已經(jīng)加載出來的ViewHolder。在刪除時(shí),A,B,C,D都會(huì)進(jìn)入Scrap,而在刪除后,A,C,D都會(huì)回來,其中C,D只進(jìn)行了位置上的移動(dòng),其內(nèi)容沒有發(fā)生變化。
RecyclerView的局部刷新,依賴的就是Scrap的臨時(shí)緩存,我們需要通過notifyItemRemoved()、notifyItemChanged()等系列方法通知RecyclerView哪些位置發(fā)生了變化,這樣RecyclerView就能在處理這些變化的時(shí)候,使用Scrap來緩存其它內(nèi)容沒有發(fā)生變化的ViewHolder,于是就完成了局部刷新。需要注意的是,如果我們使用notifyDataSetChanged()方法來通知RecyclerView進(jìn)行更新,其會(huì)標(biāo)記所有屏幕上的View為FLAG_INVALID,從而不會(huì)嘗試使用Scrap來緩存一會(huì)兒還會(huì)回來的ViewHolder,而是統(tǒng)統(tǒng)直接扔進(jìn)RecycledViewPool池子里,回來的時(shí)候就要重新走一遍綁定的過程。
Scrap只是作為布局時(shí)的臨時(shí)緩存,它和滑動(dòng)時(shí)的緩存沒有任何關(guān)系,它的detach和重新attach只臨時(shí)存在于布局的過程中。布局結(jié)束時(shí)Scrap列表應(yīng)該是空的,其成員要么被重新布局出來,要么將被移除,總之在布局過程結(jié)束的時(shí)候,兩個(gè)Scrap列表中都不應(yīng)該再存在任何東西。
2.緩存屏幕之外的ViewHolder——CacheView
CacheView是在RecyclerView列表位置產(chǎn)生變動(dòng)的時(shí)候,對(duì)剛剛移出屏幕的View進(jìn)行回收復(fù)用的緩存列表,它的數(shù)據(jù)結(jié)構(gòu)是:
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
mCacheViews的緩存動(dòng)作發(fā)生在滑動(dòng)時(shí),當(dāng)有Item滑出屏幕外,就會(huì)原封不動(dòng)的保存到mCacheViews中,復(fù)用動(dòng)作發(fā)生在滑動(dòng)回來的時(shí)候,場(chǎng)景是當(dāng)上下小距離滑動(dòng)時(shí),剛劃出去的Item又劃回來,不用再重新創(chuàng)建和重新綁定數(shù)據(jù)
注意mCachedViews是有大小限制的,默認(rèn)最大是2,當(dāng)超過2時(shí)會(huì)怎樣呢?
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的長(zhǎng)度大于等于2時(shí),就會(huì)移除索引為0的ViewHolder,這第0個(gè)是最早緩存進(jìn)來的,這體現(xiàn)了LRU緩存的特性,淘汰最近不常用的,然后因?yàn)槭茿rrayList所以索引是1的會(huì)替補(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)是一個(gè)SparseArray,它的Value的數(shù)據(jù)類型是ScrapData,ScrapData中主要維護(hù)了一個(gè)ViewHolder的ArrayList。
原因是RecycledViewPool保存的是以ViewHolder的viewType為區(qū)分(我們?cè)谥貙慠ecyclerView的onCreateViewHolder()時(shí)可以發(fā)現(xiàn)這里有個(gè)viewType參數(shù),可以借助它來實(shí)現(xiàn)展示不同類型的列表項(xiàng))的多個(gè)列表。
與前兩者不同,RecycledViewPool在進(jìn)行回收的時(shí)候,目標(biāo)只是回收一個(gè)該viewType的ViewHolder對(duì)象,并沒有保存下原來ViewHolder的內(nèi)容,在保存之前會(huì)進(jìn)行ViewHolder的格式化清空數(shù)據(jù)內(nèi)容,因?yàn)榍蹇蘸蟮腣iewHolder都是一樣的,所以它只保存前五個(gè),后面的直接丟掉,并沒有使用LRU緩存邏輯,在復(fù)用時(shí),將會(huì)調(diào)用bindViewHolder()按照我們?cè)趏nBindViewHolder()描述的綁定步驟進(jìn)行重新綁定,從而搖身一變變成了一個(gè)新的列表項(xiàng)展示出來。
同樣,RecycledViewPool也有一個(gè)最大數(shù)量限制,默認(rèn)情況下是5。在沒有超過最大數(shù)量限制的情況下,Recycler會(huì)盡量把將被廢棄的ViewHolder回收到RecycledViewPool中,以期能被復(fù)用。值得一提的是,RecycledViewPool只會(huì)按照ViewType進(jìn)行區(qū)分,只要ViewType是相同的,甚至可以在多個(gè)RecyclerView中進(jìn)行通用的復(fù)用,只要為它們?cè)O(shè)置同一個(gè)RecycledViewPool就可以了。
總的來看,RecyclerView著重在兩個(gè)場(chǎng)景使用緩存與回收復(fù)用進(jìn)行了性能上的優(yōu)化。一是,在數(shù)據(jù)更新時(shí),利用Scrap實(shí)現(xiàn)局部更新,盡可能地減少?zèng)]有被更改的View進(jìn)行無用地重新創(chuàng)建與綁定工作。二是,在快速滑動(dòng)的時(shí)候,重復(fù)利用已經(jīng)滑過的ViewHolder對(duì)象,以盡可能減少重新創(chuàng)建ViewHolder對(duì)象時(shí)帶來的壓力。總體的思路就是:只要沒有改變,就直接重用;只要能不創(chuàng)建或重新綁定,就盡可能地偷懶。
二、到底是四級(jí)緩存還是三級(jí)緩存
Google工程師告訴我們有三層緩存,分別是:CacheView、mViewCacheExtension、RecycledViewPool
其實(shí)我們發(fā)現(xiàn)還有一個(gè):Scrap
原文鏈接:https://blog.csdn.net/m0_37707561/article/details/126608516
相關(guān)推薦
- 2022-08-15 Redis緩存三大異常的處理方案梳理總結(jié)_Redis
- 2021-12-06 Flutter多項(xiàng)選擇彈窗實(shí)現(xiàn)詳解_Android
- 2022-11-01 ElasticSearch寫入流程實(shí)例解析_相關(guān)技巧
- 2024-01-28 在已有g(shù)it倉(cāng)庫(kù)的情況下,如何提交修改后的文件
- 2022-06-19 C語言詳細(xì)分析宏定義的使用_C 語言
- 2022-11-22 Kotlin對(duì)象的懶加載方式by?lazy?與?lateinit?異同詳解_Android
- 2022-10-05 WPF實(shí)現(xiàn)好看的Loading動(dòng)畫的示例代碼_C#教程
- 2022-07-29 Linux文件系統(tǒng)介紹_linux shell
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支