網站首頁 編程語言 正文
一、牽出緩存
都有哪些緩存,作用是什么,為什么這么設計
1.緩存還在屏幕內的ViewHolder——Scrap緩存
Scrap是RecyclerView中最輕量的緩存,它不參與滑動時的回收復用,只是作為重新布局時的一種臨時緩存,緩存(保存)動作只發生在重新布局時,布局完成后就要清空緩存。它的目的是,緩存當界面重新布局(不包括初始化第一次)的前后都出現在屏幕上的ViewHolder,這樣就省去了不必要的CreateView和bindView的工作
mAttachedScrap
mAttachedScrap的數據結構:
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
它是用來保存將會原封不動的ViewHolder,例如調用notifyItemChanged方法時,在布局前先把那些屏幕內沒有改變的ViewHolder保存在mAttachedScrap中,在布局時復用mAttachedScrap中的ViewHolder,布局結束后清空mAttachedScrap,緩存的動作只發生在布局前,復用的動作只發生在布局時,布局后清空
mChangeScrap
mChangeScrap的數據結構:
ArrayList<ViewHolder> mChangedScrap
它是用來保存位置會發生移動的ViewHolder,注意只是位置發生移動,內容仍舊是原封不動,例如Remove掉一個Item,在布局前就能知道屏幕內哪些View是原封不動的,這些原封不動的保存在mAttachedScrap中,哪些View是只變換位置的,這些只變換位置的保存在mChangeScrap中,在布局時不變的復用mAttachedScrap中的,只有位置變化的復用mChangeScrap中的,緩存的動作只發生在布局前,復用的動作只發生在布局時,布局后清空
用一個例子說明
上圖描述的是我們在一個RecyclerView中刪除B項,并且調用了notifyItemRemoved()時,mAttachedScrap與mChangedScrap分別會臨時存儲的View情況。此時,A是在刪除前后完全沒有變化的,它會被臨時放入mAttachedScrap。B是我們要刪除的,它也會被放進mAttachedScrap,但是會被額外標記REMOVED,并且在之后會被移除。C和D在刪除B后會向上移動位置,因此他們會被臨時放入mChangedScrap中。E在此次操作前并沒有出現在屏幕中,它不屬于Scrap需要管轄的,Scrap只會緩存屏幕上已經加載出來的ViewHolder。在刪除時,A,B,C,D都會進入Scrap,而在刪除后,A,C,D都會回來,其中C,D只進行了位置上的移動,其內容沒有發生變化。
RecyclerView的局部刷新,依賴的就是Scrap的臨時緩存,我們需要通過notifyItemRemoved()、notifyItemChanged()等系列方法通知RecyclerView哪些位置發生了變化,這樣RecyclerView就能在處理這些變化的時候,使用Scrap來緩存其它內容沒有發生變化的ViewHolder,于是就完成了局部刷新。需要注意的是,如果我們使用notifyDataSetChanged()方法來通知RecyclerView進行更新,其會標記所有屏幕上的View為FLAG_INVALID,從而不會嘗試使用Scrap來緩存一會兒還會回來的ViewHolder,而是統統直接扔進RecycledViewPool池子里,回來的時候就要重新走一遍綁定的過程。
Scrap只是作為布局時的臨時緩存,它和滑動時的緩存沒有任何關系,它的detach和重新attach只臨時存在于布局的過程中。布局結束時Scrap列表應該是空的,其成員要么被重新布局出來,要么將被移除,總之在布局過程結束的時候,兩個Scrap列表中都不應該再存在任何東西。
2.緩存屏幕之外的ViewHolder——CacheView
CacheView是在RecyclerView列表位置產生變動的時候,對剛剛移出屏幕的View進行回收復用的緩存列表,它的數據結構是:
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
mCacheViews的緩存動作發生在滑動時,當有Item滑出屏幕外,就會原封不動的保存到mCacheViews中,復用動作發生在滑動回來的時候,場景是當上下小距離滑動時,剛劃出去的Item又劃回來,不用再重新創建和重新綁定數據
注意mCachedViews是有大小限制的,默認最大是2,當超過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--;
}
當mCachedViews的長度大于等于2時,就會移除索引為0的ViewHolder,這第0個是最早緩存進來的,這體現了LRU緩存的特性,淘汰最近不常用的,然后因為是ArrayList所以索引是1的會替補到索引是0的位置,然后下面把新加進來的VIewHolder放到索引是1的位置。
3.mViewCacheExtension
這是Google工程師預留給程序員的,可以做自己的緩存邏輯。
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都不同的,它的數據結構是一個SparseArray,它的Value的數據類型是ScrapData,ScrapData中主要維護了一個ViewHolder的ArrayList。
原因是RecycledViewPool保存的是以ViewHolder的viewType為區分(我們在重寫RecyclerView的onCreateViewHolder()時可以發現這里有個viewType參數,可以借助它來實現展示不同類型的列表項)的多個列表。
與前兩者不同,RecycledViewPool在進行回收的時候,目標只是回收一個該viewType的ViewHolder對象,并沒有保存下原來ViewHolder的內容,在保存之前會進行ViewHolder的格式化清空數據內容,因為清空后的ViewHolder都是一樣的,所以它只保存前五個,后面的直接丟掉,并沒有使用LRU緩存邏輯,在復用時,將會調用bindViewHolder()按照我們在onBindViewHolder()描述的綁定步驟進行重新綁定,從而搖身一變變成了一個新的列表項展示出來。
同樣,RecycledViewPool也有一個最大數量限制,默認情況下是5。在沒有超過最大數量限制的情況下,Recycler會盡量把將被廢棄的ViewHolder回收到RecycledViewPool中,以期能被復用。值得一提的是,RecycledViewPool只會按照ViewType進行區分,只要ViewType是相同的,甚至可以在多個RecyclerView中進行通用的復用,只要為它們設置同一個RecycledViewPool就可以了。
總的來看,RecyclerView著重在兩個場景使用緩存與回收復用進行了性能上的優化。一是,在數據更新時,利用Scrap實現局部更新,盡可能地減少沒有被更改的View進行無用地重新創建與綁定工作。二是,在快速滑動的時候,重復利用已經滑過的ViewHolder對象,以盡可能減少重新創建ViewHolder對象時帶來的壓力。總體的思路就是:只要沒有改變,就直接重用;只要能不創建或重新綁定,就盡可能地偷懶。
二、到底是四級緩存還是三級緩存
Google工程師告訴我們有三層緩存,分別是:CacheView、mViewCacheExtension、RecycledViewPool
其實我們發現還有一個:Scrap
原文鏈接:https://blog.csdn.net/m0_37707561/article/details/126608516
相關推薦
- 2022-12-01 Flutter路由框架Fluro使用教程詳細講解_Android
- 2023-02-14 Cython處理C字符串的示例詳解_python
- 2022-09-03 C++11?condition_variable條件變量的用法說明_C 語言
- 2022-10-29 關于torch.load加載預訓練模型時 造成的 臨時分配的顯存 不釋放
- 2022-03-26 c#使用listbox的詳細方法和常見問題解決_C#教程
- 2022-01-17 將字符串轉換成時間戳,yyyymmss到yyyy-mm-dd ,之后從時間戳轉換成時間格式字符串
- 2022-04-12 C#?實例解釋面向對象編程中的單一功能原則(示例代碼)_C#教程
- 2024-03-03 layui彈窗編輯表單清空
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支